From 4a09183b851f68f4dcad5106dde8f8a7b8700c5c Mon Sep 17 00:00:00 2001 From: fwesselm Date: Sun, 30 Jun 2024 21:27:47 +0200 Subject: [PATCH 01/38] Compute bound differences using HighsCDouble --- src/mip/HighsDomain.cpp | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/mip/HighsDomain.cpp b/src/mip/HighsDomain.cpp index a00e8784c0..eb38717812 100644 --- a/src/mip/HighsDomain.cpp +++ b/src/mip/HighsDomain.cpp @@ -46,18 +46,18 @@ static double activityContributionMax(double coef, const double& lb, } } -static double computeDelta(double val, double oldbound, double newbound, - double inf, HighsInt& numinfs) { - // if bounds are huge, HighsCDouble should be used when computing bound - // differences. todo: qualify usage of HighsCDouble in this function. +static HighsCDouble computeDelta(double val, double oldbound, double newbound, + double inf, HighsInt& numinfs) { if (oldbound == inf) { --numinfs; - return newbound * val; + return static_cast(newbound) * val; } else if (newbound == inf) { ++numinfs; - return -oldbound * val; + return static_cast(-oldbound) * val; } else { - return (newbound - oldbound) * val; + return (static_cast(newbound) - + static_cast(oldbound)) * + val; } } @@ -481,8 +481,8 @@ void HighsDomain::CutpoolPropagation::updateActivityLbChange(HighsInt col, cutpool->getMatrix().forEachPositiveColumnEntry( col, [&](HighsInt row, double val) { assert(val > 0); - double deltamin = computeDelta(val, oldbound, newbound, -kHighsInf, - activitycutsinf_[row]); + HighsCDouble deltamin = computeDelta(val, oldbound, newbound, + -kHighsInf, activitycutsinf_[row]); activitycuts_[row] += deltamin; if (deltamin <= 0) { @@ -542,8 +542,8 @@ void HighsDomain::CutpoolPropagation::updateActivityUbChange(HighsInt col, cutpool->getMatrix().forEachNegativeColumnEntry( col, [&](HighsInt row, double val) { assert(val < 0); - double deltamin = computeDelta(val, oldbound, newbound, kHighsInf, - activitycutsinf_[row]); + HighsCDouble deltamin = computeDelta(val, oldbound, newbound, kHighsInf, + activitycutsinf_[row]); activitycuts_[row] += deltamin; if (deltamin <= 0) { @@ -1528,7 +1528,7 @@ void HighsDomain::updateActivityLbChange(HighsInt col, double oldbound, for (HighsInt i = start; i != end; ++i) { if (mip->a_matrix_.value_[i] > 0) { - double deltamin = + HighsCDouble deltamin = computeDelta(mip->a_matrix_.value_[i], oldbound, newbound, -kHighsInf, activitymininf_[mip->a_matrix_.index_[i]]); activitymin_[mip->a_matrix_.index_[i]] += deltamin; @@ -1572,7 +1572,7 @@ void HighsDomain::updateActivityLbChange(HighsInt col, double oldbound, mip->row_upper_[mip->a_matrix_.index_[i]] != kHighsInf) markPropagate(mip->a_matrix_.index_[i]); } else { - double deltamax = + HighsCDouble deltamax = computeDelta(mip->a_matrix_.value_[i], oldbound, newbound, -kHighsInf, activitymaxinf_[mip->a_matrix_.index_[i]]); activitymax_[mip->a_matrix_.index_[i]] += deltamax; @@ -1667,7 +1667,7 @@ void HighsDomain::updateActivityUbChange(HighsInt col, double oldbound, for (HighsInt i = start; i != end; ++i) { if (mip->a_matrix_.value_[i] > 0) { - double deltamax = + HighsCDouble deltamax = computeDelta(mip->a_matrix_.value_[i], oldbound, newbound, kHighsInf, activitymaxinf_[mip->a_matrix_.index_[i]]); activitymax_[mip->a_matrix_.index_[i]] += deltamax; @@ -1714,7 +1714,7 @@ void HighsDomain::updateActivityUbChange(HighsInt col, double oldbound, // propagateinds_.push_back(mip->a_matrix_.index_[i]); } } else { - double deltamin = + HighsCDouble deltamin = computeDelta(mip->a_matrix_.value_[i], oldbound, newbound, kHighsInf, activitymininf_[mip->a_matrix_.index_[i]]); activitymin_[mip->a_matrix_.index_[i]] += deltamin; From 3681efa428da085ad251e72329d0ae1ba968dc79 Mon Sep 17 00:00:00 2001 From: fwesselm Date: Sun, 30 Jun 2024 21:51:49 +0200 Subject: [PATCH 02/38] Use tolerances when rounding in postsolve --- src/presolve/HighsPostsolveStack.cpp | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/src/presolve/HighsPostsolveStack.cpp b/src/presolve/HighsPostsolveStack.cpp index f490a3689a..6172394186 100644 --- a/src/presolve/HighsPostsolveStack.cpp +++ b/src/presolve/HighsPostsolveStack.cpp @@ -905,10 +905,12 @@ bool HighsPostsolveStack::DuplicateColumn::okMerge( const double scale = colScale; const bool x_int = colIntegral; const bool y_int = duplicateColIntegral; - const double x_lo = x_int ? std::ceil(colLower) : colLower; - const double x_up = x_int ? std::floor(colUpper) : colUpper; - const double y_lo = y_int ? std::ceil(duplicateColLower) : duplicateColLower; - const double y_up = y_int ? std::floor(duplicateColUpper) : duplicateColUpper; + 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"; @@ -1027,10 +1029,16 @@ void HighsPostsolveStack::DuplicateColumn::undoFix( const bool y_int = duplicateColIntegral; const int x_ix = col; const int y_ix = duplicateCol; - const double x_lo = x_int ? std::ceil(colLower) : colLower; - const double x_up = x_int ? std::floor(colUpper) : colUpper; - const double y_lo = y_int ? std::ceil(duplicateColLower) : duplicateColLower; - const double y_up = y_int ? std::floor(duplicateColUpper) : duplicateColUpper; + 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 = merge_value; double y_v; From 9a98a6fc09a624814b146bac9968e893db4c58db Mon Sep 17 00:00:00 2001 From: fwesselm Date: Mon, 1 Jul 2024 11:10:06 +0200 Subject: [PATCH 03/38] Minor change --- src/presolve/HighsPostsolveStack.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/presolve/HighsPostsolveStack.cpp b/src/presolve/HighsPostsolveStack.cpp index 6172394186..bd73f128e4 100644 --- a/src/presolve/HighsPostsolveStack.cpp +++ b/src/presolve/HighsPostsolveStack.cpp @@ -1017,9 +1017,8 @@ void HighsPostsolveStack::DuplicateColumn::undoFix( }; auto isFeasible = [&](const double l, const double v, const double u) { - if (v < l - primal_feasibility_tolerance) return false; - if (v > u + primal_feasibility_tolerance) return false; - return true; + return v >= l - primal_feasibility_tolerance && + v <= u + primal_feasibility_tolerance; }; const double merge_value = col_value[col]; const double value_max = 1000; From 600b069569b30070bc390e6188d92bf0db3649d9 Mon Sep 17 00:00:00 2001 From: JAJHall Date: Tue, 9 Jul 2024 15:40:53 +0100 Subject: [PATCH 04/38] Added int64_t mip_total_lp_iterations to HighsCallbackDataOut and modified accessor function --- check/TestCallbacks.cpp | 9 +++++---- src/interfaces/highs_c_api.cpp | 3 +++ src/interfaces/highs_c_api.h | 2 ++ src/lp_data/HighsCallbackStruct.h | 1 + src/mip/HighsMipSolverData.cpp | 2 ++ 5 files changed, 13 insertions(+), 4 deletions(-) diff --git a/check/TestCallbacks.cpp b/check/TestCallbacks.cpp index 171bf52721..0c221288e8 100644 --- a/check/TestCallbacks.cpp +++ b/check/TestCallbacks.cpp @@ -190,12 +190,13 @@ std::functionmip_node_count, data_out->running_time, - data_out->mip_dual_bound, data_out->mip_primal_bound, - data_out->mip_gap, data_out->objective_function_value, - message.c_str()); + data_out->mip_node_count, data_out->mip_total_lp_iterations, + data_out->running_time, data_out->mip_dual_bound, + data_out->mip_primal_bound, data_out->mip_gap, + data_out->objective_function_value, message.c_str()); }; TEST_CASE("my-callback-logging", "[highs-callback]") { diff --git a/src/interfaces/highs_c_api.cpp b/src/interfaces/highs_c_api.cpp index 07e8a0054e..0642b73283 100644 --- a/src/interfaces/highs_c_api.cpp +++ b/src/interfaces/highs_c_api.cpp @@ -1364,6 +1364,9 @@ const void* Highs_getCallbackDataOutItem(const HighsCallbackDataOut* data_out, return (void*)(&data_out->objective_function_value); } else if (!strcmp(item_name, kHighsCallbackDataOutMipNodeCountName)) { return (void*)(&data_out->mip_node_count); + } else if (!strcmp(item_name, + kHighsCallbackDataOutMipTotalLpIterationsName)) { + return (void*)(&data_out->mip_total_lp_iterations); } else if (!strcmp(item_name, kHighsCallbackDataOutMipPrimalBoundName)) { return (void*)(&data_out->mip_primal_bound); } else if (!strcmp(item_name, kHighsCallbackDataOutMipDualBoundName)) { diff --git a/src/interfaces/highs_c_api.h b/src/interfaces/highs_c_api.h index c9c5ad1cd1..1a43dd8e81 100644 --- a/src/interfaces/highs_c_api.h +++ b/src/interfaces/highs_c_api.h @@ -120,6 +120,8 @@ const char* const kHighsCallbackDataOutPdlpIterationCountName = const char* const kHighsCallbackDataOutObjectiveFunctionValueName = "objective_function_value"; const char* const kHighsCallbackDataOutMipNodeCountName = "mip_node_count"; +const char* const kHighsCallbackDataOutMipTotalLpIterationsName = + "mip_total_lp_iterations"; const char* const kHighsCallbackDataOutMipPrimalBoundName = "mip_primal_bound"; const char* const kHighsCallbackDataOutMipDualBoundName = "mip_dual_bound"; const char* const kHighsCallbackDataOutMipGapName = "mip_gap"; diff --git a/src/lp_data/HighsCallbackStruct.h b/src/lp_data/HighsCallbackStruct.h index b76716d3eb..a5f7140834 100644 --- a/src/lp_data/HighsCallbackStruct.h +++ b/src/lp_data/HighsCallbackStruct.h @@ -32,6 +32,7 @@ typedef struct { HighsInt pdlp_iteration_count; double objective_function_value; int64_t mip_node_count; + int64_t mip_total_lp_iterations; double mip_primal_bound; double mip_dual_bound; double mip_gap; diff --git a/src/mip/HighsMipSolverData.cpp b/src/mip/HighsMipSolverData.cpp index 1d926134ab..1b0610a340 100644 --- a/src/mip/HighsMipSolverData.cpp +++ b/src/mip/HighsMipSolverData.cpp @@ -1999,6 +1999,8 @@ bool HighsMipSolverData::interruptFromCallbackWithData( mipsolver.callback_->data_out.objective_function_value = mipsolver_objective_value; mipsolver.callback_->data_out.mip_node_count = mipsolver.mipdata_->num_nodes; + mipsolver.callback_->data_out.mip_total_lp_iterations = + mipsolver.mipdata_->total_lp_iterations; mipsolver.callback_->data_out.mip_primal_bound = primal_bound; mipsolver.callback_->data_out.mip_dual_bound = dual_bound; // Option mip_rel_gap, and mip_gap in HighsInfo, are both fractions, From 66849cac15451c76bafd5d40b3130e8f26b794b4 Mon Sep 17 00:00:00 2001 From: JAJHall Date: Tue, 9 Jul 2024 15:43:38 +0100 Subject: [PATCH 05/38] Reset FEATURES.md and added comment on fix-1814 --- FEATURES.md | 18 +----------------- 1 file changed, 1 insertion(+), 17 deletions(-) diff --git a/FEATURES.md b/FEATURES.md index c587caf6ee..9d7043302e 100644 --- a/FEATURES.md +++ b/FEATURES.md @@ -1,22 +1,6 @@ ## Build changes -The python wrapper highspy is now available for aarch64 on manylinux -This allows highs to be run through Python on AWS arm64 - -Bug fix for fortran on macOS - ## Code changes -The accessor function Highs_getCallbackDataOutItem in the C API means -that `pdlp_iteration_count` can be moved back to where it was inserted -into the `HighsCallbackDataOut` struct in v1.7.0, which broke the C -API. This fixes #1812 - -Some duplicate code has been eliminated from the MIP solver, and -modifications made to eliminate compiler warnings - -Declaration of the (deprecated) method `char* highsCompilationDate()` -has been corrected - -Fixed bug when describing integrality status during the human-readable solution write +Added `int64_t mip_total_lp_iterations` to `HighsCallbackDataOut` and modified accessor function From 2d078b0c5e3810d9a5ef97bfd000213d3d422288 Mon Sep 17 00:00:00 2001 From: JAJHall Date: Tue, 9 Jul 2024 22:49:12 +0100 Subject: [PATCH 06/38] Introduced highsFprintfString to replace fprintf(file so that solution etc to console can be handled through callbacks --- check/TestCallbacks.cpp | 22 +++- src/io/HighsIO.cpp | 10 ++ src/io/HighsIO.h | 7 ++ src/lp_data/HighsModelUtils.cpp | 188 +++++++++++++++++++------------- src/lp_data/HighsModelUtils.h | 21 ++-- src/mip/HighsMipSolverData.cpp | 9 +- 6 files changed, 166 insertions(+), 91 deletions(-) diff --git a/check/TestCallbacks.cpp b/check/TestCallbacks.cpp index 0c221288e8..06d9cc7e94 100644 --- a/check/TestCallbacks.cpp +++ b/check/TestCallbacks.cpp @@ -6,7 +6,7 @@ #include "catch.hpp" #include "lp_data/HighsCallback.h" -const bool dev_run = false; +const bool dev_run = true; const double egout_optimal_objective = 568.1007; const double egout_objective_target = 610; @@ -122,8 +122,9 @@ HighsCallbackFunctionType userInterruptCallback = } if (callback_type == kCallbackLogging) { if (dev_run) - printf("userInterruptCallback(type %2d; data %2d): %s", - callback_type, local_callback_data, message.c_str()); + printf("Callback: %s", message.c_str()); +// printf("userInterruptCallback(type %2d; data %2d): %s", +// callback_type, local_callback_data, message.c_str()); } else if (callback_type == kCallbackSimplexInterrupt) { if (dev_run) printf( @@ -265,6 +266,21 @@ TEST_CASE("highs-callback-logging", "[highs-callback]") { highs.run(); } +TEST_CASE("highs-callback-solution-basis-logging", "[highs-callback]") { + std::string filename = std::string(HIGHS_DIR) + "/check/instances/avgas.mps"; + int user_callback_data = kUserCallbackData; + void* p_user_callback_data = + reinterpret_cast(static_cast(user_callback_data)); + Highs highs; + highs.setOptionValue("output_flag", dev_run); + highs.readModel(filename); + highs.run(); + highs.setCallback(userInterruptCallback, p_user_callback_data); + highs.startCallback(kCallbackLogging); + highs.writeSolution("", kSolutionStylePretty); + highs.writeBasis(""); +} + TEST_CASE("highs-callback-simplex-interrupt", "[highs-callback]") { std::string filename = std::string(HIGHS_DIR) + "/check/instances/adlittle.mps"; diff --git a/src/io/HighsIO.cpp b/src/io/HighsIO.cpp index 38fbd6b405..fc622a5c20 100644 --- a/src/io/HighsIO.cpp +++ b/src/io/HighsIO.cpp @@ -219,6 +219,16 @@ void highsLogDev(const HighsLogOptions& log_options_, const HighsLogType type, va_end(argptr); } +void highsFprintfString(FILE* file, const HighsLogOptions& log_options_, + const std::string& s) { + if (file == nullptr) return; + if (file == stdout) { + highsLogUser(log_options_, HighsLogType::kInfo, "%s", s.c_str()); + } else { + fprintf(file, "%s", s.c_str()); + } +} + void highsReportDevInfo(const HighsLogOptions* log_options, const std::string line) { if (log_options) { diff --git a/src/io/HighsIO.h b/src/io/HighsIO.h index adead90c24..6da477a5d5 100644 --- a/src/io/HighsIO.h +++ b/src/io/HighsIO.h @@ -88,6 +88,13 @@ void highsLogUser(const HighsLogOptions& log_options_, const HighsLogType type, void highsLogDev(const HighsLogOptions& log_options_, const HighsLogType type, const char* format, ...); +/** + * @brief Replaces fprintf(file,... so that when file=stdout highsLogUser is used + */ +// Printing format: must contain exactly one "\n" at end of format +void highsFprintfString(FILE* file, const HighsLogOptions& log_options_, + const std::string& s); + /** * @brief For development logging when true log_options may not be available - * indicated by null pointer diff --git a/src/lp_data/HighsModelUtils.cpp b/src/lp_data/HighsModelUtils.cpp index 6a60010282..201c090581 100644 --- a/src/lp_data/HighsModelUtils.cpp +++ b/src/lp_data/HighsModelUtils.cpp @@ -133,7 +133,8 @@ std::string typeToString(const HighsVarType type) { } void writeModelBoundSolution( - FILE* file, const bool columns, const HighsInt dim, + FILE* file, const HighsLogOptions& log_options, + const bool columns, const HighsInt dim, const std::vector& lower, const std::vector& upper, const std::vector& names, const bool have_primal, const std::vector& primal, const bool have_dual, @@ -146,73 +147,73 @@ void writeModelBoundSolution( if (have_dual) assert((int)dual.size() >= dim); if (have_basis) assert((int)status.size() >= dim); const bool have_integrality = integrality != NULL; - std::string var_status_string; - if (columns) { - fprintf(file, "Columns\n"); - } else { - fprintf(file, "Rows\n"); - } - fprintf( - file, - " Index Status Lower Upper Primal Dual"); - if (have_integrality) fprintf(file, " Type "); + std::stringstream ss; + std::string s = columns ? "Columns\n" : "Rows\n"; + highsFprintfString(file, log_options, s); + ss.str(std::string()); + ss << " Index Status Lower Upper Primal Dual"; + if (have_integrality) ss << " Type "; if (have_names) { - fprintf(file, " Name\n"); + ss << " Name\n"; } else { - fprintf(file, "\n"); + ss << "\n"; } + highsFprintfString(file, log_options, ss.str()); for (HighsInt ix = 0; ix < dim; ix++) { - if (have_basis) { - var_status_string = statusToString(status[ix], lower[ix], upper[ix]); - } else { - var_status_string = ""; - } - fprintf(file, "%9" HIGHSINT_FORMAT " %4s %12g %12g", ix, - var_status_string.c_str(), lower[ix], upper[ix]); + ss.str(std::string()); + std::string var_status_string = have_basis ? + statusToString(status[ix], lower[ix], upper[ix]) : ""; + ss << highsFormatToString("%9" HIGHSINT_FORMAT " %4s %12g %12g", ix, + var_status_string.c_str(), lower[ix], upper[ix]); if (have_primal) { - fprintf(file, " %12g", primal[ix]); + ss << highsFormatToString(" %12g", primal[ix]); } else { - fprintf(file, " "); + ss << " "; } if (have_dual) { - fprintf(file, " %12g", dual[ix]); + ss << highsFormatToString(" %12g", dual[ix]); } else { - fprintf(file, " "); + ss << " "; } if (have_integrality) - fprintf(file, " %s", typeToString(integrality[ix]).c_str()); + ss << highsFormatToString(" %s", typeToString(integrality[ix]).c_str()); if (have_names) { - fprintf(file, " %-s\n", names[ix].c_str()); + ss << highsFormatToString(" %-s\n", names[ix].c_str()); } else { - fprintf(file, "\n"); + ss << "\n"; } + highsFprintfString(file, log_options, ss.str()); } } -void writeModelObjective(FILE* file, const HighsModel& model, +void writeModelObjective(FILE* file, const HighsLogOptions& log_options, + const HighsModel& model, const std::vector& primal_solution) { HighsCDouble objective_value = model.lp_.objectiveCDoubleValue(primal_solution); objective_value += model.hessian_.objectiveCDoubleValue(primal_solution); - writeObjectiveValue(file, (double)objective_value); + writeObjectiveValue(file, log_options, (double)objective_value); } -void writeLpObjective(FILE* file, const HighsLp& lp, +void writeLpObjective(FILE* file, const HighsLogOptions& log_options, + const HighsLp& lp, const std::vector& primal_solution) { HighsCDouble objective_value = lp.objectiveCDoubleValue(primal_solution); - writeObjectiveValue(file, (double)objective_value); + writeObjectiveValue(file, log_options, (double)objective_value); } -void writeObjectiveValue(FILE* file, const double objective_value) { +void writeObjectiveValue(FILE* file, const HighsLogOptions& log_options, + const double objective_value) { std::array objStr = highsDoubleToString( objective_value, kHighsSolutionValueToStringTolerance); - fprintf(file, "Objective %s\n", objStr.data()); + std::string s = highsFormatToString("Objective %s\n", objStr.data()); + highsFprintfString(file, log_options, s); } -void writePrimalSolution(FILE* file, const HighsLp& lp, +void writePrimalSolution(FILE* file, const HighsLogOptions& log_options, + const HighsLp& lp, const std::vector& primal_solution, const bool sparse) { - std::stringstream ss; HighsInt num_nonzero_primal_value = 0; const bool have_col_names = lp.col_names_.size() > 0; if (sparse) { @@ -223,8 +224,12 @@ void writePrimalSolution(FILE* file, const HighsLp& lp, // Indicate the number of column values to be written out, depending // on whether format is sparse: either lp.num_col_ if not sparse, or // the negation of the number of nonzero values, if sparse - fprintf(file, "# Columns %" HIGHSINT_FORMAT "\n", + + std::stringstream ss; + ss.str(std::string()); + ss << highsFormatToString("# Columns %" HIGHSINT_FORMAT "\n", sparse ? -num_nonzero_primal_value : lp.num_col_); + highsFprintfString(file, log_options, ss.str()); for (HighsInt ix = 0; ix < lp.num_col_; ix++) { if (sparse && !primal_solution[ix]) continue; std::array valStr = highsDoubleToString( @@ -233,12 +238,16 @@ void writePrimalSolution(FILE* file, const HighsLp& lp, ss.str(std::string()); ss << "C" << ix; const std::string name = have_col_names ? lp.col_names_[ix] : ss.str(); - fprintf(file, "%-s %s", name.c_str(), valStr.data()); - if (sparse) fprintf(file, " %d", int(ix)); - fprintf(file, "\n"); + ss.str(std::string()); + ss << highsFormatToString("%-s %s", name.c_str(), valStr.data()); + if (sparse) ss << highsFormatToString(" %d", int(ix)); + ss << "\n"; + highsFprintfString(file, log_options, ss.str()); } } -void writeModelSolution(FILE* file, const HighsModel& model, + +void writeModelSolution(FILE* file, const HighsLogOptions& log_options, + const HighsModel& model, const HighsSolution& solution, const HighsInfo& info, const bool sparse) { const HighsLp& lp = model.lp_; @@ -246,7 +255,6 @@ void writeModelSolution(FILE* file, const HighsModel& model, const bool have_row_names = lp.row_names_.size() > 0; const bool have_primal = solution.value_valid; const bool have_dual = solution.dual_valid; - std::stringstream ss; if (have_col_names) assert((int)lp.col_names_.size() >= lp.num_col_); if (have_row_names) assert((int)lp.row_names_.size() >= lp.num_row_); if (have_primal) { @@ -259,20 +267,23 @@ void writeModelSolution(FILE* file, const HighsModel& model, assert((int)solution.row_dual.size() >= lp.num_row_); assert(info.dual_solution_status != kSolutionStatusNone); } - fprintf(file, "\n# Primal solution values\n"); + std::stringstream ss; + highsFprintfString(file, log_options, "\n# Primal solution values\n"); if (!have_primal || info.primal_solution_status == kSolutionStatusNone) { - fprintf(file, "None\n"); + highsFprintfString(file, log_options, "None\n"); } else { if (info.primal_solution_status == kSolutionStatusFeasible) { - fprintf(file, "Feasible\n"); + highsFprintfString(file, log_options, "Feasible\n"); } else { assert(info.primal_solution_status == kSolutionStatusInfeasible); - fprintf(file, "Infeasible\n"); + highsFprintfString(file, log_options, "Infeasible\n"); } - writeModelObjective(file, model, solution.col_value); - writePrimalSolution(file, model.lp_, solution.col_value, sparse); + writeModelObjective(file, log_options, model, solution.col_value); + writePrimalSolution(file, log_options, model.lp_, solution.col_value, sparse); if (sparse) return; - fprintf(file, "# Rows %" HIGHSINT_FORMAT "\n", lp.num_row_); + ss.str(std::string()); + ss << highsFormatToString("# Rows %" HIGHSINT_FORMAT "\n", lp.num_row_); + highsFprintfString(file, log_options, ss.str()); for (HighsInt ix = 0; ix < lp.num_row_; ix++) { std::array valStr = highsDoubleToString( solution.row_value[ix], kHighsSolutionValueToStringTolerance); @@ -280,36 +291,46 @@ void writeModelSolution(FILE* file, const HighsModel& model, ss.str(std::string()); ss << "R" << ix; const std::string name = have_row_names ? lp.row_names_[ix] : ss.str(); - fprintf(file, "%-s %s\n", name.c_str(), valStr.data()); + ss.str(std::string()); + ss << highsFormatToString("%-s %s\n", name.c_str(), valStr.data()); + highsFprintfString(file, log_options, ss.str()); } } - fprintf(file, "\n# Dual solution values\n"); + highsFprintfString(file, log_options, "\n# Dual solution values\n"); if (!have_dual || info.dual_solution_status == kSolutionStatusNone) { - fprintf(file, "None\n"); + highsFprintfString(file, log_options, "None\n"); } else { if (info.dual_solution_status == kSolutionStatusFeasible) { - fprintf(file, "Feasible\n"); + highsFprintfString(file, log_options, "Feasible\n"); } else { assert(info.dual_solution_status == kSolutionStatusInfeasible); - fprintf(file, "Infeasible\n"); + highsFprintfString(file, log_options, "Infeasible\n"); } - fprintf(file, "# Columns %" HIGHSINT_FORMAT "\n", lp.num_col_); + ss.str(std::string()); + ss << highsFormatToString("# Columns %" HIGHSINT_FORMAT "\n", lp.num_col_); + highsFprintfString(file, log_options, ss.str()); for (HighsInt ix = 0; ix < lp.num_col_; ix++) { std::array valStr = highsDoubleToString( solution.col_dual[ix], kHighsSolutionValueToStringTolerance); ss.str(std::string()); ss << "C" << ix; const std::string name = have_col_names ? lp.col_names_[ix] : ss.str(); - fprintf(file, "%-s %s\n", name.c_str(), valStr.data()); + ss.str(std::string()); + ss << highsFormatToString("%-s %s\n", name.c_str(), valStr.data()); + highsFprintfString(file, log_options, ss.str()); } - fprintf(file, "# Rows %" HIGHSINT_FORMAT "\n", lp.num_row_); + ss.str(std::string()); + ss << highsFormatToString("# Rows %" HIGHSINT_FORMAT "\n", lp.num_row_); + highsFprintfString(file, log_options, ss.str()); for (HighsInt ix = 0; ix < lp.num_row_; ix++) { std::array valStr = highsDoubleToString( solution.row_dual[ix], kHighsSolutionValueToStringTolerance); ss.str(std::string()); ss << "R" << ix; const std::string name = have_row_names ? lp.row_names_[ix] : ss.str(); - fprintf(file, "%-s %s\n", name.c_str(), valStr.data()); + ss.str(std::string()); + ss << highsFormatToString("%-s %s\n", name.c_str(), valStr.data()); + highsFprintfString(file, log_options, ss.str()); } } } @@ -395,25 +416,33 @@ void writeSolutionFile(FILE* file, const HighsOptions& options, const bool have_dual = solution.dual_valid; const bool have_basis = basis.valid; const HighsLp& lp = model.lp_; + const HighsLogOptions& log_options = options.log_options; if (style == kSolutionStyleOldRaw) { - writeOldRawSolution(file, lp, basis, solution); + writeOldRawSolution(file, log_options, lp, basis, solution); } else if (style == kSolutionStylePretty) { const HighsVarType* integrality = lp.integrality_.size() > 0 ? lp.integrality_.data() : nullptr; - writeModelBoundSolution(file, true, lp.num_col_, lp.col_lower_, + writeModelBoundSolution(file, log_options, true, lp.num_col_, lp.col_lower_, lp.col_upper_, lp.col_names_, have_primal, solution.col_value, have_dual, solution.col_dual, have_basis, basis.col_status, integrality); - writeModelBoundSolution(file, false, lp.num_row_, lp.row_lower_, + writeModelBoundSolution(file, log_options, false, lp.num_row_, lp.row_lower_, lp.row_upper_, lp.row_names_, have_primal, solution.row_value, have_dual, solution.row_dual, have_basis, basis.row_status); - fprintf(file, "\nModel status: %s\n", + highsFprintfString(file, log_options, "\n"); + std::stringstream ss; + ss.str(std::string()); + ss << highsFormatToString("Model status: %s\n", utilModelStatusToString(model_status).c_str()); + highsFprintfString(file, log_options, ss.str()); std::array objStr = highsDoubleToString((double)info.objective_function_value, kHighsSolutionValueToStringTolerance); - fprintf(file, "\nObjective value: %s\n", objStr.data()); + highsFprintfString(file, log_options, "\n"); + ss.str(std::string()); + ss << highsFormatToString("Objective value: %s\n", objStr.data()); + highsFprintfString(file, log_options, ss.str()); } else if (style == kSolutionStyleGlpsolRaw || style == kSolutionStyleGlpsolPretty) { const bool raw = style == kSolutionStyleGlpsolRaw; @@ -423,9 +452,12 @@ void writeSolutionFile(FILE* file, const HighsOptions& options, // Standard raw solution file, possibly sparse => only nonzero primal values const bool sparse = style == kSolutionStyleSparse; assert(style == kSolutionStyleRaw || sparse); - fprintf(file, "Model status\n"); - fprintf(file, "%s\n", utilModelStatusToString(model_status).c_str()); - writeModelSolution(file, model, solution, info, sparse); + highsFprintfString(file, log_options, "Model status\n"); + std::stringstream ss; + ss.str(std::string()); + ss << highsFormatToString("%s\n", utilModelStatusToString(model_status).c_str()); + highsFprintfString(file, log_options, ss.str()); + writeModelSolution(file, log_options, model, solution, info, sparse); } } @@ -951,14 +983,15 @@ void writeGlpsolSolution(FILE* file, const HighsOptions& options, double absolute_error_value; HighsInt relative_error_index; double relative_error_value; + const HighsLogOptions& log_options = options.log_options; getKktFailures(options, model, solution, basis, local_info, errors, true); - fprintf(file, "\n"); + highsFprintfString(file, log_options, "\n"); if (is_mip) { - fprintf(file, "Integer feasibility conditions:\n"); + highsFprintfString(file, log_options, "Integer feasibility conditions:\n"); } else { - fprintf(file, "Karush-Kuhn-Tucker optimality conditions:\n"); + highsFprintfString(file, log_options, "Karush-Kuhn-Tucker optimality conditions:\n"); } - fprintf(file, "\n"); + highsFprintfString(file, log_options, "\n"); // Primal residual absolute_error_value = errors.max_primal_residual.absolute_value; absolute_error_index = errors.max_primal_residual.absolute_index + 1; @@ -976,7 +1009,7 @@ void writeGlpsolSolution(FILE* file, const HighsOptions& options, : relative_error_value <= kGlpsolLowQuality ? "Low quality" : "PRIMAL SOLUTION IS WRONG"); - fprintf(file, "\n"); + highsFprintfString(file, log_options, "\n"); // Primal infeasibility absolute_error_value = errors.max_primal_infeasibility.absolute_value; @@ -1003,7 +1036,7 @@ void writeGlpsolSolution(FILE* file, const HighsOptions& options, : relative_error_value <= kGlpsolLowQuality ? "Low quality" : "PRIMAL SOLUTION IS INFEASIBLE"); - fprintf(file, "\n"); + highsFprintfString(file, log_options, "\n"); if (have_dual) { // Dual residual @@ -1023,7 +1056,7 @@ void writeGlpsolSolution(FILE* file, const HighsOptions& options, : relative_error_value <= kGlpsolLowQuality ? "Low quality" : "DUAL SOLUTION IS WRONG"); - fprintf(file, "\n"); + highsFprintfString(file, log_options, "\n"); // Dual infeasibility absolute_error_value = errors.max_dual_infeasibility.absolute_value; @@ -1051,12 +1084,13 @@ void writeGlpsolSolution(FILE* file, const HighsOptions& options, : relative_error_value <= kGlpsolLowQuality ? "Low quality" : "DUAL SOLUTION IS INFEASIBLE"); - fprintf(file, "\n"); + highsFprintfString(file, log_options, "\n"); } - fprintf(file, "End of output\n"); + highsFprintfString(file, log_options, "End of output\n"); } -void writeOldRawSolution(FILE* file, const HighsLp& lp, const HighsBasis& basis, +void writeOldRawSolution(FILE* file, const HighsLogOptions& log_options, + const HighsLp& lp, const HighsBasis& basis, const HighsSolution& solution) { const bool have_value = solution.value_valid; const bool have_dual = solution.dual_valid; @@ -1103,7 +1137,7 @@ void writeOldRawSolution(FILE* file, const HighsLp& lp, const HighsBasis& basis, fprintf(file, "F"); } fprintf(file, " Basis\n"); - fprintf(file, "Columns\n"); + highsFprintfString(file, log_options, "Columns\n"); for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) { if (have_value) fprintf(file, "%.15g ", use_col_value[iCol]); if (have_dual) fprintf(file, "%.15g ", use_col_dual[iCol]); @@ -1111,7 +1145,7 @@ void writeOldRawSolution(FILE* file, const HighsLp& lp, const HighsBasis& basis, fprintf(file, "%" HIGHSINT_FORMAT "", (HighsInt)use_col_status[iCol]); fprintf(file, "\n"); } - fprintf(file, "Rows\n"); + highsFprintfString(file, log_options, "Rows\n"); for (HighsInt iRow = 0; iRow < lp.num_row_; iRow++) { if (have_value) fprintf(file, "%.15g ", use_row_value[iRow]); if (have_dual) fprintf(file, "%.15g ", use_row_dual[iRow]); diff --git a/src/lp_data/HighsModelUtils.h b/src/lp_data/HighsModelUtils.h index bd8d5e4b75..ac369ab5cf 100644 --- a/src/lp_data/HighsModelUtils.h +++ b/src/lp_data/HighsModelUtils.h @@ -31,7 +31,8 @@ bool hasNamesWithSpaces(const HighsLogOptions& log_options, const HighsInt num_name, const std::vector& names); void writeModelBoundSolution( - FILE* file, const bool columns, const HighsInt dim, + FILE* file, const HighsLogOptions& log_options, + const bool columns, const HighsInt dim, const std::vector& lower, const std::vector& upper, const std::vector& names, const bool have_primal, const std::vector& primal, const bool have_dual, @@ -39,19 +40,24 @@ void writeModelBoundSolution( const std::vector& status, const HighsVarType* integrality = NULL); -void writeModelObjective(FILE* file, const HighsModel& model, +void writeModelObjective(FILE* file, const HighsLogOptions& log_options, + const HighsModel& model, const std::vector& primal_solution); -void writeLpObjective(FILE* file, const HighsLp& lp, +void writeLpObjective(FILE* file, const HighsLogOptions& log_options, + const HighsLp& lp, const std::vector& primal_solution); -void writeObjectiveValue(FILE* file, const double objective_value); +void writeObjectiveValue(FILE* file, const HighsLogOptions& log_options, + const double objective_value); -void writePrimalSolution(FILE* file, const HighsLp& lp, +void writePrimalSolution(FILE* file, const HighsLogOptions& log_options, + const HighsLp& lp, const std::vector& primal_solution, const bool sparse = false); -void writeModelSolution(FILE* file, const HighsModel& model, +void writeModelSolution(FILE* file, const HighsLogOptions& log_options, + const HighsModel& model, const HighsSolution& solution, const HighsInfo& info, const bool sparse = false); @@ -78,7 +84,8 @@ void writeGlpsolSolution(FILE* file, const HighsOptions& options, const HighsModelStatus model_status, const HighsInfo& info, const bool raw); -void writeOldRawSolution(FILE* file, const HighsLp& lp, const HighsBasis& basis, +void writeOldRawSolution(FILE* file, const HighsLogOptions& log_options, + const HighsLp& lp, const HighsBasis& basis, const HighsSolution& solution); HighsBasisStatus checkedVarHighsNonbasicStatus( diff --git a/src/mip/HighsMipSolverData.cpp b/src/mip/HighsMipSolverData.cpp index 1b0610a340..eafb952cf7 100644 --- a/src/mip/HighsMipSolverData.cpp +++ b/src/mip/HighsMipSolverData.cpp @@ -1941,10 +1941,11 @@ void HighsMipSolverData::saveReportMipSolution(const double new_upper_limit) { } FILE* file = mipsolver.improving_solution_file_; if (file) { - writeLpObjective(file, *(mipsolver.orig_model_), mipsolver.solution_); - writePrimalSolution( - file, *(mipsolver.orig_model_), mipsolver.solution_, - mipsolver.options_mip_->mip_improving_solution_report_sparse); + writeLpObjective(file, mipsolver.options_mip_->log_options, + *(mipsolver.orig_model_), mipsolver.solution_); + writePrimalSolution(file, mipsolver.options_mip_->log_options, + *(mipsolver.orig_model_), mipsolver.solution_, + mipsolver.options_mip_->mip_improving_solution_report_sparse); } } From 0c6b2539336ef353717099bfc4359fa716728016 Mon Sep 17 00:00:00 2001 From: JAJHall Date: Wed, 10 Jul 2024 14:02:20 +0100 Subject: [PATCH 07/38] Replaced fprintf(file in HighsModelUtils.cpp by stringstreams and calls to highsFprintfString(file, log_options --- check/TestCallbacks.cpp | 10 +- src/io/HighsIO.cpp | 6 +- src/io/HighsIO.h | 7 +- src/lp_data/HighsModelUtils.cpp | 500 +++++++++++++++++++------------- src/lp_data/HighsModelUtils.h | 31 +- src/mip/HighsMipSolverData.cpp | 9 +- 6 files changed, 323 insertions(+), 240 deletions(-) diff --git a/check/TestCallbacks.cpp b/check/TestCallbacks.cpp index 06d9cc7e94..62e9dc105f 100644 --- a/check/TestCallbacks.cpp +++ b/check/TestCallbacks.cpp @@ -121,10 +121,10 @@ HighsCallbackFunctionType userInterruptCallback = REQUIRE(local_callback_data == kUserCallbackNoData); } if (callback_type == kCallbackLogging) { - if (dev_run) - printf("Callback: %s", message.c_str()); -// printf("userInterruptCallback(type %2d; data %2d): %s", -// callback_type, local_callback_data, message.c_str()); + if (dev_run) printf("Callback: %s", message.c_str()); + // printf("userInterruptCallback(type %2d; data %2d): %s", + // callback_type, local_callback_data, + // message.c_str()); } else if (callback_type == kCallbackSimplexInterrupt) { if (dev_run) printf( @@ -267,7 +267,7 @@ TEST_CASE("highs-callback-logging", "[highs-callback]") { } TEST_CASE("highs-callback-solution-basis-logging", "[highs-callback]") { - std::string filename = std::string(HIGHS_DIR) + "/check/instances/avgas.mps"; + std::string filename = std::string(HIGHS_DIR) + "/check/instances/avgas.mps"; int user_callback_data = kUserCallbackData; void* p_user_callback_data = reinterpret_cast(static_cast(user_callback_data)); diff --git a/src/io/HighsIO.cpp b/src/io/HighsIO.cpp index fc622a5c20..8d7911e02f 100644 --- a/src/io/HighsIO.cpp +++ b/src/io/HighsIO.cpp @@ -219,14 +219,14 @@ void highsLogDev(const HighsLogOptions& log_options_, const HighsLogType type, va_end(argptr); } -void highsFprintfString(FILE* file, const HighsLogOptions& log_options_, - const std::string& s) { +void highsFprintfString(FILE* file, const HighsLogOptions& log_options_, + const std::string& s) { if (file == nullptr) return; if (file == stdout) { highsLogUser(log_options_, HighsLogType::kInfo, "%s", s.c_str()); } else { fprintf(file, "%s", s.c_str()); - } + } } void highsReportDevInfo(const HighsLogOptions* log_options, diff --git a/src/io/HighsIO.h b/src/io/HighsIO.h index 6da477a5d5..b63d47141f 100644 --- a/src/io/HighsIO.h +++ b/src/io/HighsIO.h @@ -89,11 +89,12 @@ void highsLogDev(const HighsLogOptions& log_options_, const HighsLogType type, const char* format, ...); /** - * @brief Replaces fprintf(file,... so that when file=stdout highsLogUser is used + * @brief Replaces fprintf(file,... so that when file=stdout highsLogUser is + * used */ // Printing format: must contain exactly one "\n" at end of format -void highsFprintfString(FILE* file, const HighsLogOptions& log_options_, - const std::string& s); +void highsFprintfString(FILE* file, const HighsLogOptions& log_options_, + const std::string& s); /** * @brief For development logging when true log_options may not be available - diff --git a/src/lp_data/HighsModelUtils.cpp b/src/lp_data/HighsModelUtils.cpp index 201c090581..0e4db30085 100644 --- a/src/lp_data/HighsModelUtils.cpp +++ b/src/lp_data/HighsModelUtils.cpp @@ -133,13 +133,12 @@ std::string typeToString(const HighsVarType type) { } void writeModelBoundSolution( - FILE* file, const HighsLogOptions& log_options, - const bool columns, const HighsInt dim, - const std::vector& lower, const std::vector& upper, - const std::vector& names, const bool have_primal, - const std::vector& primal, const bool have_dual, - const std::vector& dual, const bool have_basis, - const std::vector& status, + FILE* file, const HighsLogOptions& log_options, const bool columns, + const HighsInt dim, const std::vector& lower, + const std::vector& upper, const std::vector& names, + const bool have_primal, const std::vector& primal, + const bool have_dual, const std::vector& dual, + const bool have_basis, const std::vector& status, const HighsVarType* integrality) { const bool have_names = names.size() > 0; if (have_names) assert((int)names.size() >= dim); @@ -161,10 +160,10 @@ void writeModelBoundSolution( highsFprintfString(file, log_options, ss.str()); for (HighsInt ix = 0; ix < dim; ix++) { ss.str(std::string()); - std::string var_status_string = have_basis ? - statusToString(status[ix], lower[ix], upper[ix]) : ""; + std::string var_status_string = + have_basis ? statusToString(status[ix], lower[ix], upper[ix]) : ""; ss << highsFormatToString("%9" HIGHSINT_FORMAT " %4s %12g %12g", ix, - var_status_string.c_str(), lower[ix], upper[ix]); + var_status_string.c_str(), lower[ix], upper[ix]); if (have_primal) { ss << highsFormatToString(" %12g", primal[ix]); } else { @@ -187,7 +186,7 @@ void writeModelBoundSolution( } void writeModelObjective(FILE* file, const HighsLogOptions& log_options, - const HighsModel& model, + const HighsModel& model, const std::vector& primal_solution) { HighsCDouble objective_value = model.lp_.objectiveCDoubleValue(primal_solution); @@ -196,14 +195,14 @@ void writeModelObjective(FILE* file, const HighsLogOptions& log_options, } void writeLpObjective(FILE* file, const HighsLogOptions& log_options, - const HighsLp& lp, + const HighsLp& lp, const std::vector& primal_solution) { HighsCDouble objective_value = lp.objectiveCDoubleValue(primal_solution); writeObjectiveValue(file, log_options, (double)objective_value); } void writeObjectiveValue(FILE* file, const HighsLogOptions& log_options, - const double objective_value) { + const double objective_value) { std::array objStr = highsDoubleToString( objective_value, kHighsSolutionValueToStringTolerance); std::string s = highsFormatToString("Objective %s\n", objStr.data()); @@ -211,7 +210,7 @@ void writeObjectiveValue(FILE* file, const HighsLogOptions& log_options, } void writePrimalSolution(FILE* file, const HighsLogOptions& log_options, - const HighsLp& lp, + const HighsLp& lp, const std::vector& primal_solution, const bool sparse) { HighsInt num_nonzero_primal_value = 0; @@ -224,11 +223,11 @@ void writePrimalSolution(FILE* file, const HighsLogOptions& log_options, // Indicate the number of column values to be written out, depending // on whether format is sparse: either lp.num_col_ if not sparse, or // the negation of the number of nonzero values, if sparse - + std::stringstream ss; ss.str(std::string()); ss << highsFormatToString("# Columns %" HIGHSINT_FORMAT "\n", - sparse ? -num_nonzero_primal_value : lp.num_col_); + sparse ? -num_nonzero_primal_value : lp.num_col_); highsFprintfString(file, log_options, ss.str()); for (HighsInt ix = 0; ix < lp.num_col_; ix++) { if (sparse && !primal_solution[ix]) continue; @@ -247,9 +246,8 @@ void writePrimalSolution(FILE* file, const HighsLogOptions& log_options, } void writeModelSolution(FILE* file, const HighsLogOptions& log_options, - const HighsModel& model, - const HighsSolution& solution, const HighsInfo& info, - const bool sparse) { + const HighsModel& model, const HighsSolution& solution, + const HighsInfo& info, const bool sparse) { const HighsLp& lp = model.lp_; const bool have_col_names = lp.col_names_.size() > 0; const bool have_row_names = lp.row_names_.size() > 0; @@ -279,10 +277,11 @@ void writeModelSolution(FILE* file, const HighsLogOptions& log_options, highsFprintfString(file, log_options, "Infeasible\n"); } writeModelObjective(file, log_options, model, solution.col_value); - writePrimalSolution(file, log_options, model.lp_, solution.col_value, sparse); + writePrimalSolution(file, log_options, model.lp_, solution.col_value, + sparse); if (sparse) return; ss.str(std::string()); - ss << highsFormatToString("# Rows %" HIGHSINT_FORMAT "\n", lp.num_row_); + ss << highsFormatToString("# Rows %" HIGHSINT_FORMAT "\n", lp.num_row_); highsFprintfString(file, log_options, ss.str()); for (HighsInt ix = 0; ix < lp.num_row_; ix++) { std::array valStr = highsDoubleToString( @@ -307,7 +306,7 @@ void writeModelSolution(FILE* file, const HighsLogOptions& log_options, highsFprintfString(file, log_options, "Infeasible\n"); } ss.str(std::string()); - ss << highsFormatToString("# Columns %" HIGHSINT_FORMAT "\n", lp.num_col_); + ss << highsFormatToString("# Columns %" HIGHSINT_FORMAT "\n", lp.num_col_); highsFprintfString(file, log_options, ss.str()); for (HighsInt ix = 0; ix < lp.num_col_; ix++) { std::array valStr = highsDoubleToString( @@ -320,7 +319,7 @@ void writeModelSolution(FILE* file, const HighsLogOptions& log_options, highsFprintfString(file, log_options, ss.str()); } ss.str(std::string()); - ss << highsFormatToString("# Rows %" HIGHSINT_FORMAT "\n", lp.num_row_); + ss << highsFormatToString("# Rows %" HIGHSINT_FORMAT "\n", lp.num_row_); highsFprintfString(file, log_options, ss.str()); for (HighsInt ix = 0; ix < lp.num_row_; ix++) { std::array valStr = highsDoubleToString( @@ -426,22 +425,22 @@ void writeSolutionFile(FILE* file, const HighsOptions& options, lp.col_upper_, lp.col_names_, have_primal, solution.col_value, have_dual, solution.col_dual, have_basis, basis.col_status, integrality); - writeModelBoundSolution(file, log_options, false, lp.num_row_, lp.row_lower_, - lp.row_upper_, lp.row_names_, have_primal, - solution.row_value, have_dual, solution.row_dual, - have_basis, basis.row_status); + writeModelBoundSolution(file, log_options, false, lp.num_row_, + lp.row_lower_, lp.row_upper_, lp.row_names_, + have_primal, solution.row_value, have_dual, + solution.row_dual, have_basis, basis.row_status); highsFprintfString(file, log_options, "\n"); std::stringstream ss; ss.str(std::string()); - ss << highsFormatToString("Model status: %s\n", - utilModelStatusToString(model_status).c_str()); + ss << highsFormatToString("Model status: %s\n", + utilModelStatusToString(model_status).c_str()); highsFprintfString(file, log_options, ss.str()); std::array objStr = highsDoubleToString((double)info.objective_function_value, kHighsSolutionValueToStringTolerance); highsFprintfString(file, log_options, "\n"); ss.str(std::string()); - ss << highsFormatToString("Objective value: %s\n", objStr.data()); + ss << highsFormatToString("Objective value: %s\n", objStr.data()); highsFprintfString(file, log_options, ss.str()); } else if (style == kSolutionStyleGlpsolRaw || style == kSolutionStyleGlpsolPretty) { @@ -455,36 +454,42 @@ void writeSolutionFile(FILE* file, const HighsOptions& options, highsFprintfString(file, log_options, "Model status\n"); std::stringstream ss; ss.str(std::string()); - ss << highsFormatToString("%s\n", utilModelStatusToString(model_status).c_str()); - highsFprintfString(file, log_options, ss.str()); + ss << highsFormatToString("%s\n", + utilModelStatusToString(model_status).c_str()); + highsFprintfString(file, log_options, ss.str()); writeModelSolution(file, log_options, model, solution, info, sparse); } } -void writeGlpsolCostRow(FILE* file, const bool raw, const bool is_mip, +void writeGlpsolCostRow(FILE* file, const HighsLogOptions& log_options, + const bool raw, const bool is_mip, const HighsInt row_id, const std::string objective_name, const double objective_function_value) { + std::stringstream ss; + ss.str(std::string()); if (raw) { double double_value = objective_function_value; std::array double_string = highsDoubleToString( double_value, kGlpsolSolutionValueToStringTolerance); // Last term of 0 for dual should (also) be blank when not MIP - fprintf(file, "i %d %s%s%s\n", (int)row_id, is_mip ? "" : "b ", - double_string.data(), is_mip ? "" : " 0"); + ss << highsFormatToString("i %d %s%s%s\n", (int)row_id, is_mip ? "" : "b ", + double_string.data(), is_mip ? "" : " 0"); } else { - fprintf(file, "%6d ", (int)row_id); + ss << highsFormatToString("%6d ", (int)row_id); if (objective_name.length() <= 12) { - fprintf(file, "%-12s ", objective_name.c_str()); + ss << highsFormatToString("%-12s ", objective_name.c_str()); } else { - fprintf(file, "%s\n%20s", objective_name.c_str(), ""); + ss << highsFormatToString("%s\n%20s", objective_name.c_str(), ""); } if (is_mip) { - fprintf(file, " "); + ss << highsFormatToString(" "); } else { - fprintf(file, "B "); + ss << highsFormatToString("B "); } - fprintf(file, "%13.6g %13s %13s \n", objective_function_value, "", ""); + ss << highsFormatToString("%13.6g %13s %13s \n", objective_function_value, + "", ""); } + highsFprintfString(file, log_options, ss.str()); } void writeGlpsolSolution(FILE* file, const HighsOptions& options, @@ -500,6 +505,7 @@ void writeGlpsolSolution(FILE* file, const HighsOptions& options, const double kGlpsolLowQuality = 1e-3; const double kGlpsolPrintAsZero = 1e-9; const HighsLp& lp = model.lp_; + const HighsLogOptions& log_options = options.log_options; const bool have_col_names = (lp.col_names_.size() != 0); const bool have_row_names = (lp.row_names_.size() != 0); // Determine number of nonzeros including the objective function @@ -626,16 +632,24 @@ void writeGlpsolSolution(FILE* file, const HighsOptions& options, // prefix to raw lines std::string line_prefix = ""; if (raw) line_prefix = "c "; - fprintf(file, "%s%-12s%s\n", line_prefix.c_str(), - "Problem:", lp.model_name_.c_str()); - fprintf(file, "%s%-12s%d\n", line_prefix.c_str(), - "Rows:", (int)glpsol_num_row); - fprintf(file, "%s%-12s%d", line_prefix.c_str(), "Columns:", (int)num_col); + highsFprintfString(file, log_options, + highsFormatToString("%s%-12s%s\n", line_prefix.c_str(), + "Problem:", lp.model_name_.c_str())); + highsFprintfString(file, log_options, + highsFormatToString("%s%-12s%d\n", line_prefix.c_str(), + "Rows:", (int)glpsol_num_row)); + std::stringstream ss; + ss.str(std::string()); + ss << highsFormatToString("%s%-12s%d", line_prefix.c_str(), + "Columns:", (int)num_col); if (!raw && is_mip) - fprintf(file, " (%d integer, %d binary)", (int)num_integer, - (int)num_binary); - fprintf(file, "\n"); - fprintf(file, "%s%-12s%d\n", line_prefix.c_str(), "Non-zeros:", (int)num_nz); + ss << highsFormatToString(" (%d integer, %d binary)", (int)num_integer, + (int)num_binary); + ss << highsFormatToString("\n"); + highsFprintfString(file, log_options, ss.str()); + highsFprintfString(file, log_options, + highsFormatToString("%s%-12s%d\n", line_prefix.c_str(), + "Non-zeros:", (int)num_nz)); // Use model_status to define the GLPK model_status_text and // solution_status_char, where the former is used to specify the // model status. GLPK uses a single character to specify the @@ -682,8 +696,9 @@ void writeGlpsolSolution(FILE* file, const HighsOptions& options, } assert(model_status_text != "???"); if (is_mip) assert(solution_status_char != "?"); - fprintf(file, "%s%-12s%s\n", line_prefix.c_str(), - "Status:", model_status_text.c_str()); + highsFprintfString(file, log_options, + highsFormatToString("%s%-12s%s\n", line_prefix.c_str(), + "Status:", model_status_text.c_str())); // If info is not valid, then cannot write more if (!info.valid) return; // Now write out the numerical information @@ -697,92 +712,106 @@ void writeGlpsolSolution(FILE* file, const HighsOptions& options, // non-trivial objective name if (have_row_names) assert(lp.objective_name_ != ""); const bool has_objective_name = lp.objective_name_ != ""; - fprintf(file, "%s%-12s%s%.10g (%s)\n", line_prefix.c_str(), "Objective:", + highsFprintfString( + file, log_options, + highsFormatToString( + "%s%-12s%s%.10g (%s)\n", line_prefix.c_str(), "Objective:", !(has_objective && has_objective_name) ? "" : (objective_name + " = ").c_str(), has_objective ? info.objective_function_value : 0, - lp.sense_ == ObjSense::kMinimize ? "MINimum" : "MAXimum"); + lp.sense_ == ObjSense::kMinimize ? "MINimum" : "MAXimum")); // No space after "c" on blank line! if (raw) line_prefix = "c"; - fprintf(file, "%s\n", line_prefix.c_str()); + highsFprintfString(file, log_options, + highsFormatToString("%s\n", line_prefix.c_str())); // Detailed lines are rather different if (raw) { - fprintf(file, "s %s %d %d ", is_mip ? "mip" : "bas", (int)glpsol_num_row, - (int)num_col); + ss.str(std::string()); + ss << highsFormatToString("s %s %d %d ", is_mip ? "mip" : "bas", + (int)glpsol_num_row, (int)num_col); if (is_mip) { - fprintf(file, "%s", solution_status_char.c_str()); + ss << highsFormatToString("%s", solution_status_char.c_str()); } else { if (info.primal_solution_status == kSolutionStatusNone) { - fprintf(file, "u"); + ss << highsFormatToString("u"); } else if (info.primal_solution_status == kSolutionStatusInfeasible) { - fprintf(file, "i"); + ss << highsFormatToString("i"); } else if (info.primal_solution_status == kSolutionStatusFeasible) { - fprintf(file, "f"); + ss << highsFormatToString("f"); } else { - fprintf(file, "?"); + ss << highsFormatToString("?"); } - fprintf(file, " "); + ss << highsFormatToString(" "); if (info.dual_solution_status == kSolutionStatusNone) { - fprintf(file, "u"); + ss << highsFormatToString("u"); } else if (info.dual_solution_status == kSolutionStatusInfeasible) { - fprintf(file, "i"); + ss << highsFormatToString("i"); } else if (info.dual_solution_status == kSolutionStatusFeasible) { - fprintf(file, "f"); + ss << highsFormatToString("f"); } else { - fprintf(file, "?"); + ss << highsFormatToString("?"); } } double double_value = has_objective ? info.objective_function_value : 0; std::array double_string = highsDoubleToString(double_value, kHighsSolutionValueToStringTolerance); - fprintf(file, " %s\n", double_string.data()); + ss << highsFormatToString(" %s\n", double_string.data()); + highsFprintfString(file, log_options, ss.str()); } // GLPK puts out i 1 b 0 0 etc if there's no primal point, but // that's meaningless at best, so HiGHS returns in that case if (!have_value) return; if (!raw) { - fprintf(file, - " No. Row name %s Activity Lower bound " - " Upper bound", - have_basis ? "St" : " "); - if (have_dual) fprintf(file, " Marginal"); - fprintf(file, "\n"); - - fprintf(file, - "------ ------------ %s ------------- ------------- " - "-------------", - have_basis ? "--" : " "); - if (have_dual) fprintf(file, " -------------"); - fprintf(file, "\n"); + ss.str(std::string()); + ss << highsFormatToString( + " No. Row name %s Activity Lower bound " + " Upper bound", + have_basis ? "St" : " "); + if (have_dual) ss << highsFormatToString(" Marginal"); + ss << highsFormatToString("\n"); + highsFprintfString(file, log_options, ss.str()); + ss.str(std::string()); + ss << highsFormatToString( + "------ ------------ %s ------------- ------------- " + "-------------", + have_basis ? "--" : " "); + if (have_dual) ss << highsFormatToString(" -------------"); + ss << highsFormatToString("\n"); + highsFprintfString(file, log_options, ss.str()); } HighsInt row_id = 0; for (HighsInt iRow = 0; iRow < lp.num_row_; iRow++) { row_id++; if (row_id == cost_row_location) { - writeGlpsolCostRow(file, raw, is_mip, row_id, objective_name, + writeGlpsolCostRow(file, log_options, raw, is_mip, row_id, objective_name, info.objective_function_value); row_id++; } + ss.str(std::string()); if (raw) { - fprintf(file, "i %d ", (int)row_id); + ss << highsFormatToString("i %d ", (int)row_id); if (is_mip) { // Complete the line if for a MIP double double_value = have_value ? solution.row_value[iRow] : 0; std::array double_string = highsDoubleToString( double_value, kHighsSolutionValueToStringTolerance); - fprintf(file, "%s\n", double_string.data()); + ss << highsFormatToString("%s\n", double_string.data()); + highsFprintfString(file, log_options, ss.str()); continue; } } else { - fprintf(file, "%6d ", (int)row_id); + ss << highsFormatToString("%6d ", (int)row_id); std::string row_name = ""; if (have_row_names) row_name = lp.row_names_[iRow]; if (row_name.length() <= 12) { - fprintf(file, "%-12s ", row_name.c_str()); + ss << highsFormatToString("%-12s ", row_name.c_str()); } else { - fprintf(file, "%s\n%20s", row_name.c_str(), ""); + ss << highsFormatToString("%s\n", row_name.c_str()); + highsFprintfString(file, log_options, ss.str()); + ss.str(std::string()); + ss << " "; } } const double lower = lp.row_lower_[iRow]; @@ -816,29 +845,30 @@ void writeGlpsolSolution(FILE* file, const HighsOptions& options, } } if (raw) { - fprintf(file, "%s ", status_char.c_str()); + ss << highsFormatToString("%s ", status_char.c_str()); double double_value = have_value ? solution.row_value[iRow] : 0; std::array double_string = highsDoubleToString( double_value, kHighsSolutionValueToStringTolerance); - fprintf(file, "%s ", double_string.data()); + ss << highsFormatToString("%s ", double_string.data()); } else { - fprintf(file, "%s ", status_text.c_str()); - fprintf(file, "%13.6g ", fabs(value) <= kGlpsolPrintAsZero ? 0.0 : value); + ss << highsFormatToString("%s ", status_text.c_str()); + ss << highsFormatToString( + "%13.6g ", fabs(value) <= kGlpsolPrintAsZero ? 0.0 : value); if (lower > -kHighsInf) - fprintf(file, "%13.6g ", lower); + ss << highsFormatToString("%13.6g ", lower); else - fprintf(file, "%13s ", ""); + ss << highsFormatToString("%13s ", ""); if (lower != upper && upper < kHighsInf) - fprintf(file, "%13.6g ", upper); + ss << highsFormatToString("%13.6g ", upper); else - fprintf(file, "%13s ", lower == upper ? "=" : ""); + ss << highsFormatToString("%13s ", lower == upper ? "=" : ""); } if (have_dual) { if (raw) { double double_value = solution.row_dual[iRow]; std::array double_string = highsDoubleToString( double_value, kHighsSolutionValueToStringTolerance); - fprintf(file, "%s", double_string.data()); + ss << highsFormatToString("%s", double_string.data()); } else { // If the row is known to be basic, don't print the dual // value. If there's no basis, row cannot be known to be basic @@ -847,56 +877,67 @@ void writeGlpsolSolution(FILE* file, const HighsOptions& options, not_basic = basis.row_status[iRow] != HighsBasisStatus::kBasic; if (not_basic) { if (fabs(dual) <= kGlpsolPrintAsZero) - fprintf(file, "%13s", "< eps"); + ss << highsFormatToString("%13s", "< eps"); else - fprintf(file, "%13.6g ", dual); + ss << highsFormatToString("%13.6g ", dual); } } } - fprintf(file, "\n"); + ss << highsFormatToString("\n"); + highsFprintfString(file, log_options, ss.str()); } if (cost_row_location == lp.num_row_ + 1) { row_id++; - writeGlpsolCostRow(file, raw, is_mip, row_id, objective_name, + writeGlpsolCostRow(file, log_options, raw, is_mip, row_id, objective_name, info.objective_function_value); } - if (!raw) fprintf(file, "\n"); + if (!raw) highsFprintfString(file, log_options, "\n"); if (!raw) { - fprintf(file, - " No. Column name %s Activity Lower bound " - " Upper bound", - have_basis ? "St" : " "); - if (have_dual) fprintf(file, " Marginal"); - fprintf(file, "\n"); - fprintf(file, - "------ ------------ %s ------------- ------------- " - "-------------", - have_basis ? "--" : " "); - if (have_dual) fprintf(file, " -------------"); - fprintf(file, "\n"); + ss.str(std::string()); + ss << highsFormatToString( + " No. Column name %s Activity Lower bound " + " Upper bound", + have_basis ? "St" : " "); + if (have_dual) ss << highsFormatToString(" Marginal"); + ss << highsFormatToString("\n"); + highsFprintfString(file, log_options, ss.str()); + ss.str(std::string()); + ss << highsFormatToString( + "------ ------------ %s ------------- ------------- " + "-------------", + have_basis ? "--" : " "); + if (have_dual) ss << highsFormatToString(" -------------"); + ss << highsFormatToString("\n"); + highsFprintfString(file, log_options, ss.str()); } if (raw) line_prefix = "j "; for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) { + ss.str(std::string()); if (raw) { - fprintf(file, "%s%d ", line_prefix.c_str(), (int)(iCol + 1)); + ss << highsFormatToString("%s%d ", line_prefix.c_str(), (int)(iCol + 1)); if (is_mip) { double double_value = have_value ? solution.col_value[iCol] : 0; std::array double_string = highsDoubleToString( double_value, kHighsSolutionValueToStringTolerance); - fprintf(file, "%s\n", double_string.data()); + ss << highsFormatToString("%s\n", double_string.data()); + highsFprintfString(file, log_options, ss.str()); continue; } } else { - fprintf(file, "%6d ", (int)(iCol + 1)); + ss << highsFormatToString("%6d ", (int)(iCol + 1)); std::string col_name = ""; if (have_col_names) col_name = lp.col_names_[iCol]; if (!have_col_names || col_name.length() <= 12) { - fprintf(file, "%-12s ", !have_col_names ? "" : col_name.c_str()); + ss << highsFormatToString("%-12s ", + !have_col_names ? "" : col_name.c_str()); } else { - fprintf(file, "%s\n%20s", col_name.c_str(), ""); + ss << highsFormatToString("%s\n", col_name.c_str()); + highsFprintfString(file, log_options, ss.str()); + ss.str(std::string()); + ss << " "; } } const double lower = lp.col_lower_[iCol]; @@ -933,29 +974,30 @@ void writeGlpsolSolution(FILE* file, const HighsOptions& options, status_text = "* "; } if (raw) { - fprintf(file, "%s ", status_char.c_str()); + ss << highsFormatToString("%s ", status_char.c_str()); double double_value = have_value ? solution.col_value[iCol] : 0; std::array double_string = highsDoubleToString( double_value, kHighsSolutionValueToStringTolerance); - fprintf(file, "%s ", double_string.data()); + ss << highsFormatToString("%s ", double_string.data()); } else { - fprintf(file, "%s ", status_text.c_str()); - fprintf(file, "%13.6g ", fabs(value) <= kGlpsolPrintAsZero ? 0.0 : value); + ss << highsFormatToString("%s ", status_text.c_str()); + ss << highsFormatToString( + "%13.6g ", fabs(value) <= kGlpsolPrintAsZero ? 0.0 : value); if (lower > -kHighsInf) - fprintf(file, "%13.6g ", lower); + ss << highsFormatToString("%13.6g ", lower); else - fprintf(file, "%13s ", ""); + ss << highsFormatToString("%13s ", ""); if (lower != upper && upper < kHighsInf) - fprintf(file, "%13.6g ", upper); + ss << highsFormatToString("%13.6g ", upper); else - fprintf(file, "%13s ", lower == upper ? "=" : ""); + ss << highsFormatToString("%13s ", lower == upper ? "=" : ""); } if (have_dual) { if (raw) { double double_value = solution.col_dual[iCol]; std::array double_string = highsDoubleToString( double_value, kHighsSolutionValueToStringTolerance); - fprintf(file, "%s", double_string.data()); + ss << highsFormatToString("%s", double_string.data()); } else { // If the column is known to be basic, don't print the dual // value. If there's no basis, column cannot be known to be @@ -965,16 +1007,17 @@ void writeGlpsolSolution(FILE* file, const HighsOptions& options, not_basic = basis.col_status[iCol] != HighsBasisStatus::kBasic; if (not_basic) { if (fabs(dual) <= kGlpsolPrintAsZero) - fprintf(file, "%13s", "< eps"); + ss << highsFormatToString("%13s", "< eps"); else - fprintf(file, "%13.6g ", dual); + ss << highsFormatToString("%13.6g ", dual); } } } - fprintf(file, "\n"); + ss << highsFormatToString("\n"); + highsFprintfString(file, log_options, ss.str()); } if (raw) { - fprintf(file, "e o f\n"); + highsFprintfString(file, log_options, "e o f\n"); return; } HighsPrimalDualErrors errors; @@ -983,13 +1026,13 @@ void writeGlpsolSolution(FILE* file, const HighsOptions& options, double absolute_error_value; HighsInt relative_error_index; double relative_error_value; - const HighsLogOptions& log_options = options.log_options; getKktFailures(options, model, solution, basis, local_info, errors, true); highsFprintfString(file, log_options, "\n"); if (is_mip) { highsFprintfString(file, log_options, "Integer feasibility conditions:\n"); } else { - highsFprintfString(file, log_options, "Karush-Kuhn-Tucker optimality conditions:\n"); + highsFprintfString(file, log_options, + "Karush-Kuhn-Tucker optimality conditions:\n"); } highsFprintfString(file, log_options, "\n"); // Primal residual @@ -999,17 +1042,25 @@ void writeGlpsolSolution(FILE* file, const HighsOptions& options, relative_error_index = errors.max_primal_residual.relative_index + 1; if (!absolute_error_value) absolute_error_index = 0; if (!relative_error_value) relative_error_index = 0; - fprintf(file, "KKT.PE: max.abs.err = %.2e on row %d\n", absolute_error_value, - absolute_error_index == 0 ? 0 : (int)absolute_error_index); - fprintf(file, " max.rel.err = %.2e on row %d\n", relative_error_value, - absolute_error_index == 0 ? 0 : (int)relative_error_index); - fprintf(file, "%8s%s\n", "", - relative_error_value <= kGlpsolHighQuality ? "High quality" - : relative_error_value <= kGlpsolMediumQuality ? "Medium quality" - : relative_error_value <= kGlpsolLowQuality - ? "Low quality" - : "PRIMAL SOLUTION IS WRONG"); - highsFprintfString(file, log_options, "\n"); + ss.str(std::string()); + ss << highsFormatToString( + "KKT.PE: max.abs.err = %.2e on row %d\n", absolute_error_value, + absolute_error_index == 0 ? 0 : (int)absolute_error_index); + highsFprintfString(file, log_options, ss.str()); + ss.str(std::string()); + ss << highsFormatToString( + " max.rel.err = %.2e on row %d\n", relative_error_value, + absolute_error_index == 0 ? 0 : (int)relative_error_index); + highsFprintfString(file, log_options, ss.str()); + ss.str(std::string()); + ss << highsFormatToString( + "%8s%s\n", "", + relative_error_value <= kGlpsolHighQuality ? "High quality" + : relative_error_value <= kGlpsolMediumQuality ? "Medium quality" + : relative_error_value <= kGlpsolLowQuality ? "Low quality" + : "PRIMAL SOLUTION IS WRONG"); + ss << "\n"; + highsFprintfString(file, log_options, ss.str()); // Primal infeasibility absolute_error_value = errors.max_primal_infeasibility.absolute_value; @@ -1019,24 +1070,31 @@ void writeGlpsolSolution(FILE* file, const HighsOptions& options, if (!absolute_error_value) absolute_error_index = 0; if (!relative_error_value) relative_error_index = 0; bool on_col = absolute_error_index > 0 && absolute_error_index <= lp.num_col_; - fprintf(file, "KKT.PB: max.abs.err = %.2e on %s %d\n", absolute_error_value, - on_col ? "column" : "row", - absolute_error_index <= lp.num_col_ - ? (int)absolute_error_index - : (int)(absolute_error_index - lp.num_col_)); + ss.str(std::string()); + ss << highsFormatToString("KKT.PB: max.abs.err = %.2e on %s %d\n", + absolute_error_value, on_col ? "column" : "row", + absolute_error_index <= lp.num_col_ + ? (int)absolute_error_index + : (int)(absolute_error_index - lp.num_col_)); + highsFprintfString(file, log_options, ss.str()); on_col = relative_error_index > 0 && relative_error_index <= lp.num_col_; - fprintf(file, " max.rel.err = %.2e on %s %d\n", relative_error_value, - on_col ? "column" : "row", - relative_error_index <= lp.num_col_ - ? (int)relative_error_index - : (int)(relative_error_index - lp.num_col_)); - fprintf(file, "%8s%s\n", "", - relative_error_value <= kGlpsolHighQuality ? "High quality" - : relative_error_value <= kGlpsolMediumQuality ? "Medium quality" - : relative_error_value <= kGlpsolLowQuality - ? "Low quality" - : "PRIMAL SOLUTION IS INFEASIBLE"); - highsFprintfString(file, log_options, "\n"); + ss.str(std::string()); + ss << highsFormatToString(" max.rel.err = %.2e on %s %d\n", + relative_error_value, on_col ? "column" : "row", + relative_error_index <= lp.num_col_ + ? (int)relative_error_index + : (int)(relative_error_index - lp.num_col_)); + highsFprintfString(file, log_options, ss.str()); + ss.str(std::string()); + ss << highsFormatToString( + "%8s%s\n", "", + relative_error_value <= kGlpsolHighQuality ? "High quality" + : relative_error_value <= kGlpsolMediumQuality ? "Medium quality" + : relative_error_value <= kGlpsolLowQuality + ? "Low quality" + : "PRIMAL SOLUTION IS INFEASIBLE"); + ss << "\n"; + highsFprintfString(file, log_options, ss.str()); if (have_dual) { // Dual residual @@ -1046,17 +1104,19 @@ void writeGlpsolSolution(FILE* file, const HighsOptions& options, relative_error_index = errors.max_dual_residual.relative_index + 1; if (!absolute_error_value) absolute_error_index = 0; if (!relative_error_value) relative_error_index = 0; - fprintf(file, "KKT.DE: max.abs.err = %.2e on column %d\n", - absolute_error_value, (int)absolute_error_index); - fprintf(file, " max.rel.err = %.2e on column %d\n", - relative_error_value, (int)relative_error_index); - fprintf(file, "%8s%s\n", "", - relative_error_value <= kGlpsolHighQuality ? "High quality" - : relative_error_value <= kGlpsolMediumQuality ? "Medium quality" - : relative_error_value <= kGlpsolLowQuality - ? "Low quality" - : "DUAL SOLUTION IS WRONG"); - highsFprintfString(file, log_options, "\n"); + ss.str(std::string()); + ss << highsFormatToString("KKT.DE: max.abs.err = %.2e on column %d\n", + absolute_error_value, (int)absolute_error_index); + ss << highsFormatToString(" max.rel.err = %.2e on column %d\n", + relative_error_value, (int)relative_error_index); + ss << highsFormatToString( + "%8s%s\n", "", + relative_error_value <= kGlpsolHighQuality ? "High quality" + : relative_error_value <= kGlpsolMediumQuality ? "Medium quality" + : relative_error_value <= kGlpsolLowQuality ? "Low quality" + : "DUAL SOLUTION IS WRONG"); + ss << "\n"; + highsFprintfString(file, log_options, ss.str()); // Dual infeasibility absolute_error_value = errors.max_dual_infeasibility.absolute_value; @@ -1067,30 +1127,37 @@ void writeGlpsolSolution(FILE* file, const HighsOptions& options, if (!relative_error_value) relative_error_index = 0; bool on_col = absolute_error_index > 0 && absolute_error_index <= lp.num_col_; - fprintf(file, "KKT.DB: max.abs.err = %.2e on %s %d\n", absolute_error_value, - on_col ? "column" : "row", - absolute_error_index <= lp.num_col_ - ? (int)absolute_error_index - : (int)(absolute_error_index - lp.num_col_)); + ss.str(std::string()); + ss << highsFormatToString("KKT.DB: max.abs.err = %.2e on %s %d\n", + absolute_error_value, on_col ? "column" : "row", + absolute_error_index <= lp.num_col_ + ? (int)absolute_error_index + : (int)(absolute_error_index - lp.num_col_)); + highsFprintfString(file, log_options, ss.str()); on_col = relative_error_index > 0 && relative_error_index <= lp.num_col_; - fprintf(file, " max.rel.err = %.2e on %s %d\n", relative_error_value, - on_col ? "column" : "row", - relative_error_index <= lp.num_col_ - ? (int)relative_error_index - : (int)(relative_error_index - lp.num_col_)); - fprintf(file, "%8s%s\n", "", - relative_error_value <= kGlpsolHighQuality ? "High quality" - : relative_error_value <= kGlpsolMediumQuality ? "Medium quality" - : relative_error_value <= kGlpsolLowQuality - ? "Low quality" - : "DUAL SOLUTION IS INFEASIBLE"); - highsFprintfString(file, log_options, "\n"); + ss.str(std::string()); + ss << highsFormatToString(" max.rel.err = %.2e on %s %d\n", + relative_error_value, on_col ? "column" : "row", + relative_error_index <= lp.num_col_ + ? (int)relative_error_index + : (int)(relative_error_index - lp.num_col_)); + highsFprintfString(file, log_options, ss.str()); + ss.str(std::string()); + ss << highsFormatToString( + "%8s%s\n", "", + relative_error_value <= kGlpsolHighQuality ? "High quality" + : relative_error_value <= kGlpsolMediumQuality ? "Medium quality" + : relative_error_value <= kGlpsolLowQuality + ? "Low quality" + : "DUAL SOLUTION IS INFEASIBLE"); + ss << "\n"; + highsFprintfString(file, log_options, ss.str()); } highsFprintfString(file, log_options, "End of output\n"); } void writeOldRawSolution(FILE* file, const HighsLogOptions& log_options, - const HighsLp& lp, const HighsBasis& basis, + const HighsLp& lp, const HighsBasis& basis, const HighsSolution& solution) { const bool have_value = solution.value_valid; const bool have_dual = solution.dual_valid; @@ -1114,44 +1181,59 @@ void writeOldRawSolution(FILE* file, const HighsLogOptions& log_options, use_row_status = basis.row_status; } if (!have_value && !have_dual && !have_basis) return; - fprintf(file, + highsFprintfString( + file, log_options, + highsFormatToString( "%" HIGHSINT_FORMAT " %" HIGHSINT_FORMAT " : Number of columns and rows for primal or dual solution " "or basis\n", - lp.num_col_, lp.num_row_); + lp.num_col_, lp.num_row_)); + std::stringstream ss; + ss.str(std::string()); if (have_value) { - fprintf(file, "T"); + ss << highsFormatToString("T"); } else { - fprintf(file, "F"); + ss << highsFormatToString("F"); } - fprintf(file, " Primal solution\n"); + ss << highsFormatToString(" Primal solution\n"); + highsFprintfString(file, log_options, ss.str()); + ss.str(std::string()); if (have_dual) { - fprintf(file, "T"); + ss << highsFormatToString("T"); } else { - fprintf(file, "F"); + ss << highsFormatToString("F"); } - fprintf(file, " Dual solution\n"); + ss << highsFormatToString(" Dual solution\n"); + highsFprintfString(file, log_options, ss.str()); + ss.str(std::string()); if (have_basis) { - fprintf(file, "T"); + ss << highsFormatToString("T"); } else { - fprintf(file, "F"); + ss << highsFormatToString("F"); } - fprintf(file, " Basis\n"); + ss << highsFormatToString(" Basis\n"); + highsFprintfString(file, log_options, ss.str()); highsFprintfString(file, log_options, "Columns\n"); for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) { - if (have_value) fprintf(file, "%.15g ", use_col_value[iCol]); - if (have_dual) fprintf(file, "%.15g ", use_col_dual[iCol]); + ss.str(std::string()); + if (have_value) ss << highsFormatToString("%.15g ", use_col_value[iCol]); + if (have_dual) ss << highsFormatToString("%.15g ", use_col_dual[iCol]); if (have_basis) - fprintf(file, "%" HIGHSINT_FORMAT "", (HighsInt)use_col_status[iCol]); - fprintf(file, "\n"); + ss << highsFormatToString("%" HIGHSINT_FORMAT "", + (HighsInt)use_col_status[iCol]); + ss << highsFormatToString("\n"); + highsFprintfString(file, log_options, ss.str()); } highsFprintfString(file, log_options, "Rows\n"); for (HighsInt iRow = 0; iRow < lp.num_row_; iRow++) { - if (have_value) fprintf(file, "%.15g ", use_row_value[iRow]); - if (have_dual) fprintf(file, "%.15g ", use_row_dual[iRow]); + ss.str(std::string()); + if (have_value) ss << highsFormatToString("%.15g ", use_row_value[iRow]); + if (have_dual) ss << highsFormatToString("%.15g ", use_row_dual[iRow]); if (have_basis) - fprintf(file, "%" HIGHSINT_FORMAT "", (HighsInt)use_row_status[iRow]); - fprintf(file, "\n"); + ss << highsFormatToString("%" HIGHSINT_FORMAT "", + (HighsInt)use_row_status[iRow]); + ss << highsFormatToString("\n"); + highsFprintfString(file, log_options, ss.str()); } } diff --git a/src/lp_data/HighsModelUtils.h b/src/lp_data/HighsModelUtils.h index ac369ab5cf..110e668f5a 100644 --- a/src/lp_data/HighsModelUtils.h +++ b/src/lp_data/HighsModelUtils.h @@ -31,35 +31,33 @@ bool hasNamesWithSpaces(const HighsLogOptions& log_options, const HighsInt num_name, const std::vector& names); void writeModelBoundSolution( - FILE* file, const HighsLogOptions& log_options, - const bool columns, const HighsInt dim, - const std::vector& lower, const std::vector& upper, - const std::vector& names, const bool have_primal, - const std::vector& primal, const bool have_dual, - const std::vector& dual, const bool have_basis, - const std::vector& status, + FILE* file, const HighsLogOptions& log_options, const bool columns, + const HighsInt dim, const std::vector& lower, + const std::vector& upper, const std::vector& names, + const bool have_primal, const std::vector& primal, + const bool have_dual, const std::vector& dual, + const bool have_basis, const std::vector& status, const HighsVarType* integrality = NULL); void writeModelObjective(FILE* file, const HighsLogOptions& log_options, - const HighsModel& model, + const HighsModel& model, const std::vector& primal_solution); void writeLpObjective(FILE* file, const HighsLogOptions& log_options, - const HighsLp& lp, + const HighsLp& lp, const std::vector& primal_solution); void writeObjectiveValue(FILE* file, const HighsLogOptions& log_options, - const double objective_value); + const double objective_value); void writePrimalSolution(FILE* file, const HighsLogOptions& log_options, - const HighsLp& lp, + const HighsLp& lp, const std::vector& primal_solution, const bool sparse = false); void writeModelSolution(FILE* file, const HighsLogOptions& log_options, - const HighsModel& model, - const HighsSolution& solution, const HighsInfo& info, - const bool sparse = false); + const HighsModel& model, const HighsSolution& solution, + const HighsInfo& info, const bool sparse = false); HighsInt maxNameLength(const HighsInt num_name, const std::vector& names); @@ -74,7 +72,8 @@ void writeSolutionFile(FILE* file, const HighsOptions& options, const HighsModelStatus model_status, const HighsInt style); -void writeGlpsolCostRow(FILE* file, const bool raw, const bool is_mip, +void writeGlpsolCostRow(FILE* file, const HighsLogOptions& log_options, + const bool raw, const bool is_mip, const HighsInt row_id, const std::string objective_name, const double objective_function_value); @@ -85,7 +84,7 @@ void writeGlpsolSolution(FILE* file, const HighsOptions& options, const HighsInfo& info, const bool raw); void writeOldRawSolution(FILE* file, const HighsLogOptions& log_options, - const HighsLp& lp, const HighsBasis& basis, + const HighsLp& lp, const HighsBasis& basis, const HighsSolution& solution); HighsBasisStatus checkedVarHighsNonbasicStatus( diff --git a/src/mip/HighsMipSolverData.cpp b/src/mip/HighsMipSolverData.cpp index eafb952cf7..d1ec3b890a 100644 --- a/src/mip/HighsMipSolverData.cpp +++ b/src/mip/HighsMipSolverData.cpp @@ -1942,10 +1942,11 @@ void HighsMipSolverData::saveReportMipSolution(const double new_upper_limit) { FILE* file = mipsolver.improving_solution_file_; if (file) { writeLpObjective(file, mipsolver.options_mip_->log_options, - *(mipsolver.orig_model_), mipsolver.solution_); - writePrimalSolution(file, mipsolver.options_mip_->log_options, - *(mipsolver.orig_model_), mipsolver.solution_, - mipsolver.options_mip_->mip_improving_solution_report_sparse); + *(mipsolver.orig_model_), mipsolver.solution_); + writePrimalSolution( + file, mipsolver.options_mip_->log_options, *(mipsolver.orig_model_), + mipsolver.solution_, + mipsolver.options_mip_->mip_improving_solution_report_sparse); } } From abcce09c4c3cc5d393c3f24202d86b14163ee897 Mon Sep 17 00:00:00 2001 From: JAJHall Date: Wed, 10 Jul 2024 16:14:31 +0100 Subject: [PATCH 08/38] Identified that HighsMipSolverData::checkLimits is not called for a long time before new call before mipdata_->evaluateRootNode(); --- src/mip/HighsMipSolver.cpp | 7 ++++++- src/mip/HighsMipSolverData.cpp | 2 ++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/mip/HighsMipSolver.cpp b/src/mip/HighsMipSolver.cpp index 2108729c3e..28811b0324 100644 --- a/src/mip/HighsMipSolver.cpp +++ b/src/mip/HighsMipSolver.cpp @@ -137,6 +137,11 @@ void HighsMipSolver::run() { mipdata_->runSetup(); restart: if (modelstatus_ == HighsModelStatus::kNotset) { + // Check limits have not been reached before evaluating root node + if (mipdata_->checkLimits()) { + cleanupSolve(); + return; + } mipdata_->evaluateRootNode(); // age 5 times to remove stored but never violated cuts after root // separation @@ -146,7 +151,7 @@ void HighsMipSolver::run() { mipdata_->cutpool.performAging(); mipdata_->cutpool.performAging(); } - if (mipdata_->nodequeue.empty()) { + if (mipdata_->nodequeue.empty() || mipdata_->checkLimits()) { cleanupSolve(); return; } diff --git a/src/mip/HighsMipSolverData.cpp b/src/mip/HighsMipSolverData.cpp index d1ec3b890a..9dcd8129b6 100644 --- a/src/mip/HighsMipSolverData.cpp +++ b/src/mip/HighsMipSolverData.cpp @@ -1870,6 +1870,8 @@ bool HighsMipSolverData::checkLimits(int64_t nodeOffset) const { return true; } + // const double time = mipsolver.timer_.read(mipsolver.timer_.solve_clock); + // printf("checkLimits: time = %g\n", time); if (mipsolver.timer_.read(mipsolver.timer_.solve_clock) >= options.time_limit) { if (mipsolver.modelstatus_ == HighsModelStatus::kNotset) { From 6096006369d5a08318a5db7df03722c87e346262 Mon Sep 17 00:00:00 2001 From: JAJHall Date: Wed, 10 Jul 2024 16:24:04 +0100 Subject: [PATCH 09/38] Added some time reporting to HighsMipSolver::run() when not submip --- src/mip/HighsMipSolver.cpp | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/mip/HighsMipSolver.cpp b/src/mip/HighsMipSolver.cpp index 28811b0324..4724f1a33b 100644 --- a/src/mip/HighsMipSolver.cpp +++ b/src/mip/HighsMipSolver.cpp @@ -115,6 +115,10 @@ void HighsMipSolver::run() { mipdata_ = decltype(mipdata_)(new HighsMipSolverData(*this)); mipdata_->init(); mipdata_->runPresolve(options_mip_->presolve_reduction_limit); + if (!submip) + highsLogUser(options_mip_->log_options, HighsLogType::kInfo, + "MIP-Timing: After %6.4fs - completed mipdata_->runPresolve\n", + timer_.read(timer_.solve_clock)); // Identify whether time limit has been reached (in presolve) if (modelstatus_ == HighsModelStatus::kNotset && timer_.read(timer_.solve_clock) >= options_mip_->time_limit) @@ -134,7 +138,15 @@ void HighsMipSolver::run() { return; } + if (!submip) + highsLogUser(options_mip_->log_options, HighsLogType::kInfo, + "MIP-Timing: After %6.4fs - reached mipdata_->runSetup()\n", + timer_.read(timer_.solve_clock)); mipdata_->runSetup(); + if (!submip) + highsLogUser(options_mip_->log_options, HighsLogType::kInfo, + "MIP-Timing: After %6.4fs - completed mipdata_->runSetup()\n", + timer_.read(timer_.solve_clock)); restart: if (modelstatus_ == HighsModelStatus::kNotset) { // Check limits have not been reached before evaluating root node @@ -142,7 +154,17 @@ void HighsMipSolver::run() { cleanupSolve(); return; } + if (!submip) + highsLogUser( + options_mip_->log_options, HighsLogType::kInfo, + "MIP-Timing: After %6.4fs - reached mipdata_->evaluateRootNode()\n", + timer_.read(timer_.solve_clock)); mipdata_->evaluateRootNode(); + if (!submip) + highsLogUser( + options_mip_->log_options, HighsLogType::kInfo, + "MIP-Timing: After %6.4fs - completed mipdata_->evaluateRootNode()\n", + timer_.read(timer_.solve_clock)); // age 5 times to remove stored but never violated cuts after root // separation mipdata_->cutpool.performAging(); From 8d269a21d614422923ba64fa91f7799a7bec5148 Mon Sep 17 00:00:00 2001 From: jajhall Date: Thu, 11 Jul 2024 16:51:05 +0100 Subject: [PATCH 10/38] Added setSolution for sparse primal solution and test to check-set-mip-solution --- check/TestCheckSolution.cpp | 42 +++++++++++++++++++++++++++++++++++-- src/Highs.h | 7 +++++++ src/lp_data/Highs.cpp | 8 +++++++ 3 files changed, 55 insertions(+), 2 deletions(-) diff --git a/check/TestCheckSolution.cpp b/check/TestCheckSolution.cpp index c7ca67da2b..3ef82eb0bc 100644 --- a/check/TestCheckSolution.cpp +++ b/check/TestCheckSolution.cpp @@ -5,7 +5,7 @@ #include "SpecialLps.h" #include "catch.hpp" -const bool dev_run = false; +const bool dev_run = true; void runWriteReadCheckSolution(Highs& highs, const std::string model, const HighsModelStatus require_model_status, @@ -89,7 +89,7 @@ TEST_CASE("check-set-mip-solution", "[highs_check_solution]") { highs.clear(); - const bool other_tests = true; + const bool other_tests = false;//true; const bool test0 = other_tests; bool valid, integral, feasible; if (test0) { @@ -229,6 +229,44 @@ TEST_CASE("check-set-mip-solution", "[highs_check_solution]") { highs.clear(); } + const bool test6 = true;//other_tests; + if (test6) { + if (dev_run) + printf( + "\n***************************\nSolving from sparse integer " + "solution\n"); + HighsInt num_integer_variable = 0; + for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) + if (lp.integrality_[iCol] == HighsVarType::kInteger) num_integer_variable++; + + + highs.setOptionValue("output_flag", dev_run); + highs.readModel(model_file); + + std::vector is_set; + is_set.assign(lp.num_col_, false); + std::vector index; + std::vector value; + HighsInt num_to_set = std::max(10, (8*num_integer_variable)/10); + assert(num_to_set>0); + HighsRandom random; + for (HighsInt iSet = 0; iSet < num_to_set;) { + HighsInt iCol = random.integer(lp.num_col_); + if (lp.integrality_[iCol] != HighsVarType::kInteger) continue; + if (is_set[iCol]) continue; + index.push_back(iCol); + value.push_back(optimal_solution.col_value[iCol]); + iSet++; + } + HighsInt num_entries = index.size(); + assert(num_entries == num_to_set); + return_status = highs.setSolution(num_entries, index.data(), value.data()); + REQUIRE(return_status == HighsStatus::kOk); + highs.run(); + REQUIRE(info.mip_node_count < scratch_num_nodes); + highs.clear(); + } + assert(other_tests); std::remove(solution_file.c_str()); } diff --git a/src/Highs.h b/src/Highs.h index 7b3c08ca26..3a1c637642 100644 --- a/src/Highs.h +++ b/src/Highs.h @@ -1063,6 +1063,13 @@ class Highs { */ HighsStatus setSolution(const HighsSolution& solution); + /** + * @brief Pass a sparse primal solution + */ + HighsStatus setSolution(const HighsInt num_entries, + const HighsInt* index, + const double* value); + /** * @brief Set the callback method to use for HiGHS */ diff --git a/src/lp_data/Highs.cpp b/src/lp_data/Highs.cpp index fa600bba14..691af85ade 100644 --- a/src/lp_data/Highs.cpp +++ b/src/lp_data/Highs.cpp @@ -1965,6 +1965,14 @@ HighsStatus Highs::setSolution(const HighsSolution& solution) { return returnFromHighs(return_status); } +HighsStatus Highs::setSolution(const HighsInt num_entries, + const HighsInt* index, + const double* value) { + HighsStatus return_status = HighsStatus::kOk; + return_status = HighsStatus::kError; + return returnFromHighs(return_status); +} + HighsStatus Highs::setCallback(HighsCallbackFunctionType user_callback, void* user_callback_data) { this->callback_.clear(); From 7fd28a11519ea40cea74115ad409a82198189bf3 Mon Sep 17 00:00:00 2001 From: JAJHall Date: Sun, 14 Jul 2024 17:29:08 +0100 Subject: [PATCH 11/38] Now clearing solution when completeSolutionFromDiscreteAssignment decides it's not worth it --- check/TestCallbacks.cpp | 2 +- check/TestCheckSolution.cpp | 33 ++++-- src/Highs.h | 5 +- src/lp_data/HConst.h | 1 + src/lp_data/Highs.cpp | 197 +++++++++++++++++++++++++++--------- 5 files changed, 178 insertions(+), 60 deletions(-) diff --git a/check/TestCallbacks.cpp b/check/TestCallbacks.cpp index 62e9dc105f..437fdaf7b3 100644 --- a/check/TestCallbacks.cpp +++ b/check/TestCallbacks.cpp @@ -6,7 +6,7 @@ #include "catch.hpp" #include "lp_data/HighsCallback.h" -const bool dev_run = true; +const bool dev_run = false; const double egout_optimal_objective = 568.1007; const double egout_objective_target = 610; diff --git a/check/TestCheckSolution.cpp b/check/TestCheckSolution.cpp index 3ef82eb0bc..d0f2309b1c 100644 --- a/check/TestCheckSolution.cpp +++ b/check/TestCheckSolution.cpp @@ -5,7 +5,7 @@ #include "SpecialLps.h" #include "catch.hpp" -const bool dev_run = true; +const bool dev_run = false; void runWriteReadCheckSolution(Highs& highs, const std::string model, const HighsModelStatus require_model_status, @@ -89,7 +89,7 @@ TEST_CASE("check-set-mip-solution", "[highs_check_solution]") { highs.clear(); - const bool other_tests = false;//true; + const bool other_tests = true; const bool test0 = other_tests; bool valid, integral, feasible; if (test0) { @@ -229,7 +229,7 @@ TEST_CASE("check-set-mip-solution", "[highs_check_solution]") { highs.clear(); } - const bool test6 = true;//other_tests; + const bool test6 = other_tests; if (test6) { if (dev_run) printf( @@ -237,28 +237,41 @@ TEST_CASE("check-set-mip-solution", "[highs_check_solution]") { "solution\n"); HighsInt num_integer_variable = 0; for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) - if (lp.integrality_[iCol] == HighsVarType::kInteger) num_integer_variable++; + if (lp.integrality_[iCol] == HighsVarType::kInteger) + num_integer_variable++; - highs.setOptionValue("output_flag", dev_run); highs.readModel(model_file); + std::vector index; + std::vector value; + // Check that duplicate values are spotted + index.push_back(0); + value.push_back(0); + index.push_back(1); + value.push_back(1); + index.push_back(0); + value.push_back(2); + HighsInt num_entries = index.size(); + return_status = highs.setSolution(num_entries, index.data(), value.data()); + REQUIRE(return_status == HighsStatus::kWarning); + index.clear(); + value.clear(); std::vector is_set; is_set.assign(lp.num_col_, false); - std::vector index; - std::vector value; - HighsInt num_to_set = std::max(10, (8*num_integer_variable)/10); - assert(num_to_set>0); + HighsInt num_to_set = 2; + assert(num_to_set > 0); HighsRandom random; for (HighsInt iSet = 0; iSet < num_to_set;) { HighsInt iCol = random.integer(lp.num_col_); if (lp.integrality_[iCol] != HighsVarType::kInteger) continue; if (is_set[iCol]) continue; + is_set[iCol] = true; index.push_back(iCol); value.push_back(optimal_solution.col_value[iCol]); iSet++; } - HighsInt num_entries = index.size(); + num_entries = index.size(); assert(num_entries == num_to_set); return_status = highs.setSolution(num_entries, index.data(), value.data()); REQUIRE(return_status == HighsStatus::kOk); diff --git a/src/Highs.h b/src/Highs.h index 3a1c637642..f104d72f1d 100644 --- a/src/Highs.h +++ b/src/Highs.h @@ -1066,9 +1066,8 @@ class Highs { /** * @brief Pass a sparse primal solution */ - HighsStatus setSolution(const HighsInt num_entries, - const HighsInt* index, - const double* value); + HighsStatus setSolution(const HighsInt num_entries, const HighsInt* index, + const double* value); /** * @brief Set the callback method to use for HiGHS diff --git a/src/lp_data/HConst.h b/src/lp_data/HConst.h index e9359556e2..d7980f4cc8 100644 --- a/src/lp_data/HConst.h +++ b/src/lp_data/HConst.h @@ -27,6 +27,7 @@ const size_t kHighsSize_tInf = std::numeric_limits::max(); const HighsInt kHighsIInf = std::numeric_limits::max(); const HighsInt kHighsIInf32 = std::numeric_limits::max(); const double kHighsInf = std::numeric_limits::infinity(); +const double kHighsUndefined = kHighsInf; const double kHighsTiny = 1e-14; const double kHighsMacheps = std::ldexp(1, -52); const double kHighsZero = 1e-50; diff --git a/src/lp_data/Highs.cpp b/src/lp_data/Highs.cpp index 691af85ade..bf3c72b384 100644 --- a/src/lp_data/Highs.cpp +++ b/src/lp_data/Highs.cpp @@ -1966,11 +1966,53 @@ HighsStatus Highs::setSolution(const HighsSolution& solution) { } HighsStatus Highs::setSolution(const HighsInt num_entries, - const HighsInt* index, - const double* value) { + const HighsInt* index, const double* value) { HighsStatus return_status = HighsStatus::kOk; - return_status = HighsStatus::kError; - return returnFromHighs(return_status); + // Warn about duplicates in index + HighsInt num_duplicates = 0; + std::vector is_set; + is_set.assign(model_.lp_.num_col_, false); + for (HighsInt iX = 0; iX < num_entries; iX++) { + HighsInt iCol = index[iX]; + if (iCol < 0 || iCol > model_.lp_.num_col_) { + highsLogUser(options_.log_options, HighsLogType::kError, + "setSolution: User solution index %d has value %d out of " + "range [0, %d)", + int(iX), int(iCol), int(model_.lp_.num_col_)); + return HighsStatus::kError; + } else if (value[iX] < model_.lp_.col_lower_[iCol] - + options_.primal_feasibility_tolerance || + model_.lp_.col_upper_[iCol] + + options_.primal_feasibility_tolerance < + value[iX]) { + highsLogUser(options_.log_options, HighsLogType::kError, + "setSolution: User solution value %d of %g is infeasible " + "for bounds [%g, %g]", + int(iX), value[iX], model_.lp_.col_lower_[iCol], + model_.lp_.col_upper_[iCol]); + return HighsStatus::kError; + } + if (is_set[iCol]) num_duplicates++; + is_set[iCol] = true; + } + if (num_duplicates > 0) { + highsLogUser(options_.log_options, HighsLogType::kWarning, + "setSolution: User set of indices has %d duplicate%s: last " + "value used\n", + int(num_duplicates), num_duplicates > 1 ? "s" : ""); + return_status = HighsStatus::kWarning; + } + + // Clear the solution, indicate the values not determined by the + // user, and insert the values determined by the user + HighsSolution new_solution; + new_solution.col_value.assign(model_.lp_.num_col_, kHighsUndefined); + for (HighsInt iX = 0; iX < num_entries; iX++) { + HighsInt iCol = index[iX]; + new_solution.col_value[iCol] = value[iX]; + } + return interpretCallStatus(options_.log_options, setSolution(new_solution), + return_status, "setSolution"); } HighsStatus Highs::setCallback(HighsCallbackFunctionType user_callback, @@ -3356,62 +3398,125 @@ void Highs::invalidateEkk() { ekk_instance_.invalidate(); } HighsStatus Highs::completeSolutionFromDiscreteAssignment() { // Determine whether the current solution of a MIP is feasible and, - // if not, try to assign values to continuous variables to achieve a - // feasible solution. Valuable in the case where users make a - // heuristic assignment of discrete variables + // if not, try to assign values to continuous variables and discrete + // variables not at integer values to achieve a feasible + // solution. Valuable in the case where users make a heuristic + // (partial) assignment of discrete variables assert(model_.isMip() && solution_.value_valid); HighsLp& lp = model_.lp_; - bool valid, integral, feasible; - // Determine whether this solution is feasible, or just integer feasible - HighsStatus return_status = assessLpPrimalSolution(options_, lp, solution_, - valid, integral, feasible); - assert(return_status != HighsStatus::kError); - assert(valid); - // If the current solution is feasible, then solution can be used by - // MIP solver to get a primal bound - if (feasible) return HighsStatus::kOk; + // Determine whether the solution contains undefined values, in + // order to decide whether to check its feasibility + bool contains_undefined_values = false; + for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) { + if (solution_.col_value[iCol] == kHighsUndefined) { + contains_undefined_values = true; + break; + } + } + if (!contains_undefined_values) { + bool valid, integral, feasible; + // Determine whether this solution is integer feasible + HighsStatus return_status = assessLpPrimalSolution( + options_, lp, solution_, valid, integral, feasible); + assert(return_status != HighsStatus::kError); + assert(valid); + // If the current solution is integer feasible, then it can be + // used by MIP solver to get a primal bound + if (feasible) return HighsStatus::kOk; + } // Save the column bounds and integrality in preparation for fixing - // the non-continuous variables when user-supplied values are + // the discrete variables when user-supplied values are // integer std::vector save_col_lower = lp.col_lower_; std::vector save_col_upper = lp.col_upper_; std::vector save_integrality = lp.integrality_; const bool have_integrality = (lp.integrality_.size() != 0); - bool is_integer = true; + assert(have_integrality); + // Count the number of fixed and unfixed discrete variables + HighsInt num_fixed_discrete_variable = 0; + HighsInt num_unfixed_discrete_variable = 0; for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) { - if (lp.integrality_[iCol] == HighsVarType::kContinuous) continue; - // Fix non-continuous variable if it has integer value const double primal = solution_.col_value[iCol]; - const double lower = lp.col_lower_[iCol]; - const double upper = lp.col_upper_[iCol]; - const HighsVarType type = - have_integrality ? lp.integrality_[iCol] : HighsVarType::kContinuous; - double col_infeasibility = 0; - double integer_infeasibility = 0; - assessColPrimalSolution(options_, primal, lower, upper, type, - col_infeasibility, integer_infeasibility); - if (integer_infeasibility > options_.mip_feasibility_tolerance) { - // Variable is not integer feasible, so record that a MIP will - // have to be solved - is_integer = false; + // Default value is lower bound, unless primal is integer for a + // discrete variable + solution_.col_value[iCol] = lp.col_lower_[iCol]; + if (lp.integrality_[iCol] == HighsVarType::kContinuous) continue; + // Fix discrete variable if its value is defined and integer + if (primal == kHighsUndefined) { + num_unfixed_discrete_variable++; } else { - // Variable is integer feasible, so fix it at this value and - // remove its integrality - lp.col_lower_[iCol] = solution_.col_value[iCol]; - lp.col_upper_[iCol] = solution_.col_value[iCol]; - lp.integrality_[iCol] = HighsVarType::kContinuous; + const double lower = lp.col_lower_[iCol]; + const double upper = lp.col_upper_[iCol]; + const HighsVarType type = + have_integrality ? lp.integrality_[iCol] : HighsVarType::kContinuous; + double col_infeasibility = 0; + double integer_infeasibility = 0; + assessColPrimalSolution(options_, primal, lower, upper, type, + col_infeasibility, integer_infeasibility); + if (integer_infeasibility > options_.mip_feasibility_tolerance) { + num_unfixed_discrete_variable++; + } else { + // Variable is integer feasible, so fix it at this value and + // remove its integrality + num_fixed_discrete_variable++; + lp.col_lower_[iCol] = primal; + lp.col_upper_[iCol] = primal; + lp.integrality_[iCol] = HighsVarType::kContinuous; + } } } - // If the solution is integer valued, only an LP needs to be solved, - // so clear all integrality - if (is_integer) lp.integrality_.clear(); + const HighsInt num_discrete_variable = + num_unfixed_discrete_variable + num_fixed_discrete_variable; + const HighsInt num_continuous_variable = lp.num_col_ - num_discrete_variable; + assert(num_continuous_variable >= 0); + bool call_run = true; + if (num_unfixed_discrete_variable == 0) { + // Solution is integer valued + if (num_continuous_variable == 0) { + // There are no continuous variables, so no feasible solution can be + // deduced + highsLogUser(options_.log_options, HighsLogType::kInfo, + "User-supplied values of discrete variables cannot yield " + "feasible solution\n"); + call_run = false; + } else { + // Solve an LP, so clear all integrality + lp.integrality_.clear(); + highsLogUser( + options_.log_options, HighsLogType::kInfo, + "Attempting to find feasible solution " + "by solving LP for user-supplied values of discrete variables\n"); + } + } else { + // There are unfixed discrete variables + if (10 * num_fixed_discrete_variable < num_discrete_variable) { + // Too few discrete variables are fixed + highsLogUser(options_.log_options, HighsLogType::kInfo, + "User-supplied values fix only %d / %d discrete variables, " + "so not attempting to complete a feasible solution\n", + int(num_fixed_discrete_variable), + int(num_discrete_variable)); + call_run = false; + } else { + highsLogUser(options_.log_options, HighsLogType::kInfo, + "Attempting to find feasible solution " + "by solving MIP for user-supplied values of %d / %d " + "discrete variables\n", + int(num_fixed_discrete_variable), + int(num_discrete_variable)); + } + } + HighsStatus return_status = HighsStatus::kOk; + // Clear the current solution since either the user solution has + // been used to fix (a subset of) discrete variables - so a valid + // solution will be obtained from run() if the local model is + // feasible - or it's not worth using the user solution solution_.clear(); - basis_.clear(); - // Solve the model - highsLogUser(options_.log_options, HighsLogType::kInfo, - "Attempting to find feasible solution " - "for (partial) user-supplied values of discrete variables\n"); - return_status = this->run(); + if (call_run) { + // Solve the model + basis_.clear(); + return_status = this->run(); + } // Recover the column bounds and integrality lp.col_lower_ = save_col_lower; lp.col_upper_ = save_col_upper; From 39569be5ae58ef62d2f93cd7fd4909ecbe310156 Mon Sep 17 00:00:00 2001 From: JAJHall Date: Sun, 14 Jul 2024 17:47:06 +0100 Subject: [PATCH 12/38] Added highs_setSolution and highs_setSparseSolution to highs_bindings --- src/highs_bindings.cpp | 21 ++++++++++++++++++++- src/lp_data/HStruct.h | 1 + src/lp_data/Highs.cpp | 1 + src/lp_data/HighsSolution.cpp | 6 ++++++ 4 files changed, 28 insertions(+), 1 deletion(-) diff --git a/src/highs_bindings.cpp b/src/highs_bindings.cpp index 4883bd6c25..e7f9e49cac 100644 --- a/src/highs_bindings.cpp +++ b/src/highs_bindings.cpp @@ -285,6 +285,24 @@ HighsStatus highs_deleteRows(Highs* h, HighsInt num_set_entries, std::vectordeleteRows(num_set_entries, indices.data()); } + +HighsStatus highs_setSolution(Highs* h, HighsSolution& solution) { + return h->setSolution(solution); +} + +HighsStatus highs_setSparseSolution(Highs* h, HighsInt num_entries, + py::array_t index, + py::array_t value) { + py::buffer_info index_info = index.request(); + py::buffer_info value_info = value.request(); + + HighsInt* index_ptr = reinterpret_cast(index_info.ptr); + double* value_ptr = static_cast(value_info.ptr); + + return h->setSolution(num_entries, index_ptr, value_ptr); +} + + HighsStatus highs_setBasis(Highs* h, HighsBasis& basis) { return h->setBasis(basis); } @@ -935,7 +953,8 @@ PYBIND11_MODULE(_core, m) { .def("deleteCols", &highs_deleteCols) .def("deleteVars", &highs_deleteCols) // alias .def("deleteRows", &highs_deleteRows) - .def("setSolution", &Highs::setSolution) + .def("setSolution", &highs_setSolution) + .def("setSolution", &highs_setSparseSolution) .def("setBasis", &highs_setBasis) .def("setBasis", &highs_setLogicalBasis) .def("modelStatusToString", &Highs::modelStatusToString) diff --git a/src/lp_data/HStruct.h b/src/lp_data/HStruct.h index 863020bfde..e54657d406 100644 --- a/src/lp_data/HStruct.h +++ b/src/lp_data/HStruct.h @@ -33,6 +33,7 @@ struct HighsSolution { std::vector col_dual; std::vector row_value; std::vector row_dual; + bool hasUndefined(); void invalidate(); void clear(); }; diff --git a/src/lp_data/Highs.cpp b/src/lp_data/Highs.cpp index bf3c72b384..fee0502b58 100644 --- a/src/lp_data/Highs.cpp +++ b/src/lp_data/Highs.cpp @@ -3413,6 +3413,7 @@ HighsStatus Highs::completeSolutionFromDiscreteAssignment() { break; } } + assert(solution_.hasUndefined() == contains_undefined_values); if (!contains_undefined_values) { bool valid, integral, feasible; // Determine whether this solution is integer feasible diff --git a/src/lp_data/HighsSolution.cpp b/src/lp_data/HighsSolution.cpp index 4ad8617ddd..77d3daa8ce 100644 --- a/src/lp_data/HighsSolution.cpp +++ b/src/lp_data/HighsSolution.cpp @@ -1438,6 +1438,12 @@ bool isBasisRightSize(const HighsLp& lp, const HighsBasis& basis) { basis.row_status.size() == static_cast(lp.num_row_); } +bool HighsSolution::hasUndefined() { + for (HighsInt iCol = 0; iCol < HighsInt(this->col_value.size()); iCol++) + if (this->col_value[iCol] == kHighsUndefined) return true; + return false; +} + void HighsSolution::invalidate() { this->value_valid = false; this->dual_valid = false; From faeb3f3972761cf2e1a86fcefd5395d770e66136 Mon Sep 17 00:00:00 2001 From: JAJHall Date: Sun, 14 Jul 2024 17:48:56 +0100 Subject: [PATCH 13/38] Formatted HighsSolution::hasUndefined() --- src/lp_data/HighsSolution.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lp_data/HighsSolution.cpp b/src/lp_data/HighsSolution.cpp index 77d3daa8ce..29e40168a0 100644 --- a/src/lp_data/HighsSolution.cpp +++ b/src/lp_data/HighsSolution.cpp @@ -1439,7 +1439,7 @@ bool isBasisRightSize(const HighsLp& lp, const HighsBasis& basis) { } bool HighsSolution::hasUndefined() { - for (HighsInt iCol = 0; iCol < HighsInt(this->col_value.size()); iCol++) + for (HighsInt iCol = 0; iCol < HighsInt(this->col_value.size()); iCol++) if (this->col_value[iCol] == kHighsUndefined) return true; return false; } From 6e9f9bdf6421d838586066570dbffc4c407395be Mon Sep 17 00:00:00 2001 From: JAJHall Date: Sun, 14 Jul 2024 18:11:18 +0100 Subject: [PATCH 14/38] Added Highs_setSparseSolution to C API --- src/interfaces/highs_c_api.cpp | 5 +++++ src/interfaces/highs_c_api.h | 13 +++++++++++++ 2 files changed, 18 insertions(+) diff --git a/src/interfaces/highs_c_api.cpp b/src/interfaces/highs_c_api.cpp index 0642b73283..59b73668b6 100644 --- a/src/interfaces/highs_c_api.cpp +++ b/src/interfaces/highs_c_api.cpp @@ -668,6 +668,11 @@ HighsInt Highs_setSolution(void* highs, const double* col_value, return (HighsInt)((Highs*)highs)->setSolution(solution); } +HighsInt Highs_setSparseSolution(void* highs, const HighsInt num_entries, + const HighsInt* index, const double* value) { + return (HighsInt)((Highs*)highs)->setSolution(num_entries, index, value); +} + HighsInt Highs_setCallback(void* highs, HighsCCallbackType user_callback, void* user_callback_data) { auto status = static_cast(highs)->setCallback(user_callback, diff --git a/src/interfaces/highs_c_api.h b/src/interfaces/highs_c_api.h index 1a43dd8e81..801ddf332e 100644 --- a/src/interfaces/highs_c_api.h +++ b/src/interfaces/highs_c_api.h @@ -1139,6 +1139,19 @@ HighsInt Highs_setSolution(void* highs, const double* col_value, const double* row_value, const double* col_dual, const double* row_dual); +/** + * Set a partial primal solution by passing values for a set of variables + * + * @param highs A pointer to the Highs instance. + * @param num_entries Number of variables in the set + * @param index Indices of variables in the set + * @param value Values of variables in the set + * + * @returns A `kHighsStatus` constant indicating whether the call succeeded. + */ +HighsInt Highs_setSparseSolution(void* highs, const HighsInt num_entries, + const HighsInt* index, const double* value); + /** * Set the callback method to use for HiGHS * From 3c343537206787d7e8d01702af58bd24908a3022 Mon Sep 17 00:00:00 2001 From: JAJHall Date: Sun, 14 Jul 2024 18:39:15 +0100 Subject: [PATCH 15/38] Now using solution_.hasUndefined(); updated FEATURES.md and silenced unti tests --- FEATURES.md | 7 +++++++ check/TestCallbacks.cpp | 4 ++-- src/lp_data/Highs.cpp | 10 ++-------- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/FEATURES.md b/FEATURES.md index 9d7043302e..a5e198a407 100644 --- a/FEATURES.md +++ b/FEATURES.md @@ -4,3 +4,10 @@ Added `int64_t mip_total_lp_iterations` to `HighsCallbackDataOut` and modified accessor function +`Highs::writeSolution` and `Highs::writeBasis` now being done via `HighsIO` logging, so can be redirected to logging callback. + +Introduced `const double kHighsUndefined` as value of undefined values in a user solution. It's equal to `kHighsInf` + +Added `Highs::setSolution(const HighsInt num_entries, const HighsInt* index, const double* value);` to allow a sparse primal solution to be defined. + + diff --git a/check/TestCallbacks.cpp b/check/TestCallbacks.cpp index 437fdaf7b3..d97bc0d271 100644 --- a/check/TestCallbacks.cpp +++ b/check/TestCallbacks.cpp @@ -277,8 +277,8 @@ TEST_CASE("highs-callback-solution-basis-logging", "[highs-callback]") { highs.run(); highs.setCallback(userInterruptCallback, p_user_callback_data); highs.startCallback(kCallbackLogging); - highs.writeSolution("", kSolutionStylePretty); - highs.writeBasis(""); + if (dev_run) highs.writeSolution("", kSolutionStylePretty); + if (dev_run) highs.writeBasis(""); } TEST_CASE("highs-callback-simplex-interrupt", "[highs-callback]") { diff --git a/src/lp_data/Highs.cpp b/src/lp_data/Highs.cpp index fee0502b58..ac8196715a 100644 --- a/src/lp_data/Highs.cpp +++ b/src/lp_data/Highs.cpp @@ -3406,14 +3406,7 @@ HighsStatus Highs::completeSolutionFromDiscreteAssignment() { HighsLp& lp = model_.lp_; // Determine whether the solution contains undefined values, in // order to decide whether to check its feasibility - bool contains_undefined_values = false; - for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) { - if (solution_.col_value[iCol] == kHighsUndefined) { - contains_undefined_values = true; - break; - } - } - assert(solution_.hasUndefined() == contains_undefined_values); + const bool contains_undefined_values = solution_.hasUndefined(); if (!contains_undefined_values) { bool valid, integral, feasible; // Determine whether this solution is integer feasible @@ -3466,6 +3459,7 @@ HighsStatus Highs::completeSolutionFromDiscreteAssignment() { } } } + assert(!solution_.hasUndefined()); const HighsInt num_discrete_variable = num_unfixed_discrete_variable + num_fixed_discrete_variable; const HighsInt num_continuous_variable = lp.num_col_ - num_discrete_variable; From cf3b5368bf6d5d03a7fdbf680a7a063fdfb2a19c Mon Sep 17 00:00:00 2001 From: Rohit Goswami Date: Sat, 4 May 2024 18:24:41 +0000 Subject: [PATCH 16/38] MAINT: Silence -Wsign-compare In any case it is better to be consistent here --- src/qpsolver/feasibility_bounded.hpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/qpsolver/feasibility_bounded.hpp b/src/qpsolver/feasibility_bounded.hpp index 4a92c813e1..f28f5c5ca0 100644 --- a/src/qpsolver/feasibility_bounded.hpp +++ b/src/qpsolver/feasibility_bounded.hpp @@ -18,16 +18,16 @@ static void computeStartingPointBounded(Instance& instance, L.resize(instance.num_var * instance.num_var); // compute cholesky factorization of Q - for (size_t col = 0; col < (size_t)instance.num_var; col++) { - for (size_t idx = instance.Q.mat.start[col]; idx < (size_t)instance.Q.mat.start[col+1]; idx++) { + for (HighsInt col = 0; col < static_cast(instance.num_var); col++) { + for (HighsInt idx = instance.Q.mat.start[col]; idx < static_cast(instance.Q.mat.start[col+1]); idx++) { double sum = 0; - size_t row = instance.Q.mat.index[idx]; + HighsInt row = instance.Q.mat.index[idx]; if (row == col) { - for (size_t k = 0; k < row; k++) + for (HighsInt k = 0; k < row; k++) sum += L[k * instance.num_var + row] * L[k * instance.num_var + row]; L[row * instance.num_var + row] = sqrt(instance.Q.mat.value[idx] - sum); } else { - for (size_t k = 0; k < row; k++) + for (HighsInt k = 0; k < row; k++) sum += (L[k * instance.num_var + col] * L[k * instance.num_var + row]); L[row * instance.num_var + col] = (instance.Q.mat.value[idx] - sum) / L[row * instance.num_var + row]; From cb2556de8a4b5f7b385a7cc056ae8617ce3c81be Mon Sep 17 00:00:00 2001 From: Rohit Goswami Date: Sat, 4 May 2024 18:36:12 +0000 Subject: [PATCH 17/38] MAINT: Silence -Wmaybe-uninitialized --- src/pdlp/cupdlp/cupdlp_scaling_cuda.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/pdlp/cupdlp/cupdlp_scaling_cuda.c b/src/pdlp/cupdlp/cupdlp_scaling_cuda.c index c9b68e0159..f5ae4def66 100644 --- a/src/pdlp/cupdlp/cupdlp_scaling_cuda.c +++ b/src/pdlp/cupdlp/cupdlp_scaling_cuda.c @@ -54,8 +54,8 @@ cupdlp_retcode cupdlp_ruiz_scaling_cuda(CUPDLPcsc *csc, cupdlp_float *cost, cupdlp_int nRows = csc->nRows; cupdlp_int nCols = csc->nCols; - cupdlp_float *current_col_scaling; // for variable - cupdlp_float *current_row_scaling; // for constraint + cupdlp_float *current_col_scaling = NULL; // for variable + cupdlp_float *current_row_scaling = NULL; // for constraint CUPDLP_INIT_ZERO_DOUBLE(current_col_scaling, nCols); CUPDLP_INIT_ZERO_DOUBLE(current_row_scaling, nRows); @@ -128,8 +128,8 @@ cupdlp_retcode cupdlp_l2norm_scaling_cuda(CUPDLPcsc *csc, cupdlp_float *cost, cupdlp_int nRows = csc->nRows; cupdlp_int nCols = csc->nCols; - cupdlp_float *current_col_scaling; // for variable - cupdlp_float *current_row_scaling; // for constraint + cupdlp_float *current_col_scaling = NULL; // for variable + cupdlp_float *current_row_scaling = NULL; // for constraint CUPDLP_INIT_ZERO_DOUBLE(current_col_scaling, nCols); CUPDLP_INIT_ZERO_DOUBLE(current_row_scaling, nRows); @@ -179,8 +179,8 @@ cupdlp_retcode cupdlp_pc_scaling_cuda(CUPDLPcsc *csc, cupdlp_float *cost, cupdlp_int nCols = csc->nCols; cupdlp_float alpha = scaling->PcAlpha; - cupdlp_float *current_col_scaling; // for variable - cupdlp_float *current_row_scaling; // for constraint + cupdlp_float *current_col_scaling = NULL; // for variable + cupdlp_float *current_row_scaling = NULL; // for constraint CUPDLP_INIT_ZERO_DOUBLE(current_col_scaling, nCols); CUPDLP_INIT_ZERO_DOUBLE(current_row_scaling, nRows); From d226a6d69e328d689bb84f8927854f6815be71a5 Mon Sep 17 00:00:00 2001 From: Rohit Goswami Date: Sat, 4 May 2024 18:42:40 +0000 Subject: [PATCH 18/38] MAINT: Silence -Wcomment --- src/pdlp/CupdlpWrapper.h | 8 +- src/pdlp/cupdlp/glbopts.h | 154 +++++++++++++++++++------------------- 2 files changed, 81 insertions(+), 81 deletions(-) diff --git a/src/pdlp/CupdlpWrapper.h b/src/pdlp/CupdlpWrapper.h index 04444ccb7e..4444c955ce 100644 --- a/src/pdlp/CupdlpWrapper.h +++ b/src/pdlp/CupdlpWrapper.h @@ -46,10 +46,10 @@ typedef enum CONSTRAINT_TYPE { EQ = 0, LEQ, GEQ, BOUND } constraint_type; #define cupdlp_copy_vec(dst, src, type, size) \ memcpy(dst, src, sizeof(type) * (size)) -//#define cupdlp_init_csc_cpu(var, size) \ -// {\ -// (var) = (CUPDLPcsc*)malloc((size) * sizeof(CUPDLPcsc));\ -// } +/* #define cupdlp_init_csc_cpu(var, size) \ */ +/* {\ */ +/* (var) = (CUPDLPcsc*)malloc((size) * sizeof(CUPDLPcsc));\ */ +/* } */ cupdlp_retcode problem_create(CUPDLPproblem** prob); // cupdlp_retcode csc_create(CUPDLPcsc **csc_cpu); diff --git a/src/pdlp/cupdlp/glbopts.h b/src/pdlp/cupdlp/glbopts.h index ff6599c7ca..756d34be19 100644 --- a/src/pdlp/cupdlp/glbopts.h +++ b/src/pdlp/cupdlp/glbopts.h @@ -1,11 +1,11 @@ #ifndef GLB_H_GUARD #define GLB_H_GUARD -// #ifndef CUPDLP_CPU -// #include // cublas -// #include // cudaMalloc, cudaMemcpy, etc. -// #include // cusparseSpMV -// #endif +/* #ifndef CUPDLP_CPU */ +/* #include cublas */ +/* #include cudaMalloc, cudaMemcpy, etc. */ +/* #include cusparseSpMV */ +/* #endif */ #ifdef __cplusplus @@ -61,69 +61,69 @@ extern "C" { #define _cupdlp_realloc realloc #endif -// for cuda +/* for cuda */ #ifndef CUPDLP_CPU -// #define CUPDLP_FREE_VEC(x) \ -// { \ -// cudaFree(x); \ -// x = cupdlp_NULL; \ -// } - -// #define CUPDLP_COPY_VEC(dst, src, type, size) \ -// cudaMemcpy(dst, src, sizeof(type) * (size), cudaMemcpyDefault) - -// #define CUPDLP_INIT_VEC(var, size) \ -// { \ -// cusparseStatus_t status = \ -// cudaMalloc((void **)&var, (size) * sizeof(typeof(*var))); \ -// if (status != CUSPARSE_STATUS_SUCCESS) { \ -// printf("CUSPARSE API failed at line %d with error: %s (%d)\n", __LINE__, \ -// cusparseGetErrorString(status), status); \ -// goto exit_cleanup; \ -// } \ -// } -// #define CUPDLP_INIT_ZERO_VEC(var, size) \ -// { \ -// cusparseStatus_t status = \ -// cudaMalloc((void **)&var, (size) * sizeof(typeof(*var))); \ -// if (status != CUSPARSE_STATUS_SUCCESS) { \ -// printf("CUSPARSE API failed at line %d with error: %s (%d)\n", __LINE__, \ -// cusparseGetErrorString(status), status); \ -// goto exit_cleanup; \ -// } \ -// status = cudaMemset(var, 0, (size) * sizeof(typeof(*var))); \ -// if (status != CUSPARSE_STATUS_SUCCESS) { \ -// printf("CUSPARSE API failed at line %d with error: %s (%d)\n", __LINE__, \ -// cusparseGetErrorString(status), status); \ -// goto exit_cleanup; \ -// } \ -// } -// #define CUPDLP_ZERO_VEC(var, type, size) \ -// cudaMemset(var, 0, sizeof(type) * (size)) +/* #define CUPDLP_FREE_VEC(x) \ */ +/* { \ */ +/* cudaFree(x); \ */ +/* x = cupdlp_NULL; \ */ +/* } */ + +/* #define CUPDLP_COPY_VEC(dst, src, type, size) \ */ +/* cudaMemcpy(dst, src, sizeof(type) * (size), cudaMemcpyDefault) */ + +/* #define CUPDLP_INIT_VEC(var, size) \ */ +/* { \ */ +/* cusparseStatus_t status = \ */ +/* cudaMalloc((void **)&var, (size) * sizeof(typeof(*var))); \ */ +/* if (status != CUSPARSE_STATUS_SUCCESS) { \ */ +/* printf("CUSPARSE API failed at line %d with error: %s (%d)\n", __LINE__, \ */ +/* cusparseGetErrorString(status), status); \ */ +/* goto exit_cleanup; \ */ +/* } \ */ +/* } */ +/* #define CUPDLP_INIT_ZERO_VEC(var, size) \ */ +/* { \ */ +/* cusparseStatus_t status = \ */ +/* cudaMalloc((void **)&var, (size) * sizeof(typeof(*var))); \ */ +/* if (status != CUSPARSE_STATUS_SUCCESS) { \ */ +/* printf("CUSPARSE API failed at line %d with error: %s (%d)\n", __LINE__, \ */ +/* cusparseGetErrorString(status), status); \ */ +/* goto exit_cleanup; \ */ +/* } \ */ +/* status = cudaMemset(var, 0, (size) * sizeof(typeof(*var))); \ */ +/* if (status != CUSPARSE_STATUS_SUCCESS) { \ */ +/* printf("CUSPARSE API failed at line %d with error: %s (%d)\n", __LINE__, \ */ +/* cusparseGetErrorString(status), status); \ */ +/* goto exit_cleanup; \ */ +/* } \ */ +/* } */ +/* #define CUPDLP_ZERO_VEC(var, type, size) \ */ +/* cudaMemset(var, 0, sizeof(type) * (size)) */ #else #define CUPDLP_COPY_VEC(dst, src, type, size) \ memcpy(dst, src, sizeof(type) * (size)) - //CUPDLP_INIT_VEC is not used - // - //#define CUPDLP_INIT_VEC(var, size) \ -// { \ -// (var) = (typeof(var))malloc((size) * sizeof(typeof(*var))); \ -// if ((var) == cupdlp_NULL) { \ -// retcode = RETCODE_FAILED; \ -// goto exit_cleanup; \ -// } \ -// } - //#define CUPDLP_INIT_ZERO_VEC(var, size) \ -// { \ -// (var) = (typeof(var))calloc(size, sizeof(typeof(*var))); \ -// if ((var) == cupdlp_NULL) { \ -// retcode = RETCODE_FAILED; \ -// goto exit_cleanup; \ -// } \ -// } + /* CUPDLP_INIT_VEC is not used */ + +/* #define CUPDLP_INIT_VEC(var, size) \ */ + /* { \ */ + /* (var) = (typeof(var))malloc((size) * sizeof(typeof(*var))); \ */ + /* if ((var) == cupdlp_NULL) { \ */ + /* retcode = RETCODE_FAILED; \ */ + /* goto exit_cleanup; \ */ + /* } \ */ + /* } */ +/* #define CUPDLP_INIT_ZERO_VEC(var, size) \ */ + /* { \ */ + /* (var) = (typeof(var))calloc(size, sizeof(typeof(*var))); \ */ + /* if ((var) == cupdlp_NULL) { \ */ + /* retcode = RETCODE_FAILED; \ */ + /* goto exit_cleanup; \ */ + /* } \ */ + /* } */ #define CUPDLP_INIT_ZERO_DOUBLE_VEC(var, size) \ { \ (var) = (double*)calloc(size, sizeof(double)); \ @@ -160,14 +160,14 @@ extern "C" { #define cupdlp_zero(var, type, size) memset(var, 0, sizeof(type) * (size)) #define cupdlp_copy(dst, src, type, size) \ memcpy(dst, src, sizeof(type) * (size)) -//#define CUPDLP_INIT(var, size) \ -// { \ -// (var) = (typeof(var))malloc((size) * sizeof(typeof(*var))); \ -// if ((var) == cupdlp_NULL) { \ -// retcode = RETCODE_FAILED; \ -// goto exit_cleanup; \ -// } \ -// } +/* #define CUPDLP_INIT(var, size) \ */ +/* { \ */ +/* (var) = (typeof(var))malloc((size) * sizeof(typeof(*var))); \ */ +/* if ((var) == cupdlp_NULL) { \ */ +/* retcode = RETCODE_FAILED; \ */ +/* goto exit_cleanup; \ */ +/* } \ */ +/* } */ #define CUPDLP_INIT_DOUBLE(var, size) \ { \ (var) = (double*)malloc((size) * sizeof(double)); \ @@ -248,14 +248,14 @@ extern "C" { goto exit_cleanup; \ } \ } - //#define CUPDLP_INIT_ZERO(var, size) \ -// { \ -// (var) = (typeof(var))calloc(size, sizeof(typeof(*var))); \ -// if ((var) == cupdlp_NULL) { \ -// retcode = RETCODE_FAILED; \ -// goto exit_cleanup; \ -// } \ -// } +/* #define CUPDLP_INIT_ZERO(var, size) \ */ +/* { \ */ +/* (var) = (typeof(var))calloc(size, sizeof(typeof(*var))); \ */ +/* if ((var) == cupdlp_NULL) { \ */ +/* retcode = RETCODE_FAILED; \ */ +/* goto exit_cleanup; \ */ +/* } \ */ +/* } */ #define CUPDLP_INIT_ZERO_DOUBLE(var, size) \ { \ (var) = (double*)calloc(size, sizeof(double)); \ From e724f52d09fd7c8a3ecbdc4c81211fdc03e8950c Mon Sep 17 00:00:00 2001 From: Rohit Goswami Date: Sat, 4 May 2024 18:37:53 +0000 Subject: [PATCH 19/38] BLD: Reduce the silenced warnings BLD: Remove more filtered warnings --- meson.build | 5 ----- 1 file changed, 5 deletions(-) diff --git a/meson.build b/meson.build index a689b4ad5e..63f2360580 100644 --- a/meson.build +++ b/meson.build @@ -22,13 +22,8 @@ is_mingw = is_windows and cc.get_id() == 'gcc' # Conditional arguments _args += cppc.get_supported_arguments([ - '-Wno-return-type', - '-Wno-switch', - '-Wno-comment', '-Wno-unused-variable', '-Wno-unused-but-set-variable', - '-Wno-unused-const-variable', - '-Wno-unused-function', '-Wno-unused-label', ]) From 9407f71d13896480d5ccf35ab4bd80284a5448f4 Mon Sep 17 00:00:00 2001 From: Rohit Goswami Date: Sat, 4 May 2024 19:00:28 +0000 Subject: [PATCH 20/38] BLD: Rework to harmonize with CMake Also simplify, C wrapper is always built evidently --- meson_options.txt | 3 -- src/meson.build | 87 ++++++++++++++++++----------------------------- 2 files changed, 34 insertions(+), 56 deletions(-) diff --git a/meson_options.txt b/meson_options.txt index 9f763abd82..e92fb8d53b 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -13,9 +13,6 @@ option('with_fortran', option('with_csharp', type : 'boolean', value : false) -option('with_c', - type : 'boolean', - value : false) option('debug_sol', type: 'boolean', value: false) diff --git a/src/meson.build b/src/meson.build index 61dd94f9e7..2836c6f417 100644 --- a/src/meson.build +++ b/src/meson.build @@ -194,84 +194,86 @@ _ipx_srcs = [ _srcs = [ '../extern/filereaderlp/reader.cpp', + 'interfaces/highs_c_api.cpp', 'io/Filereader.cpp', - 'io/FilereaderLp.cpp', 'io/FilereaderEms.cpp', + 'io/FilereaderLp.cpp', 'io/FilereaderMps.cpp', - 'io/HighsIO.cpp', 'io/HMPSIO.cpp', 'io/HMpsFF.cpp', + 'io/HighsIO.cpp', 'io/LoadOptions.cpp', + 'ipm/IpxWrapper.cpp', 'lp_data/Highs.cpp', 'lp_data/HighsCallback.cpp', 'lp_data/HighsDebug.cpp', + 'lp_data/HighsDeprecated.cpp', 'lp_data/HighsInfo.cpp', 'lp_data/HighsInfoDebug.cpp', - 'lp_data/HighsDeprecated.cpp', 'lp_data/HighsInterface.cpp', 'lp_data/HighsLp.cpp', 'lp_data/HighsLpUtils.cpp', 'lp_data/HighsModelUtils.cpp', + 'lp_data/HighsOptions.cpp', 'lp_data/HighsRanging.cpp', 'lp_data/HighsSolution.cpp', 'lp_data/HighsSolutionDebug.cpp', 'lp_data/HighsSolve.cpp', 'lp_data/HighsStatus.cpp', - 'lp_data/HighsOptions.cpp', - 'mip/HighsMipSolver.cpp', - 'mip/HighsMipSolverData.cpp', + 'mip/HighsCliqueTable.cpp', + 'mip/HighsConflictPool.cpp', + 'mip/HighsCutGeneration.cpp', + 'mip/HighsCutPool.cpp', + 'mip/HighsDebugSol.cpp', 'mip/HighsDomain.cpp', 'mip/HighsDynamicRowMatrix.cpp', + 'mip/HighsGFkSolve.cpp', + 'mip/HighsImplications.cpp', + 'mip/HighsLpAggregator.cpp', 'mip/HighsLpRelaxation.cpp', - 'mip/HighsSeparation.cpp', - 'mip/HighsSeparator.cpp', - 'mip/HighsTableauSeparator.cpp', + 'mip/HighsMipSolver.cpp', + 'mip/HighsMipSolverData.cpp', 'mip/HighsModkSeparator.cpp', + 'mip/HighsNodeQueue.cpp', + 'mip/HighsObjectiveFunction.cpp', 'mip/HighsPathSeparator.cpp', - 'mip/HighsCutGeneration.cpp', - 'mip/HighsSearch.cpp', - 'mip/HighsConflictPool.cpp', - 'mip/HighsCutPool.cpp', - 'mip/HighsCliqueTable.cpp', - 'mip/HighsGFkSolve.cpp', - 'mip/HighsTransformedLp.cpp', - 'mip/HighsLpAggregator.cpp', - 'mip/HighsDebugSol.cpp', - 'mip/HighsImplications.cpp', 'mip/HighsPrimalHeuristics.cpp', 'mip/HighsPseudocost.cpp', 'mip/HighsRedcostFixing.cpp', - 'mip/HighsNodeQueue.cpp', - 'mip/HighsObjectiveFunction.cpp', + 'mip/HighsSearch.cpp', + 'mip/HighsSeparation.cpp', + 'mip/HighsSeparator.cpp', + 'mip/HighsTableauSeparator.cpp', + 'mip/HighsTransformedLp.cpp', 'model/HighsHessian.cpp', 'model/HighsHessianUtils.cpp', 'model/HighsModel.cpp', 'parallel/HighsTaskExecutor.cpp', + 'pdlp/CupdlpWrapper.cpp', + 'presolve/HPresolve.cpp', + 'presolve/HPresolveAnalysis.cpp', + 'presolve/HighsPostsolveStack.cpp', + 'presolve/HighsSymmetry.cpp', 'presolve/ICrash.cpp', 'presolve/ICrashUtil.cpp', 'presolve/ICrashX.cpp', - 'presolve/HighsPostsolveStack.cpp', - 'presolve/HighsSymmetry.cpp', - 'presolve/HPresolve.cpp', - 'presolve/HPresolveAnalysis.cpp', 'presolve/PresolveComponent.cpp', 'qpsolver/a_asm.cpp', 'qpsolver/a_quass.cpp', 'qpsolver/basis.cpp', + 'qpsolver/perturbation.cpp', 'qpsolver/quass.cpp', 'qpsolver/ratiotest.cpp', 'qpsolver/scaling.cpp', - 'qpsolver/perturbation.cpp', 'simplex/HEkk.cpp', 'simplex/HEkkControl.cpp', 'simplex/HEkkDebug.cpp', - 'simplex/HEkkPrimal.cpp', 'simplex/HEkkDual.cpp', + 'simplex/HEkkDualMulti.cpp', 'simplex/HEkkDualRHS.cpp', 'simplex/HEkkDualRow.cpp', - 'simplex/HEkkDualMulti.cpp', 'simplex/HEkkInterface.cpp', - 'simplex/HighsSimplexAnalysis.cpp', + 'simplex/HEkkPrimal.cpp', 'simplex/HSimplex.cpp', 'simplex/HSimplexDebug.cpp', 'simplex/HSimplexNla.cpp', @@ -279,6 +281,7 @@ _srcs = [ 'simplex/HSimplexNlaFreeze.cpp', 'simplex/HSimplexNlaProductForm.cpp', 'simplex/HSimplexReport.cpp', + 'simplex/HighsSimplexAnalysis.cpp', 'test/DevKkt.cpp', 'test/KktCh2.cpp', 'util/HFactor.cpp', @@ -286,6 +289,8 @@ _srcs = [ 'util/HFactorExtend.cpp', 'util/HFactorRefactor.cpp', 'util/HFactorUtils.cpp', + 'util/HSet.cpp', + 'util/HVectorBase.cpp', 'util/HighsHash.cpp', 'util/HighsLinearSumBounds.cpp', 'util/HighsMatrixPic.cpp', @@ -293,16 +298,12 @@ _srcs = [ 'util/HighsSort.cpp', 'util/HighsSparseMatrix.cpp', 'util/HighsUtils.cpp', - 'util/HSet.cpp', - 'util/HVectorBase.cpp', 'util/stringutil.cpp', ] highslib_srcs = [ highs_conf_file, _srcs, - 'ipm/IpxWrapper.cpp', - 'pdlp/CupdlpWrapper.cpp', _cupdlp_srcs, _basiclu_srcs, _ipx_srcs @@ -360,26 +361,6 @@ if get_option('with_csharp') install: true) endif -# C -if get_option('with_c') - add_languages('c', required: true) - if not is_windows - m_dep = cppc.find_library('m', required: false) - _deps += m_dep - endif - _c_src = [ - 'interfaces/highs_c_api.cpp', - ] - c_lib = library('HighsC', - _c_src, - dependencies: _deps, - cpp_args: _args, - link_with: highslib, - include_directories: _incdirs, - pic: true, - install: true) -endif - highs_dep = declare_dependency(link_with: highslib, dependencies: _deps, include_directories: _incdirs, From 324f5c194de8333b425c38e8d86bf2ddf2fa7593 Mon Sep 17 00:00:00 2001 From: Rohit Goswami Date: Sat, 4 May 2024 19:06:17 +0000 Subject: [PATCH 21/38] BLD: Rework and sort-lines in meson --- src/meson.build | 48 ++++++++++++++++++++++++------------------------ 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/src/meson.build b/src/meson.build index 2836c6f417..825bd27c0a 100644 --- a/src/meson.build +++ b/src/meson.build @@ -117,45 +117,45 @@ _incdirs += include_directories('.') # --------------------- Libraries _cupdlp_srcs = [ - 'pdlp/cupdlp/cupdlp_solver.c', - 'pdlp/cupdlp/cupdlp_scaling_cuda.c', - 'pdlp/cupdlp/cupdlp_restart.c', - 'pdlp/cupdlp/cupdlp_proj.c', - 'pdlp/cupdlp/cupdlp_linalg.c', 'pdlp/cupdlp/cupdlp_cs.c', - 'pdlp/cupdlp/cupdlp_utils.c', + 'pdlp/cupdlp/cupdlp_linalg.c', + 'pdlp/cupdlp/cupdlp_proj.c', + 'pdlp/cupdlp/cupdlp_restart.c', + 'pdlp/cupdlp/cupdlp_scaling_cuda.c', + 'pdlp/cupdlp/cupdlp_solver.c', 'pdlp/cupdlp/cupdlp_step.c', + 'pdlp/cupdlp/cupdlp_utils.c', ] _basiclu_srcs = [ 'ipm/basiclu/basiclu_factorize.c', - 'ipm/basiclu/basiclu_solve_dense.c', - 'ipm/basiclu/lu_build_factors.c', - 'ipm/basiclu/lu_factorize_bump.c', - 'ipm/basiclu/lu_initialize.c', - 'ipm/basiclu/lu_markowitz.c', - 'ipm/basiclu/lu_setup_bump.c', - 'ipm/basiclu/lu_solve_sparse.c', 'ipm/basiclu/basiclu_get_factors.c', + 'ipm/basiclu/basiclu_initialize.c', + 'ipm/basiclu/basiclu_object.c', + 'ipm/basiclu/basiclu_solve_dense.c', 'ipm/basiclu/basiclu_solve_for_update.c', + 'ipm/basiclu/basiclu_solve_sparse.c', + 'ipm/basiclu/basiclu_update.c', + 'ipm/basiclu/lu_build_factors.c', 'ipm/basiclu/lu_condest.c', + 'ipm/basiclu/lu_dfs.c', + 'ipm/basiclu/lu_factorize_bump.c', 'ipm/basiclu/lu_file.c', + 'ipm/basiclu/lu_garbage_perm.c', + 'ipm/basiclu/lu_initialize.c', 'ipm/basiclu/lu_internal.c', + 'ipm/basiclu/lu_markowitz.c', 'ipm/basiclu/lu_matrix_norm.c', - 'ipm/basiclu/lu_singletons.c', - 'ipm/basiclu/lu_solve_symbolic.c', - 'ipm/basiclu/lu_update.c', - 'ipm/basiclu/basiclu_initialize.c', - 'ipm/basiclu/basiclu_solve_sparse.c', 'ipm/basiclu/lu_pivot.c', - 'ipm/basiclu/lu_solve_dense.c', - 'ipm/basiclu/lu_solve_triangular.c', - 'ipm/basiclu/basiclu_object.c', - 'ipm/basiclu/basiclu_update.c', - 'ipm/basiclu/lu_dfs.c', - 'ipm/basiclu/lu_garbage_perm.c', 'ipm/basiclu/lu_residual_test.c', + 'ipm/basiclu/lu_setup_bump.c', + 'ipm/basiclu/lu_singletons.c', + 'ipm/basiclu/lu_solve_dense.c', 'ipm/basiclu/lu_solve_for_update.c', + 'ipm/basiclu/lu_solve_sparse.c', + 'ipm/basiclu/lu_solve_symbolic.c', + 'ipm/basiclu/lu_solve_triangular.c', + 'ipm/basiclu/lu_update.c', ] _ipx_srcs = [ From 749309bc74234c12f776d495e1b2ef61b264f1fa Mon Sep 17 00:00:00 2001 From: Rohit Goswami Date: Sun, 5 May 2024 12:30:48 +0000 Subject: [PATCH 22/38] BLD: Setup the newer python library in meson --- src/meson.build | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/src/meson.build b/src/meson.build index 825bd27c0a..066ab742e4 100644 --- a/src/meson.build +++ b/src/meson.build @@ -367,6 +367,7 @@ highs_dep = declare_dependency(link_with: highslib, ) if get_option('with_pybind11') + _deps += highs_dep py = import('python').find_installation(pure: false) pyb11_dep = [ @@ -375,6 +376,7 @@ if get_option('with_pybind11') py.dependency(), dependency('pybind11') ] + _deps += pyb11_dep highspy_cpp = files([ 'highs_bindings.cpp' @@ -384,9 +386,9 @@ if get_option('with_pybind11') ]) py.extension_module( - '_highs', + '_core', sources : highspy_cpp, - dependencies: [pyb11_dep, highs_dep], + dependencies: _deps, cpp_args: _args, install: true, subdir: 'highspy', @@ -396,11 +398,19 @@ if get_option('with_pybind11') py.extension_module( '_highs_options', sources : highsoptions_cpp, - dependencies: [pyb11_dep, highs_dep], + dependencies: _deps, cpp_args: _args, install: true, subdir: 'highspy', include_directories: _incdirs, ) + py.install_sources([ + 'highspy/__init__.py', + 'highspy/highs.py', + ], + pure: false, + subdir: 'highspy' + ) + endif From d8fef70f099ec51e68634c0feabc59d399497553 Mon Sep 17 00:00:00 2001 From: Rohit Goswami Date: Sun, 5 May 2024 12:31:03 +0000 Subject: [PATCH 23/38] MAINT: Switch back to meson-python --- pyproject.toml | 160 ++++--------------------------------------------- 1 file changed, 10 insertions(+), 150 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index e3173dfedf..f586a4d2be 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,8 +3,8 @@ "Bug Tracker" = "https://github.com/ERGO-Code/HiGHS/issues" [build-system] -requires = ["scikit-build-core>=0.3.3", "pybind11", "numpy"] -build-backend = "scikit_build_core.build" +requires = ["meson-python<0.14.0", "meson>=1.2.0"] +build-backend = "mesonpy" [project] name = "highspy" @@ -30,154 +30,14 @@ classifiers = [ [project.optional-dependencies] test = ["pytest", "numpy"] -[tool.scikit-build] -cmake.args = ["-DPYTHON_BUILD_SETUP=ON"] -wheel.expand-macos-universal-tags = true - -# A list of packages to auto-copy into the wheel. If this is not set, it will -# default to the first of ``src/``, ``python/``, or -# ```` if they exist. The prefix(s) will be stripped from the package -# name inside the wheel. -wheel.packages = ["src/highspy"] - -# Files to include in the SDist even if they are skipped by default. Supports -# gitignore syntax. -sdist.include = [ - "src/highspy/highs.py", - "tests/test_highspy.py", - "Version.txt", - "LICENSE", - "README.md", - "src/HConfig.h.in", - "src", - "external", - "cmake", -] - -sdist.exclude = [ - ".github", - ".gitattributes", - ".gitignore", - ".github", - "app", - "build", - "check", - "docs", - "subprojects", - ".coin-or", - "build_webdemo.sh", - ".clang-format", - "__setup.py", - "BUILD.bazel", - "meson*", - "MODS.md", - "WORKSPACE", -] - - -# # Verbose printout when building. -# cmake.verbose = false - -# # The build type to use when building the project. Valid options are: "Debug", -# # "Release", "RelWithDebInfo", "MinSizeRel", "", etc. -# cmake.build-type = "Release" - -# # The versions of Ninja to allow. If Ninja is not present on the system or does -# # not pass this specifier, it will be downloaded via PyPI if possible. An empty -# # string will disable this check. -# ninja.version = ">=1.5" - -# # If CMake is not present on the system or is older required, it will be -# # downloaded via PyPI if possible. An empty string will disable this check. -# ninja.make-fallback = true - -# # The logging level to display, "DEBUG", "INFO", "WARNING", and "ERROR" are -# # possible options. -# logging.level = "WARNING" - - -# # If set to True, try to build a reproducible distribution (Unix and Python 3.9+ -# # recommended). ``SOURCE_DATE_EPOCH`` will be used for timestamps, or a fixed -# # value if not set. -# sdist.reproducible = true - -# # If set to True, CMake will be run before building the SDist. -# sdist.cmake = false - -# # The Python tags. The default (empty string) will use the default Python -# # version. You can also set this to "cp37" to enable the CPython 3.7+ Stable ABI -# # / Limited API (only on CPython and if the version is sufficient, otherwise -# # this has no effect). Or you can set it to "py3" or "py2.py3" to ignore Python -# # ABI compatibility. The ABI tag is inferred from this tag. -# wheel.py-api = "" - -# # Fill out extra tags that are not required. This adds "x86_64" and "arm64" to -# # the list of platforms when "universal2" is used, which helps older Pip's -# # (before 21.0.1) find the correct wheel. -# # wheel.expand-macos-universal-tags = false - -# # The install directory for the wheel. This is relative to the platlib root. You -# # might set this to the package name. The original dir is still at -# # SKBUILD_PLATLIB_DIR (also SKBUILD_DATA_DIR, etc. are available). EXPERIMENTAL: -# # An absolute path will be one level higher than the platlib root, giving access -# # to "/platlib", "/data", "/headers", and "/scripts". -# # wheel.install-dir = "" - -# # A list of license files to include in the wheel. Supports glob patterns. -# wheel.license-files = ["LICEN[CS]E*", "COPYING*", "NOTICE*", "AUTHORS*"] - -# # If set to True (the default), CMake will be run before building the wheel. -# wheel.cmake = true - -# # Target the platlib or the purelib. If not set, the default is to target the -# # platlib if wheel.cmake is true, and the purelib otherwise. -# # wheel.platlib = "" - -# # A set of patterns to exclude from the wheel. This is additive to the SDist -# # exclude patterns. This applies to the final paths in the wheel, and can -# # exclude files from CMake output as well. Editable installs may not respect -# # this exclusion. -# wheel.exclude = [] - -# # The build tag to use for the wheel. If empty, no build tag is used. -# wheel.build-tag = "" - -# # If CMake is less than this value, backport a copy of FindPython. Set to 0 -# # disable this, or the empty string. -# backport.find-python = "3.26.1" - -# # Select the editable mode to use. Can be "redirect" (default) or "inplace". -# editable.mode = "redirect" - -# # Turn on verbose output for the editable mode rebuilds. -# editable.verbose = true - -# # Rebuild the project when the package is imported. The build-directory must be -# # set. -# editable.rebuild = false - -# # The components to install. If empty, all default components are installed. -# install.components = [] - -# # Whether to strip the binaries. True for scikit-build-core 0.5+. -# install.strip = false - -# # List dynamic metadata fields and hook locations in this table. -# metadata = {} - -# # Strictly check all config options. If False, warnings will be printed for -# # unknown options. If True, an error will be raised. -# strict-config = true - -# # Enable early previews of features not finalized yet. -# experimental = false - -# # If set, this will provide a method for backward compatibility. -# # minimum-version = "0.8" # current version - -# # The build directory. Defaults to a temporary directory, but can be set. -# # build-dir = "" - +[tool.meson-python.args] +setup = ['-Dwith_pybind11=True', + '-Dhighsint64=False', + '-Dwrap_mode=forcefallback', + # ^-- collects pybind11, see https://github.com/ERGO-Code/HiGHS/pull/1343#discussion_r1252446966 + ] +dist = ['--include-subprojects'] +install = ['--skip-subprojects'] [tool.pytest.ini_options] minversion = "6.0" From 8ef97cbd6b4aa0f11aa1924ee9a8f9909d59f216 Mon Sep 17 00:00:00 2001 From: Rohit Goswami Date: Sun, 5 May 2024 12:37:44 +0000 Subject: [PATCH 24/38] MAINT: Lint and cleanup excess enum exports --- src/highs_bindings.cpp | 52 ++++++++++++++++++------------------------ 1 file changed, 22 insertions(+), 30 deletions(-) diff --git a/src/highs_bindings.cpp b/src/highs_bindings.cpp index e7f9e49cac..5d747c1ec6 100644 --- a/src/highs_bindings.cpp +++ b/src/highs_bindings.cpp @@ -277,12 +277,13 @@ HighsStatus highs_changeColsIntegrality(Highs* h, HighsInt num_set_entries, // Same as deleteVars HighsStatus highs_deleteCols(Highs* h, HighsInt num_set_entries, - std::vector& indices) { + std::vector& indices) { return h->deleteCols(num_set_entries, indices.data()); } -HighsStatus highs_deleteRows(Highs* h, HighsInt num_set_entries, std::vector& indices) { - return h->deleteRows(num_set_entries, indices.data()); +HighsStatus highs_deleteRows(Highs* h, HighsInt num_set_entries, + std::vector& indices) { + return h->deleteRows(num_set_entries, indices.data()); } @@ -576,22 +577,9 @@ std::tuple highs_getRowByName(Highs* h, return std::make_tuple(status, row); } - PYBIND11_MODULE(_core, m) { - // enum classes - py::enum_(m, "ObjSense") - .value("kMinimize", ObjSense::kMinimize) - .value("kMaximize", ObjSense::kMaximize); - // // .export_values(); - py::enum_(m, "MatrixFormat") - .value("kColwise", MatrixFormat::kColwise) - .value("kRowwise", MatrixFormat::kRowwise) - .value("kRowwisePartitioned", MatrixFormat::kRowwisePartitioned); - // // .export_values(); - py::enum_(m, "HessianFormat") - .value("kTriangular", HessianFormat::kTriangular) - .value("kSquare", HessianFormat::kSquare); - // .export_values(); + // enumerations + // Older enums, need to have values exported py::enum_(m, "SolutionStatus") .value("kSolutionStatusNone", SolutionStatus::kSolutionStatusNone) .value("kSolutionStatusInfeasible", @@ -602,6 +590,17 @@ PYBIND11_MODULE(_core, m) { .value("kBasisValidityInvalid", BasisValidity::kBasisValidityInvalid) .value("kBasisValidityValid", BasisValidity::kBasisValidityValid) .export_values(); + // C++ enum classes do not need to have values exported + py::enum_(m, "ObjSense") + .value("kMinimize", ObjSense::kMinimize) + .value("kMaximize", ObjSense::kMaximize); + py::enum_(m, "MatrixFormat") + .value("kColwise", MatrixFormat::kColwise) + .value("kRowwise", MatrixFormat::kRowwise) + .value("kRowwisePartitioned", MatrixFormat::kRowwisePartitioned); + py::enum_(m, "HessianFormat") + .value("kTriangular", HessianFormat::kTriangular) + .value("kSquare", HessianFormat::kSquare); py::enum_(m, "HighsModelStatus") .value("kNotset", HighsModelStatus::kNotset) .value("kLoadError", HighsModelStatus::kLoadError) @@ -622,7 +621,6 @@ PYBIND11_MODULE(_core, m) { .value("kSolutionLimit", HighsModelStatus::kSolutionLimit) .value("kInterrupt", HighsModelStatus::kInterrupt) .value("kMemoryLimit", HighsModelStatus::kMemoryLimit); - // .export_values(); py::enum_(m, "HighsPresolveStatus") .value("kNotPresolved", HighsPresolveStatus::kNotPresolved) .value("kNotReduced", HighsPresolveStatus::kNotReduced) @@ -634,43 +632,36 @@ PYBIND11_MODULE(_core, m) { .value("kTimeout", HighsPresolveStatus::kTimeout) .value("kNullError", HighsPresolveStatus::kNullError) .value("kOptionsError", HighsPresolveStatus::kOptionsError); - // .export_values(); py::enum_(m, "HighsBasisStatus") .value("kLower", HighsBasisStatus::kLower) .value("kBasic", HighsBasisStatus::kBasic) .value("kUpper", HighsBasisStatus::kUpper) .value("kZero", HighsBasisStatus::kZero) .value("kNonbasic", HighsBasisStatus::kNonbasic); - // .export_values(); py::enum_(m, "HighsVarType") .value("kContinuous", HighsVarType::kContinuous) .value("kInteger", HighsVarType::kInteger) .value("kSemiContinuous", HighsVarType::kSemiContinuous) .value("kSemiInteger", HighsVarType::kSemiInteger); - // .export_values(); py::enum_(m, "HighsOptionType") .value("kBool", HighsOptionType::kBool) .value("kInt", HighsOptionType::kInt) .value("kDouble", HighsOptionType::kDouble) .value("kString", HighsOptionType::kString); - // .export_values(); py::enum_(m, "HighsInfoType") .value("kInt64", HighsInfoType::kInt64) .value("kInt", HighsInfoType::kInt) .value("kDouble", HighsInfoType::kDouble); - // .export_values(); py::enum_(m, "HighsStatus") .value("kError", HighsStatus::kError) .value("kOk", HighsStatus::kOk) .value("kWarning", HighsStatus::kWarning); - // .export_values(); py::enum_(m, "HighsLogType") .value("kInfo", HighsLogType::kInfo) .value("kDetailed", HighsLogType::kDetailed) .value("kVerbose", HighsLogType::kVerbose) .value("kWarning", HighsLogType::kWarning) .value("kError", HighsLogType::kError); - // .export_values(); // Classes py::class_(m, "HighsSparseMatrix") .def(py::init<>()) @@ -951,7 +942,7 @@ PYBIND11_MODULE(_core, m) { .def("changeColsBounds", &highs_changeColsBounds) .def("changeColsIntegrality", &highs_changeColsIntegrality) .def("deleteCols", &highs_deleteCols) - .def("deleteVars", &highs_deleteCols) // alias + .def("deleteVars", &highs_deleteCols) // alias .def("deleteRows", &highs_deleteRows) .def("setSolution", &highs_setSolution) .def("setSolution", &highs_setSparseSolution) @@ -1168,9 +1159,10 @@ PYBIND11_MODULE(_core, m) { HighsCallbackType::kCallbackMipImprovingSolution) .value("kCallbackMipLogging", HighsCallbackType::kCallbackMipLogging) .value("kCallbackMipInterrupt", HighsCallbackType::kCallbackMipInterrupt) - .value("kCallbackMipGetCutPool", HighsCallbackType::kCallbackMipGetCutPool) + .value("kCallbackMipGetCutPool", + HighsCallbackType::kCallbackMipGetCutPool) .value("kCallbackMipDefineLazyConstraints", - HighsCallbackType::kCallbackMipDefineLazyConstraints) + HighsCallbackType::kCallbackMipDefineLazyConstraints) .value("kCallbackMax", HighsCallbackType::kCallbackMax) .value("kNumCallbackType", HighsCallbackType::kNumCallbackType) .export_values(); @@ -1200,7 +1192,7 @@ PYBIND11_MODULE(_core, m) { return py::array(3, self.mip_solution); }, [](HighsCallbackDataOut& self, py::array_t new_mip_solution) { - self.mip_solution = new_mip_solution.mutable_data(); + self.mip_solution = new_mip_solution.mutable_data(); }); py::class_(callbacks, "HighsCallbackDataIn") .def(py::init<>()) From c51941fea4075bee8c3662d0e8c4c845dc5f854d Mon Sep 17 00:00:00 2001 From: Rohit Goswami Date: Sun, 5 May 2024 12:58:31 +0000 Subject: [PATCH 25/38] MAINT,BLD: Finish removing -Dwith_c for meson --- check/meson.build | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/check/meson.build b/check/meson.build index 1cf8e5663d..9c6c13abac 100644 --- a/check/meson.build +++ b/check/meson.build @@ -54,6 +54,7 @@ test_array = [ ['test_lpvalidation', 'TestLpValidation.cpp'], ['test_lpmodification', 'TestLpModification.cpp'], ['test_lporientation', 'TestLpOrientation.cpp'], + ['test_capi', 'TestCAPI.c'], ] foreach test : test_array test(test.get(0), @@ -70,11 +71,3 @@ foreach test : test_array timeout: 300, ) endforeach - -if get_option('with_c') - test('test_capi', - executable('capi_unit_tests', 'TestCAPI.c', - include_directories: _incdirs, - link_with : highslib, - )) -endif From fbf7d73af4f6425b2fcf2fa846ff4b288b2c2f2f Mon Sep 17 00:00:00 2001 From: Rohit Goswami Date: Sun, 5 May 2024 13:06:13 +0000 Subject: [PATCH 26/38] CI: Rework to have dependencies when building --- .github/workflows/build-python-package.yml | 54 +++++++++++----------- pyproject.toml | 2 +- 2 files changed, 29 insertions(+), 27 deletions(-) diff --git a/.github/workflows/build-python-package.yml b/.github/workflows/build-python-package.yml index 96e21715e0..eb0dc2abcc 100644 --- a/.github/workflows/build-python-package.yml +++ b/.github/workflows/build-python-package.yml @@ -14,18 +14,19 @@ jobs: - name: Build sdist run: | - python3 -m pip install build + python3 -m pip install build --user python3 -m build --sdist - name: Install sdist run: | ls dist + python3 -m pip install meson-python meson pybind11 ninja --user python3 -m pip install dist/*.tar.gz - name: Test highspy run: | - python3 -m pip install pytest - python3 -m pytest $GITHUB_WORKSPACE + python3 -m pip install pytest --user + python3 -m pytest $GITHUB_WORKSPACE/tests build_sdist_mac: runs-on: macos-latest @@ -34,18 +35,19 @@ jobs: - name: Build sdist run: | - python3 -m pip install build --break-system-packages + python3 -m pip install build --user --break-system-packages python3 -m build --sdist - name: Install sdist run: | ls dist - python3 -m pip install dist/*.tar.gz --break-system-packages + python3 -m pip install meson-python meson pybind11 ninja --user --break-system-packages + python3 -m pip install dist/*.tar.gz --user --break-system-packages - name: Test highspy run: | - python3 -m pip install pytest --break-system-packages - python3 -m pytest $GITHUB_WORKSPACE + python3 -m pip install pytest --user --break-system-packages + python3 -m pytest $GITHUB_WORKSPACE/tests build_sdist_win: runs-on: windows-2019 @@ -59,19 +61,20 @@ jobs: - name: Build sdist run: | - python -m pip install build + python -m pip install build --user python -m build --sdist - name: Install sdist run: | + python3 -m pip install meson-python meson pybind11 ninja --user $item = Get-ChildItem dist - python -m pip install "$item" + python -m pip install "$item" --user python -c "import highspy; print(dir(highspy))" - name: Test highspy run: | - python -m pip install pytest - python -m pytest + python -m pip install pytest --user + python -m pytest tests build_wheel_linux: # ubuntu 22 has a latest version of cmake, but setup-python @@ -103,7 +106,7 @@ jobs: - name: Test highspy run: | python3 -m pip install pytest - python3 -m pytest $GITHUB_WORKSPACE + python3 -m pytest $GITHUB_WORKSPACE/tests # macos 12 is Intel @@ -122,20 +125,20 @@ jobs: - name: Build wheel run: | - python3 -m pip install cibuildwheel + python3 -m pip install cibuildwheel --break-system-packages python3 -m cibuildwheel --only cp311-macosx_x86_64 $GITHUB_WORKSPACE - name: Install wheel run: | ls wheelhouse python3 --version - python3 -m pip install wheelhouse/*.whl + python3 -m pip install wheelhouse/*.whl --break-system-packages python3 -c "import highspy; print(dir(highspy))" - name: Test highspy run: | - python3 -m pip install pytest - python3 -m pytest $GITHUB_WORKSPACE + python3 -m pip install pytest --break-system-packages + python3 -m pytest $GITHUB_WORKSPACE/tests # macos 13 is Intel build_wheel_macos_13: @@ -153,20 +156,20 @@ jobs: - name: Build wheel run: | - python3 -m pip install cibuildwheel + python3 -m pip install cibuildwheel --break-system-packages python3 -m cibuildwheel --only cp311-macosx_x86_64 $GITHUB_WORKSPACE - name: Install wheel run: | ls wheelhouse python3 --version - python3 -m pip install wheelhouse/*.whl + python3 -m pip install wheelhouse/*.whl --break-system-packages python3 -c "import highspy; print(dir(highspy))" - name: Test highspy run: | - python3 -m pip install pytest - python3 -m pytest $GITHUB_WORKSPACE + python3 -m pip install pytest --break-system-packages + python3 -m pytest $GITHUB_WORKSPACE/tests # macos 14 is M1 (beta) build_wheel_macos_14: @@ -184,20 +187,20 @@ jobs: - name: Build wheel run: | - python3 -m pip install cibuildwheel + python3 -m pip install cibuildwheel --break-system-packages python3 -m cibuildwheel --only cp311-macosx_arm64 $GITHUB_WORKSPACE - name: Install wheel run: | ls wheelhouse python3 --version - python3 -m pip install wheelhouse/*.whl + python3 -m pip install wheelhouse/*.whl --break-system-packages python3 -c "import highspy; print(dir(highspy))" - name: Test highspy run: | - python3 -m pip install pytest - python3 -m pytest $GITHUB_WORKSPACE + python3 -m pip install pytest --break-system-packages + python3 -m pytest $GITHUB_WORKSPACE/tests build_wheel_windows: runs-on: windows-2019 @@ -224,5 +227,4 @@ jobs: - name: Test highspy run: | python -m pip install pytest - python -m pytest - \ No newline at end of file + python -m pytest diff --git a/pyproject.toml b/pyproject.toml index f586a4d2be..0c532603cb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,7 +3,7 @@ "Bug Tracker" = "https://github.com/ERGO-Code/HiGHS/issues" [build-system] -requires = ["meson-python<0.14.0", "meson>=1.2.0"] +requires = ["meson-python<0.14.0", "meson>=1.2.0", "pybind11", "ninja"] build-backend = "mesonpy" [project] From 0a4ed1e5c167a5dade9fdce555eeae31270fdd2c Mon Sep 17 00:00:00 2001 From: Rohit Goswami Date: Sun, 5 May 2024 13:21:27 +0000 Subject: [PATCH 27/38] BLD: Add pack cibuildwheel config --- pyproject.toml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 0c532603cb..9550208125 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -56,3 +56,21 @@ test-command = "pytest {project}/tests" test-extras = ["test", "numpy"] test-skip = ["*universal2:arm64"] build-verbosity = 1 + +[tool.cibuildwheel.linux] +manylinux-x86_64-image = "manylinux2014" +manylinux-i686-image = "manylinux2014" +repair-wheel-command = "auditwheel repair -w {dest_dir} {wheel}" + +[tool.cibuildwheel.macos] +archs = ["x86_64 arm64"] +environment = { RUNNER_OS="macOS" } +repair-wheel-command = [ + "delocate-listdeps {wheel}", + "delocate-wheel --require-archs {delocate_archs} -w {dest_dir} {wheel}", +] + +[tool.cibuildwheel.windows] +# Use delvewheel on windows, and install the project so delvewheel can find it +before-build = "pip install delvewheel meson ninja && meson setup bbdir && meson install -C bbdir" +repair-wheel-command = "delvewheel repair --add-path c:/bin;c:/lib;c:/bin/src;c:/lib/src;D:/a/HiGHS/HiGHS/bbdir/src/ -w {dest_dir} {wheel}" From 56a4335acbcc8d0ae01b0fb8ecdd5d142e451747 Mon Sep 17 00:00:00 2001 From: Rohit Goswami Date: Sun, 5 May 2024 13:32:41 +0000 Subject: [PATCH 28/38] BLD: Link with highs explicitly --- src/meson.build | 1 + 1 file changed, 1 insertion(+) diff --git a/src/meson.build b/src/meson.build index 066ab742e4..6b0a9e1d72 100644 --- a/src/meson.build +++ b/src/meson.build @@ -390,6 +390,7 @@ if get_option('with_pybind11') sources : highspy_cpp, dependencies: _deps, cpp_args: _args, + link_with: highslib, install: true, subdir: 'highspy', include_directories: _incdirs, From f5c14c5be3b28015a93c1d44b85335c57a253605 Mon Sep 17 00:00:00 2001 From: Rohit Goswami Date: Sun, 5 May 2024 13:32:50 +0000 Subject: [PATCH 29/38] CI: Test only highs/tests/ --- .github/workflows/build-python-package.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-python-package.yml b/.github/workflows/build-python-package.yml index eb0dc2abcc..c408458a13 100644 --- a/.github/workflows/build-python-package.yml +++ b/.github/workflows/build-python-package.yml @@ -227,4 +227,4 @@ jobs: - name: Test highspy run: | python -m pip install pytest - python -m pytest + python -m pytest tests From 62ab499db02427ec6960c7c39ea6a1553c29f698 Mon Sep 17 00:00:00 2001 From: Rohit Goswami Date: Sun, 5 May 2024 14:12:13 +0000 Subject: [PATCH 30/38] BLD: Fix C_API test Doesn't use Catch2 --- check/meson.build | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/check/meson.build b/check/meson.build index 9c6c13abac..16dd5b41cf 100644 --- a/check/meson.build +++ b/check/meson.build @@ -54,7 +54,6 @@ test_array = [ ['test_lpvalidation', 'TestLpValidation.cpp'], ['test_lpmodification', 'TestLpModification.cpp'], ['test_lporientation', 'TestLpOrientation.cpp'], - ['test_capi', 'TestCAPI.c'], ] foreach test : test_array test(test.get(0), @@ -71,3 +70,11 @@ foreach test : test_array timeout: 300, ) endforeach + +test('test_capi', + executable('capi_unit_tests', + sources: ['TestCAPI.c', highs_conf_file], + include_directories: _incdirs, + link_with : highslib, + ) + ) From 251590a2ccefe6c4b9522d8459c93a6291d91576 Mon Sep 17 00:00:00 2001 From: Rohit Goswami Date: Sun, 19 May 2024 18:11:59 +0000 Subject: [PATCH 31/38] MAINT: Try to rebuild correctly on WIN --- pyproject.toml | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 9550208125..2f7c45b887 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -32,7 +32,7 @@ test = ["pytest", "numpy"] [tool.meson-python.args] setup = ['-Dwith_pybind11=True', - '-Dhighsint64=False', + '-Dhighsint64=True', '-Dwrap_mode=forcefallback', # ^-- collects pybind11, see https://github.com/ERGO-Code/HiGHS/pull/1343#discussion_r1252446966 ] @@ -71,6 +71,5 @@ repair-wheel-command = [ ] [tool.cibuildwheel.windows] -# Use delvewheel on windows, and install the project so delvewheel can find it -before-build = "pip install delvewheel meson ninja && meson setup bbdir && meson install -C bbdir" -repair-wheel-command = "delvewheel repair --add-path c:/bin;c:/lib;c:/bin/src;c:/lib/src;D:/a/HiGHS/HiGHS/bbdir/src/ -w {dest_dir} {wheel}" +before-build = "pip install delvewheel" +repair-wheel-command = "delvewheel repair -w {dest_dir} {wheel}" \ No newline at end of file From c01795e82a1a35f7851455b491e781d6cf5ec9b6 Mon Sep 17 00:00:00 2001 From: Rohit Goswami Date: Sun, 19 May 2024 18:28:46 +0000 Subject: [PATCH 32/38] MAINT: Remove unused build date --- src/meson.build | 27 --------------------------- 1 file changed, 27 deletions(-) diff --git a/src/meson.build b/src/meson.build index 6b0a9e1d72..2c9fa9aa31 100644 --- a/src/meson.build +++ b/src/meson.build @@ -22,33 +22,6 @@ if host_machine.cpu_family() == 'x86_64' else conf_data.set('HIGHSINT64', false) endif -# Date -# today_str = 'unknown' -# if (not meson.is_subproject()) -# python_getdate = ''' -# import datetime -# import os -# import time - -# (ig) -# Deprecate compilation date - -# build_date = datetime.datetime.utcfromtimestamp( -# int(os.environ.get('SOURCE_DATE_EPOCH', time.time())) -# ) -# build_date_str = build_date.strftime('%Y-%m-%d') -# print(build_date_str) -# ''' -# # Note: this is not guaranteed to work on Windows. This should be removed -# # (as proposed in gh-1735), and otherwise moved into a .py script with -# # a `#!/usr/bin/env python3` shebang, and `python3 -c` dropped. -# today_cmd = run_command('python3', '-c', -# python_getdate, -# check: false) -# today_str = today_cmd.stdout().strip() -# endif -# conf_data.set_quoted('HIGHS_COMPILATION_DATE', -# today_str) conf_data.set_quoted('HIGHS_COMPILATION_DATE', 'deprecated') From a0390a50b87c1bd41e5b2d690469417007d0ed91 Mon Sep 17 00:00:00 2001 From: Rohit Goswami Date: Sun, 19 May 2024 19:30:58 +0000 Subject: [PATCH 33/38] BLD,TMP: Try windows again --- .github/workflows/test-python-win.yml | 32 ++++++++++++--- meson.build | 8 ++-- pyproject.toml | 13 +++++-- scripts/post_install_win.bat | 10 +++++ src/HConfig.h.meson.in | 1 + src/highspy/__init__.py | 2 +- src/highspy/highs.py | 2 +- src/meson.build | 56 +++++++++++++++++++++------ 8 files changed, 99 insertions(+), 25 deletions(-) create mode 100644 scripts/post_install_win.bat diff --git a/.github/workflows/test-python-win.yml b/.github/workflows/test-python-win.yml index 3b2f141b55..14bb8e147f 100644 --- a/.github/workflows/test-python-win.yml +++ b/.github/workflows/test-python-win.yml @@ -18,12 +18,34 @@ jobs: python-version: ${{ matrix.python }} - name: Install build dependencies - run: python -m pip install numpy setuptools wheel pytest - - - name: Test python install + run: python -m pip install numpy setuptools wheel pytest meson ninja delvewheel build + + - name: Build project run: | - python -m pip install . - pytest + meson setup bbdir --prefix=${{ github.workspace }}/local_install + meson compile -C bbdir + + - name: Install project to custom directory + run: meson install -C bbdir + + - name: Build Python package + run: python -m build + + - name: Repair the wheel + shell: pwsh + run: | + $installedPath = "${{ github.workspace }}/local_install" + $wheels = Get-ChildItem -Path dist -Filter *.whl + foreach ($wheel in $wheels) { + delvewheel repair $wheel.FullName --add-path "$installedPath/bin;$installedPath/lib" -w repaired_wheels + } + + - name: Install the repaired wheel + run: | + $wheels = Get-ChildItem -Path repaired_wheels -Filter *.whl + foreach ($wheel in $wheels){ + pip install $wheel.FullName + } - name: Test Python Examples run: | diff --git a/meson.build b/meson.build index 63f2360580..167b44f46d 100644 --- a/meson.build +++ b/meson.build @@ -68,9 +68,6 @@ if is_mingw endif # --------------------- Dependencies -threads_dep = dependency('threads', required: false) -_deps += threads_dep - # Determine whether it is necessary to link libatomic. This could be the case # e.g. on 32-bit platforms when atomic operations are used on 64-bit types. # The check is copied from SciPy which in turn came from @@ -110,6 +107,11 @@ endif _deps += atomic_dep +threads_dep = null_dep +if not is_windows + threads_dep = dependency('threads', required: false) +endif +_deps += threads_dep # Optional zlib_dep = dependency('zlib', required: get_option('use_zlib')) diff --git a/pyproject.toml b/pyproject.toml index 2f7c45b887..56d9cb9fe5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,7 +3,7 @@ "Bug Tracker" = "https://github.com/ERGO-Code/HiGHS/issues" [build-system] -requires = ["meson-python<0.14.0", "meson>=1.2.0", "pybind11", "ninja"] +requires = ["meson-python>=0.16.0", "meson>=1.2.0", "pybind11", "ninja"] build-backend = "mesonpy" [project] @@ -32,7 +32,7 @@ test = ["pytest", "numpy"] [tool.meson-python.args] setup = ['-Dwith_pybind11=True', - '-Dhighsint64=True', + '-Dhighsint64=False', '-Dwrap_mode=forcefallback', # ^-- collects pybind11, see https://github.com/ERGO-Code/HiGHS/pull/1343#discussion_r1252446966 ] @@ -70,6 +70,11 @@ repair-wheel-command = [ "delocate-wheel --require-archs {delocate_archs} -w {dest_dir} {wheel}", ] +#[tool.cibuildwheel.windows] +#before-build = "pip install delvewheel" +#repair-wheel-command = "delvewheel repair -w {dest_dir} {wheel}" + [tool.cibuildwheel.windows] -before-build = "pip install delvewheel" -repair-wheel-command = "delvewheel repair -w {dest_dir} {wheel}" \ No newline at end of file +# Use delvewheel on windows, and install the project so delvewheel can find it +before-build = "pip install delvewheel meson ninja && meson setup bbdir && meson install -C bbdir" +repair-wheel-command = "delvewheel repair --add-path c:/bin;c:/lib;c:/bin/src;c:/lib/src;D:/a/HiGHS/HiGHS/bbdir/src/ -w {dest_dir} {wheel}" diff --git a/scripts/post_install_win.bat b/scripts/post_install_win.bat new file mode 100644 index 0000000000..35ae5c49dd --- /dev/null +++ b/scripts/post_install_win.bat @@ -0,0 +1,10 @@ +pip install delvewheel meson ninja + +meson setup bbdir +meson compile -C bbdir + +REM Repair the wheel using delvewheel +set destDir=%1 +set wheel=%2 + +delvewheel repair --add-path c:/bin;c:/lib;c:/bin/src;c:/lib/src;D:/a/HiGHS/HiGHS/bbdir/src/ -w %destDir% %wheel% diff --git a/src/HConfig.h.meson.in b/src/HConfig.h.meson.in index 7b2fb3318c..eff4bc810c 100644 --- a/src/HConfig.h.meson.in +++ b/src/HConfig.h.meson.in @@ -8,6 +8,7 @@ #mesondefine HIGHS_HAVE_MM_PAUSE #mesondefine HIGHS_HAVE_BUILTIN_CLZ #mesondefine HIGHS_HAVE_BITSCAN_REVERSE +#mesondefine HIGHS_NO_DEFAULT_THREADS #define HIGHS_GITHASH "_HIGHS_GITHASH_" #define HIGHS_VERSION_MAJOR @HIGHS_VERSION_MAJOR@ diff --git a/src/highspy/__init__.py b/src/highspy/__init__.py index 3638f094c2..78005667b7 100644 --- a/src/highspy/__init__.py +++ b/src/highspy/__init__.py @@ -1,6 +1,6 @@ # from __future__ import annotations -from ._core import \ +from highspy._core import \ ObjSense, \ MatrixFormat, \ HessianFormat, \ diff --git a/src/highspy/highs.py b/src/highspy/highs.py index 9abf20ea26..5576abfc91 100644 --- a/src/highspy/highs.py +++ b/src/highspy/highs.py @@ -1,4 +1,4 @@ -from ._core import ( +from highspy._core import ( # enum classes ObjSense, MatrixFormat, diff --git a/src/meson.build b/src/meson.build index 2c9fa9aa31..e7ebe859ee 100644 --- a/src/meson.build +++ b/src/meson.build @@ -10,12 +10,22 @@ conf_data.set('HIGHS_VERSION_PATCH', meson.project_version().split('.')[2]) conf_data.set('ZLIB_FOUND', zlib_dep.found()) -# Is the use of the following two lines the cause of the meson build -# failure? + +if is_windows +# There is a bug with threading on windows, see CMakeList.txt:307 +# Also https://github.com/ERGO-Code/HiGHS/issues/1382#issuecomment-1670967735 +# Generally, no point using threading with HiGHs at this point (May 2024) + conf_data.set('HIGHS_NO_DEFAULT_THREADS', + true) +else + conf_data.set('HIGHS_NO_DEFAULT_THREADS', + threads_dep.found()) +endif +# This dependency hasn't been setup.. #conf_data.set('CUPDLP_CPU', # cupdlp_cpu.found()) # 64 bit numbers -if host_machine.cpu_family() == 'x86_64' +if host_machine.cpu_family() == 'x86_64' or host_machine.cpu_family() == 'amd64' # Get user's option, if it's not provided, enable highsint64 by default on x86_64 highsint64_opt = get_option('highsint64') conf_data.set('HIGHSINT64', highsint64_opt) @@ -290,6 +300,8 @@ if get_option('default_library') == 'static' symbol_visibility = 'inlineshidden' endif + +if not get_option('with_pybind11') highslib = library('highs', highslib_srcs, dependencies: _deps, @@ -299,6 +311,12 @@ highslib = library('highs', gnu_symbol_visibility: symbol_visibility, pic: true, install: true) + +highs_dep = declare_dependency(link_with: highslib, + dependencies: _deps, + include_directories: _incdirs, + ) +endif # --------------- Interfaces @@ -334,15 +352,27 @@ if get_option('with_csharp') install: true) endif -highs_dep = declare_dependency(link_with: highslib, - dependencies: _deps, - include_directories: _incdirs, - ) if get_option('with_pybind11') - _deps += highs_dep py = import('python').find_installation(pure: false) + highslib = library('highs', + highslib_srcs, + dependencies: _deps, + cpp_args: _args, + c_args: _args, + include_directories: _incdirs, + gnu_symbol_visibility: symbol_visibility, + pic: true, + install: true, + install_dir: py.get_install_dir() / 'highspy') + +highs_dep = declare_dependency(link_with: highslib, + dependencies: _deps, + include_directories: _incdirs, + ) + _deps += highs_dep + pyb11_dep = [ # py_dep is auto-added for Python >= 3.9, so it can be dropped here when # that is the minimum supported Python version @@ -360,10 +390,9 @@ if get_option('with_pybind11') py.extension_module( '_core', - sources : highspy_cpp, + sources : highspy_cpp + highs_conf_file, dependencies: _deps, cpp_args: _args, - link_with: highslib, install: true, subdir: 'highspy', include_directories: _incdirs, @@ -371,7 +400,7 @@ if get_option('with_pybind11') py.extension_module( '_highs_options', - sources : highsoptions_cpp, + sources : highsoptions_cpp + highs_conf_file, dependencies: _deps, cpp_args: _args, install: true, @@ -387,4 +416,9 @@ if get_option('with_pybind11') subdir: 'highspy' ) +if is_windows + rootdir = meson.source_root() + meson.add_install_script(f'@rootdir@' / 'scripts' / 'post_install_win.bat', '{dest_dir}', '{wheel}') +endif + endif From 12ba85a68f0973ceab52785e20412dcf88294938 Mon Sep 17 00:00:00 2001 From: Rohit Goswami Date: Sun, 14 Jul 2024 22:46:14 +0000 Subject: [PATCH 34/38] BLD: Rework meson slightly --- meson.build | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/meson.build b/meson.build index 167b44f46d..3c02480b0a 100644 --- a/meson.build +++ b/meson.build @@ -28,7 +28,7 @@ _args += cppc.get_supported_arguments([ ]) if cppc.get_id() == 'msvc' - add_project_arguments( +_args += cppc.get_supported_arguments([ '/wd4018', # Disable warning: 'expression' : signed/unsigned mismatch '/wd4061', # Disable warning: enumerator 'identifier' in switch of enum 'enumeration' is not explicitly handled by a case label '/wd4100', # Disable warning: 'identifier' : unreferenced formal parameter @@ -48,9 +48,8 @@ if cppc.get_id() == 'msvc' '/wd4514', # Disable warning: 'function' : unreferenced inline function has been removed '/wd4701', # Disable warning: potentially uninitialized local variable 'name' used '/wd4820', # Disable warning: 'bytes' bytes padding added after construct 'member_name' - language: 'cpp', - ) - _args += '-D_CRT_SECURE_NO_WARNINGS' + ]) +_args += '-D_CRT_SECURE_NO_WARNINGS' endif cpu_family = host_machine.cpu_family() @@ -64,7 +63,7 @@ endif if is_mingw # For mingw-w64, don't use LTO - add_project_arguments('-fno-use-linker-plugin', language: ['c', 'cpp']) + add_project_arguments(cppc.get_supported_arguments('-fno-use-linker-plugin'), language: ['c', 'cpp']) endif # --------------------- Dependencies From 7ed8ca2b2a988e350630425827801adf171edc3c Mon Sep 17 00:00:00 2001 From: Rohit Goswami Date: Sun, 14 Jul 2024 22:46:32 +0000 Subject: [PATCH 35/38] BLD: Revert back to scikit-build --- pyproject.toml | 182 ++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 150 insertions(+), 32 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 56d9cb9fe5..e3173dfedf 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,8 +3,8 @@ "Bug Tracker" = "https://github.com/ERGO-Code/HiGHS/issues" [build-system] -requires = ["meson-python>=0.16.0", "meson>=1.2.0", "pybind11", "ninja"] -build-backend = "mesonpy" +requires = ["scikit-build-core>=0.3.3", "pybind11", "numpy"] +build-backend = "scikit_build_core.build" [project] name = "highspy" @@ -30,14 +30,154 @@ classifiers = [ [project.optional-dependencies] test = ["pytest", "numpy"] -[tool.meson-python.args] -setup = ['-Dwith_pybind11=True', - '-Dhighsint64=False', - '-Dwrap_mode=forcefallback', - # ^-- collects pybind11, see https://github.com/ERGO-Code/HiGHS/pull/1343#discussion_r1252446966 - ] -dist = ['--include-subprojects'] -install = ['--skip-subprojects'] +[tool.scikit-build] +cmake.args = ["-DPYTHON_BUILD_SETUP=ON"] +wheel.expand-macos-universal-tags = true + +# A list of packages to auto-copy into the wheel. If this is not set, it will +# default to the first of ``src/``, ``python/``, or +# ```` if they exist. The prefix(s) will be stripped from the package +# name inside the wheel. +wheel.packages = ["src/highspy"] + +# Files to include in the SDist even if they are skipped by default. Supports +# gitignore syntax. +sdist.include = [ + "src/highspy/highs.py", + "tests/test_highspy.py", + "Version.txt", + "LICENSE", + "README.md", + "src/HConfig.h.in", + "src", + "external", + "cmake", +] + +sdist.exclude = [ + ".github", + ".gitattributes", + ".gitignore", + ".github", + "app", + "build", + "check", + "docs", + "subprojects", + ".coin-or", + "build_webdemo.sh", + ".clang-format", + "__setup.py", + "BUILD.bazel", + "meson*", + "MODS.md", + "WORKSPACE", +] + + +# # Verbose printout when building. +# cmake.verbose = false + +# # The build type to use when building the project. Valid options are: "Debug", +# # "Release", "RelWithDebInfo", "MinSizeRel", "", etc. +# cmake.build-type = "Release" + +# # The versions of Ninja to allow. If Ninja is not present on the system or does +# # not pass this specifier, it will be downloaded via PyPI if possible. An empty +# # string will disable this check. +# ninja.version = ">=1.5" + +# # If CMake is not present on the system or is older required, it will be +# # downloaded via PyPI if possible. An empty string will disable this check. +# ninja.make-fallback = true + +# # The logging level to display, "DEBUG", "INFO", "WARNING", and "ERROR" are +# # possible options. +# logging.level = "WARNING" + + +# # If set to True, try to build a reproducible distribution (Unix and Python 3.9+ +# # recommended). ``SOURCE_DATE_EPOCH`` will be used for timestamps, or a fixed +# # value if not set. +# sdist.reproducible = true + +# # If set to True, CMake will be run before building the SDist. +# sdist.cmake = false + +# # The Python tags. The default (empty string) will use the default Python +# # version. You can also set this to "cp37" to enable the CPython 3.7+ Stable ABI +# # / Limited API (only on CPython and if the version is sufficient, otherwise +# # this has no effect). Or you can set it to "py3" or "py2.py3" to ignore Python +# # ABI compatibility. The ABI tag is inferred from this tag. +# wheel.py-api = "" + +# # Fill out extra tags that are not required. This adds "x86_64" and "arm64" to +# # the list of platforms when "universal2" is used, which helps older Pip's +# # (before 21.0.1) find the correct wheel. +# # wheel.expand-macos-universal-tags = false + +# # The install directory for the wheel. This is relative to the platlib root. You +# # might set this to the package name. The original dir is still at +# # SKBUILD_PLATLIB_DIR (also SKBUILD_DATA_DIR, etc. are available). EXPERIMENTAL: +# # An absolute path will be one level higher than the platlib root, giving access +# # to "/platlib", "/data", "/headers", and "/scripts". +# # wheel.install-dir = "" + +# # A list of license files to include in the wheel. Supports glob patterns. +# wheel.license-files = ["LICEN[CS]E*", "COPYING*", "NOTICE*", "AUTHORS*"] + +# # If set to True (the default), CMake will be run before building the wheel. +# wheel.cmake = true + +# # Target the platlib or the purelib. If not set, the default is to target the +# # platlib if wheel.cmake is true, and the purelib otherwise. +# # wheel.platlib = "" + +# # A set of patterns to exclude from the wheel. This is additive to the SDist +# # exclude patterns. This applies to the final paths in the wheel, and can +# # exclude files from CMake output as well. Editable installs may not respect +# # this exclusion. +# wheel.exclude = [] + +# # The build tag to use for the wheel. If empty, no build tag is used. +# wheel.build-tag = "" + +# # If CMake is less than this value, backport a copy of FindPython. Set to 0 +# # disable this, or the empty string. +# backport.find-python = "3.26.1" + +# # Select the editable mode to use. Can be "redirect" (default) or "inplace". +# editable.mode = "redirect" + +# # Turn on verbose output for the editable mode rebuilds. +# editable.verbose = true + +# # Rebuild the project when the package is imported. The build-directory must be +# # set. +# editable.rebuild = false + +# # The components to install. If empty, all default components are installed. +# install.components = [] + +# # Whether to strip the binaries. True for scikit-build-core 0.5+. +# install.strip = false + +# # List dynamic metadata fields and hook locations in this table. +# metadata = {} + +# # Strictly check all config options. If False, warnings will be printed for +# # unknown options. If True, an error will be raised. +# strict-config = true + +# # Enable early previews of features not finalized yet. +# experimental = false + +# # If set, this will provide a method for backward compatibility. +# # minimum-version = "0.8" # current version + +# # The build directory. Defaults to a temporary directory, but can be set. +# # build-dir = "" + [tool.pytest.ini_options] minversion = "6.0" @@ -56,25 +196,3 @@ test-command = "pytest {project}/tests" test-extras = ["test", "numpy"] test-skip = ["*universal2:arm64"] build-verbosity = 1 - -[tool.cibuildwheel.linux] -manylinux-x86_64-image = "manylinux2014" -manylinux-i686-image = "manylinux2014" -repair-wheel-command = "auditwheel repair -w {dest_dir} {wheel}" - -[tool.cibuildwheel.macos] -archs = ["x86_64 arm64"] -environment = { RUNNER_OS="macOS" } -repair-wheel-command = [ - "delocate-listdeps {wheel}", - "delocate-wheel --require-archs {delocate_archs} -w {dest_dir} {wheel}", -] - -#[tool.cibuildwheel.windows] -#before-build = "pip install delvewheel" -#repair-wheel-command = "delvewheel repair -w {dest_dir} {wheel}" - -[tool.cibuildwheel.windows] -# Use delvewheel on windows, and install the project so delvewheel can find it -before-build = "pip install delvewheel meson ninja && meson setup bbdir && meson install -C bbdir" -repair-wheel-command = "delvewheel repair --add-path c:/bin;c:/lib;c:/bin/src;c:/lib/src;D:/a/HiGHS/HiGHS/bbdir/src/ -w {dest_dir} {wheel}" From 1ca67197a42f10bdeb522cf1f027200a1545b93b Mon Sep 17 00:00:00 2001 From: Rohit Goswami Date: Sat, 20 Jul 2024 18:47:16 +0000 Subject: [PATCH 36/38] BLD: Export python sources via variable --- src/meson.build | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/meson.build b/src/meson.build index e7ebe859ee..7788c9e0d7 100644 --- a/src/meson.build +++ b/src/meson.build @@ -408,10 +408,11 @@ highs_dep = declare_dependency(link_with: highslib, include_directories: _incdirs, ) - py.install_sources([ + highs_py_srcs = files( 'highspy/__init__.py', - 'highspy/highs.py', - ], + 'highspy/highs.py',) + + py.install_sources(highs_py_srcs, pure: false, subdir: 'highspy' ) From 8dc335eb802f23fc40a17b9e215163c0ca75b28a Mon Sep 17 00:00:00 2001 From: Rohit Goswami Date: Sat, 20 Jul 2024 18:47:57 +0000 Subject: [PATCH 37/38] BIND: Use module local scope for exports --- src/highs_bindings.cpp | 75 +++++++++++++++++++++--------------------- 1 file changed, 38 insertions(+), 37 deletions(-) diff --git a/src/highs_bindings.cpp b/src/highs_bindings.cpp index 5d747c1ec6..99563a4486 100644 --- a/src/highs_bindings.cpp +++ b/src/highs_bindings.cpp @@ -580,28 +580,28 @@ std::tuple highs_getRowByName(Highs* h, PYBIND11_MODULE(_core, m) { // enumerations // Older enums, need to have values exported - py::enum_(m, "SolutionStatus") + py::enum_(m, "SolutionStatus", py::module_local()) .value("kSolutionStatusNone", SolutionStatus::kSolutionStatusNone) .value("kSolutionStatusInfeasible", SolutionStatus::kSolutionStatusInfeasible) .value("kSolutionStatusFeasible", SolutionStatus::kSolutionStatusFeasible) .export_values(); - py::enum_(m, "BasisValidity") + py::enum_(m, "BasisValidity", py::module_local()) .value("kBasisValidityInvalid", BasisValidity::kBasisValidityInvalid) .value("kBasisValidityValid", BasisValidity::kBasisValidityValid) .export_values(); // C++ enum classes do not need to have values exported - py::enum_(m, "ObjSense") + py::enum_(m, "ObjSense", py::module_local()) .value("kMinimize", ObjSense::kMinimize) .value("kMaximize", ObjSense::kMaximize); - py::enum_(m, "MatrixFormat") + py::enum_(m, "MatrixFormat", py::module_local()) .value("kColwise", MatrixFormat::kColwise) .value("kRowwise", MatrixFormat::kRowwise) .value("kRowwisePartitioned", MatrixFormat::kRowwisePartitioned); - py::enum_(m, "HessianFormat") + py::enum_(m, "HessianFormat", py::module_local()) .value("kTriangular", HessianFormat::kTriangular) .value("kSquare", HessianFormat::kSquare); - py::enum_(m, "HighsModelStatus") + py::enum_(m, "HighsModelStatus", py::module_local()) .value("kNotset", HighsModelStatus::kNotset) .value("kLoadError", HighsModelStatus::kLoadError) .value("kModelError", HighsModelStatus::kModelError) @@ -621,7 +621,7 @@ PYBIND11_MODULE(_core, m) { .value("kSolutionLimit", HighsModelStatus::kSolutionLimit) .value("kInterrupt", HighsModelStatus::kInterrupt) .value("kMemoryLimit", HighsModelStatus::kMemoryLimit); - py::enum_(m, "HighsPresolveStatus") + py::enum_(m, "HighsPresolveStatus", py::module_local()) .value("kNotPresolved", HighsPresolveStatus::kNotPresolved) .value("kNotReduced", HighsPresolveStatus::kNotReduced) .value("kInfeasible", HighsPresolveStatus::kInfeasible) @@ -632,38 +632,38 @@ PYBIND11_MODULE(_core, m) { .value("kTimeout", HighsPresolveStatus::kTimeout) .value("kNullError", HighsPresolveStatus::kNullError) .value("kOptionsError", HighsPresolveStatus::kOptionsError); - py::enum_(m, "HighsBasisStatus") + py::enum_(m, "HighsBasisStatus", py::module_local()) .value("kLower", HighsBasisStatus::kLower) .value("kBasic", HighsBasisStatus::kBasic) .value("kUpper", HighsBasisStatus::kUpper) .value("kZero", HighsBasisStatus::kZero) .value("kNonbasic", HighsBasisStatus::kNonbasic); - py::enum_(m, "HighsVarType") + py::enum_(m, "HighsVarType", py::module_local()) .value("kContinuous", HighsVarType::kContinuous) .value("kInteger", HighsVarType::kInteger) .value("kSemiContinuous", HighsVarType::kSemiContinuous) .value("kSemiInteger", HighsVarType::kSemiInteger); - py::enum_(m, "HighsOptionType") + py::enum_(m, "HighsOptionType", py::module_local()) .value("kBool", HighsOptionType::kBool) .value("kInt", HighsOptionType::kInt) .value("kDouble", HighsOptionType::kDouble) .value("kString", HighsOptionType::kString); - py::enum_(m, "HighsInfoType") + py::enum_(m, "HighsInfoType", py::module_local()) .value("kInt64", HighsInfoType::kInt64) .value("kInt", HighsInfoType::kInt) .value("kDouble", HighsInfoType::kDouble); - py::enum_(m, "HighsStatus") + py::enum_(m, "HighsStatus", py::module_local()) .value("kError", HighsStatus::kError) .value("kOk", HighsStatus::kOk) .value("kWarning", HighsStatus::kWarning); - py::enum_(m, "HighsLogType") + py::enum_(m, "HighsLogType", py::module_local()) .value("kInfo", HighsLogType::kInfo) .value("kDetailed", HighsLogType::kDetailed) .value("kVerbose", HighsLogType::kVerbose) .value("kWarning", HighsLogType::kWarning) .value("kError", HighsLogType::kError); // Classes - py::class_(m, "HighsSparseMatrix") + py::class_(m, "HighsSparseMatrix", py::module_local()) .def(py::init<>()) .def_readwrite("format_", &HighsSparseMatrix::format_) .def_readwrite("num_col_", &HighsSparseMatrix::num_col_) @@ -672,7 +672,7 @@ PYBIND11_MODULE(_core, m) { .def_readwrite("p_end_", &HighsSparseMatrix::p_end_) .def_readwrite("index_", &HighsSparseMatrix::index_) .def_readwrite("value_", &HighsSparseMatrix::value_); - py::class_(m, "HighsLp") + py::class_(m, "HighsLp", py::module_local()) .def(py::init<>()) .def_readwrite("num_col_", &HighsLp::num_col_) .def_readwrite("num_row_", &HighsLp::num_row_) @@ -692,18 +692,18 @@ PYBIND11_MODULE(_core, m) { .def_readwrite("is_scaled_", &HighsLp::is_scaled_) .def_readwrite("is_moved_", &HighsLp::is_moved_) .def_readwrite("mods_", &HighsLp::mods_); - py::class_(m, "HighsHessian") + py::class_(m, "HighsHessian", py::module_local()) .def(py::init<>()) .def_readwrite("dim_", &HighsHessian::dim_) .def_readwrite("format_", &HighsHessian::format_) .def_readwrite("start_", &HighsHessian::start_) .def_readwrite("index_", &HighsHessian::index_) .def_readwrite("value_", &HighsHessian::value_); - py::class_(m, "HighsModel") + py::class_(m, "HighsModel", py::module_local()) .def(py::init<>()) .def_readwrite("lp_", &HighsModel::lp_) .def_readwrite("hessian_", &HighsModel::hessian_); - py::class_(m, "HighsInfo") + py::class_(m, "HighsInfo", py::module_local()) .def(py::init<>()) .def_readwrite("valid", &HighsInfo::valid) .def_readwrite("mip_node_count", &HighsInfo::mip_node_count) @@ -740,7 +740,7 @@ PYBIND11_MODULE(_core, m) { &HighsInfo::max_complementarity_violation) .def_readwrite("sum_complementarity_violations", &HighsInfo::sum_complementarity_violations); - py::class_(m, "HighsOptions") + py::class_(m, "HighsOptions", py::module_local()) .def(py::init<>()) .def_readwrite("presolve", &HighsOptions::presolve) .def_readwrite("solver", &HighsOptions::solver) @@ -828,7 +828,7 @@ PYBIND11_MODULE(_core, m) { &HighsOptions::mip_heuristic_effort) .def_readwrite("mip_min_logging_interval", &HighsOptions::mip_min_logging_interval); - py::class_(m, "_Highs") + py::class_(m, "_Highs", py::module_local()) .def(py::init<>()) .def("version", &Highs::version) .def("versionMajor", &Highs::versionMajor) @@ -967,7 +967,7 @@ PYBIND11_MODULE(_core, m) { .def("stopCallbackInt", static_cast( &Highs::stopCallback)); // structs - py::class_(m, "HighsSolution") + py::class_(m, "HighsSolution", py::module_local()) .def(py::init<>()) .def_readwrite("value_valid", &HighsSolution::value_valid) .def_readwrite("dual_valid", &HighsSolution::dual_valid) @@ -975,11 +975,11 @@ PYBIND11_MODULE(_core, m) { .def_readwrite("col_dual", &HighsSolution::col_dual) .def_readwrite("row_value", &HighsSolution::row_value) .def_readwrite("row_dual", &HighsSolution::row_dual); - py::class_(m, "HighsObjectiveSolution") + py::class_(m, "HighsObjectiveSolution", py::module_local()) .def(py::init<>()) .def_readwrite("objective", &HighsObjectiveSolution::objective) .def_readwrite("col_value", &HighsObjectiveSolution::col_value); - py::class_(m, "HighsBasis") + py::class_(m, "HighsBasis", py::module_local()) .def(py::init<>()) .def_readwrite("valid", &HighsBasis::valid) .def_readwrite("alien", &HighsBasis::alien) @@ -989,13 +989,13 @@ PYBIND11_MODULE(_core, m) { .def_readwrite("debug_origin_name", &HighsBasis::debug_origin_name) .def_readwrite("col_status", &HighsBasis::col_status) .def_readwrite("row_status", &HighsBasis::row_status); - py::class_(m, "HighsRangingRecord") + py::class_(m, "HighsRangingRecord", py::module_local()) .def(py::init<>()) .def_readwrite("value_", &HighsRangingRecord::value_) .def_readwrite("objective_", &HighsRangingRecord::objective_) .def_readwrite("in_var_", &HighsRangingRecord::in_var_) .def_readwrite("ou_var_", &HighsRangingRecord::ou_var_); - py::class_(m, "HighsRanging") + py::class_(m, "HighsRanging", py::module_local()) .def(py::init<>()) .def_readwrite("valid", &HighsRanging::valid) .def_readwrite("col_cost_up", &HighsRanging::col_cost_up) @@ -1016,7 +1016,7 @@ PYBIND11_MODULE(_core, m) { py::module_ simplex_constants = m.def_submodule("simplex_constants", "Submodule for simplex constants"); - py::enum_(simplex_constants, "SimplexStrategy") + py::enum_(simplex_constants, "SimplexStrategy", py::module_local()) .value("kSimplexStrategyMin", SimplexStrategy::kSimplexStrategyMin) .value("kSimplexStrategyChoose", SimplexStrategy::kSimplexStrategyChoose) .value("kSimplexStrategyDual", SimplexStrategy::kSimplexStrategyDual) @@ -1031,7 +1031,7 @@ PYBIND11_MODULE(_core, m) { .value("kSimplexStrategyNum", SimplexStrategy::kSimplexStrategyNum) .export_values(); // needed since it isn't an enum class py::enum_(simplex_constants, - "SimplexUnscaledSolutionStrategy") + "SimplexUnscaledSolutionStrategy", py::module_local()) .value( "kSimplexUnscaledSolutionStrategyMin", SimplexUnscaledSolutionStrategy::kSimplexUnscaledSolutionStrategyMin) @@ -1051,7 +1051,7 @@ PYBIND11_MODULE(_core, m) { "kSimplexUnscaledSolutionStrategyNum", SimplexUnscaledSolutionStrategy::kSimplexUnscaledSolutionStrategyNum) .export_values(); - py::enum_(simplex_constants, "SimplexSolvePhase") + py::enum_(simplex_constants, "SimplexSolvePhase", py::module_local()) .value("kSolvePhaseMin", SimplexSolvePhase::kSolvePhaseMin) .value("kSolvePhaseError", SimplexSolvePhase::kSolvePhaseError) .value("kSolvePhaseExit", SimplexSolvePhase::kSolvePhaseExit) @@ -1067,7 +1067,7 @@ PYBIND11_MODULE(_core, m) { .value("kSolvePhaseMax", SimplexSolvePhase::kSolvePhaseMax) .export_values(); py::enum_(simplex_constants, - "SimplexEdgeWeightStrategy") + "SimplexEdgeWeightStrategy", py::module_local()) .value("kSimplexEdgeWeightStrategyMin", SimplexEdgeWeightStrategy::kSimplexEdgeWeightStrategyMin) .value("kSimplexEdgeWeightStrategyChoose", @@ -1081,7 +1081,7 @@ PYBIND11_MODULE(_core, m) { .value("kSimplexEdgeWeightStrategyMax", SimplexEdgeWeightStrategy::kSimplexEdgeWeightStrategyMax) .export_values(); - py::enum_(simplex_constants, "SimplexPriceStrategy") + py::enum_(simplex_constants, "SimplexPriceStrategy", py::module_local()) .value("kSimplexPriceStrategyMin", SimplexPriceStrategy::kSimplexPriceStrategyMin) .value("kSimplexPriceStrategyCol", @@ -1096,7 +1096,7 @@ PYBIND11_MODULE(_core, m) { SimplexPriceStrategy::kSimplexPriceStrategyMax) .export_values(); py::enum_( - simplex_constants, "SimplexPivotalRowRefinementStrategy") + simplex_constants, "SimplexPivotalRowRefinementStrategy", py::module_local()) .value("kSimplexInfeasibilityProofRefinementMin", SimplexPivotalRowRefinementStrategy:: kSimplexInfeasibilityProofRefinementMin) @@ -1114,7 +1114,7 @@ PYBIND11_MODULE(_core, m) { kSimplexInfeasibilityProofRefinementMax) .export_values(); py::enum_(simplex_constants, - "SimplexPrimalCorrectionStrategy") + "SimplexPrimalCorrectionStrategy", py::module_local()) .value( "kSimplexPrimalCorrectionStrategyNone", SimplexPrimalCorrectionStrategy::kSimplexPrimalCorrectionStrategyNone) @@ -1125,7 +1125,7 @@ PYBIND11_MODULE(_core, m) { SimplexPrimalCorrectionStrategy:: kSimplexPrimalCorrectionStrategyAlways) .export_values(); - py::enum_(simplex_constants, "SimplexNlaOperation") + py::enum_(simplex_constants, "SimplexNlaOperation", py::module_local()) .value("kSimplexNlaNull", SimplexNlaOperation::kSimplexNlaNull) .value("kSimplexNlaBtranFull", SimplexNlaOperation::kSimplexNlaBtranFull) .value("kSimplexNlaPriceFull", SimplexNlaOperation::kSimplexNlaPriceFull) @@ -1142,14 +1142,15 @@ PYBIND11_MODULE(_core, m) { .value("kNumSimplexNlaOperation", SimplexNlaOperation::kNumSimplexNlaOperation) .export_values(); - py::enum_(simplex_constants, "EdgeWeightMode") + py::enum_(simplex_constants, "EdgeWeightMode", py::module_local()) .value("kDantzig", EdgeWeightMode::kDantzig) .value("kDevex", EdgeWeightMode::kDevex) .value("kSteepestEdge", EdgeWeightMode::kSteepestEdge) .value("kCount", EdgeWeightMode::kCount); + py::module_ callbacks = m.def_submodule("cb", "Callback interface submodule"); // Types for interface - py::enum_(callbacks, "HighsCallbackType") + py::enum_(callbacks, "HighsCallbackType", py::module_local()) .value("kCallbackMin", HighsCallbackType::kCallbackMin) .value("kCallbackLogging", HighsCallbackType::kCallbackLogging) .value("kCallbackSimplexInterrupt", @@ -1167,7 +1168,7 @@ PYBIND11_MODULE(_core, m) { .value("kNumCallbackType", HighsCallbackType::kNumCallbackType) .export_values(); // Classes - py::class_(callbacks, "HighsCallbackDataOut") + py::class_(callbacks, "HighsCallbackDataOut", py::module_local()) .def(py::init<>()) .def_readwrite("log_type", &HighsCallbackDataOut::log_type) .def_readwrite("running_time", &HighsCallbackDataOut::running_time) @@ -1194,7 +1195,7 @@ PYBIND11_MODULE(_core, m) { [](HighsCallbackDataOut& self, py::array_t new_mip_solution) { self.mip_solution = new_mip_solution.mutable_data(); }); - py::class_(callbacks, "HighsCallbackDataIn") + py::class_(callbacks, "HighsCallbackDataIn", py::module_local()) .def(py::init<>()) .def_readwrite("user_interrupt", &HighsCallbackDataIn::user_interrupt); } From cc6528009214f7e3476108cb531587cb1b73f9ae Mon Sep 17 00:00:00 2001 From: Rohit Goswami Date: Sat, 20 Jul 2024 23:21:32 +0000 Subject: [PATCH 38/38] MAINT: Rework options for scipy --- src/highs_options.cpp | 53 +++++++++++++++++++++++++++++++------------ 1 file changed, 39 insertions(+), 14 deletions(-) diff --git a/src/highs_options.cpp b/src/highs_options.cpp index c043b0d697..a7f6e140d7 100644 --- a/src/highs_options.cpp +++ b/src/highs_options.cpp @@ -8,16 +8,10 @@ namespace py = pybind11; -bool log_to_console = false; -bool output_flag = true; - -// HighsLogOptions highs_log_options = {nullptr, &output_flag, &log_to_console, -// nullptr}; -HighsLogOptions highs_log_options = {}; - class HighsOptionsManager { public: HighsOptionsManager() { + initialize_log_options(); for (const auto& record : highs_options_.records) { record_type_lookup_.emplace(record->name, record->type); } @@ -55,6 +49,21 @@ class HighsOptionsManager { HighsOptions highs_options_; std::mutex highs_options_mutex; std::map record_type_lookup_; + HighsLogOptions highs_log_options; + + static constexpr bool log_to_console = false; + static constexpr bool output_flag = true; + + void initialize_log_options() { + highs_log_options.log_stream = nullptr; + highs_log_options.output_flag = const_cast(&output_flag); + highs_log_options.log_to_console = const_cast(&log_to_console); + highs_log_options.log_dev_level = nullptr; + highs_log_options.user_log_callback = nullptr; + highs_log_options.user_log_callback_data = nullptr; + highs_log_options.user_callback_data = nullptr; + highs_log_options.user_callback_active = false; + } }; PYBIND11_MODULE(_highs_options, m) { @@ -79,16 +88,32 @@ PYBIND11_MODULE(_highs_options, m) { }) .def("check_int_option", [](HighsOptionsManager& self, const std::string& name, int value) { - return self.check_option(name, value); + try { + return self.check_option(name, value); + } catch (const std::exception& e) { + py::print("Exception caught in check_int_option:", e.what()); + return false; + } }) .def( "check_double_option", [](HighsOptionsManager& self, const std::string& name, double value) { - return self.check_option(name, value); + try { + return self.check_option(name, value); + } catch (const std::exception& e) { + py::print("Exception caught in check_double_option:", e.what()); + return false; + } }) - .def("check_string_option", [](HighsOptionsManager& self, - const std::string& name, - const std::string& value) { - return self.check_option(name, value); - }); + .def("check_string_option", + [](HighsOptionsManager& self, const std::string& name, + const std::string& value) { + try { + return self.check_option(name, + value); + } catch (const std::exception& e) { + py::print("Exception caught in check_string_option:", e.what()); + return false; + } + }); }