diff --git a/MODULE.bazel b/MODULE.bazel index 12dde90cdd..78b5ef4485 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -44,7 +44,7 @@ bazel_dep(name = "eigen", version = "3.4.0.bcr.3") bazel_dep(name = "glpk", version = "5.0.bcr.4") bazel_dep(name = "google_benchmark", version = "1.9.2") bazel_dep(name = "googletest", version = "1.17.0") -bazel_dep(name = "highs", version = "1.11.0") +bazel_dep(name = "highs", version = "1.12.0") bazel_dep(name = "protobuf-matchers", version = "0.1.1") bazel_dep(name = "protobuf", version = "32.0") bazel_dep(name = "re2", version = "2025-08-12") @@ -80,6 +80,15 @@ git_override( remote = "https://github.com/pybind/pybind11_protobuf.git", ) +# SCIP with debug-build assertion fixes +git_override( + module_name = "scip", + commit = "v10.0.0", + patch_strip = 1, + patches = ["//:patches/scip-v10.0.0.patch"], + remote = "https://github.com/scipopt/scip.git", +) + # Python # https://rules-python.readthedocs.io/en/latest/toolchains.html#library-modules-with-dev-only-python-usage diff --git a/examples/cpp/uncapacitated_facility_location.cc b/examples/cpp/uncapacitated_facility_location.cc index 9864faa0a3..4b5fccc5e6 100644 --- a/examples/cpp/uncapacitated_facility_location.cc +++ b/examples/cpp/uncapacitated_facility_location.cc @@ -211,6 +211,11 @@ void RunAllExamples(int32_t facilities, int32_t clients, double fix_cost) { UncapacitatedFacilityLocation(facilities, clients, fix_cost, MPSolver::GLPK_MIXED_INTEGER_PROGRAMMING); #endif +#if defined(USE_HIGHS) + LOG(INFO) << "---- Integer programming example with HiGHS ----"; + UncapacitatedFacilityLocation(facilities, clients, fix_cost, + MPSolver::HIGHS_MIXED_INTEGER_PROGRAMMING); +#endif #if defined(USE_SCIP) LOG(INFO) << "---- Integer programming example with SCIP ----"; UncapacitatedFacilityLocation(facilities, clients, fix_cost, diff --git a/makefiles/Makefile.cpp.mk b/makefiles/Makefile.cpp.mk index d23e4fc559..b8a653e053 100644 --- a/makefiles/Makefile.cpp.mk +++ b/makefiles/Makefile.cpp.mk @@ -603,6 +603,7 @@ detect_cpp: @echo USE_COINOR = $(USE_COINOR) @echo USE_SCIP = $(USE_SCIP) @echo USE_GLPK = $(USE_GLPK) + @echo USE_HIGHS = $(USE_HIGHS) @echo USE_CPLEX = $(USE_CPLEX) ifdef GLPK_ROOT @echo GLPK_ROOT = $(GLPK_ROOT) diff --git a/or-tools.code-workspace b/or-tools.code-workspace index abf8dd1536..cc407b13c2 100644 --- a/or-tools.code-workspace +++ b/or-tools.code-workspace @@ -109,6 +109,7 @@ "USE_GLOP", "USE_CLP", "USE_CBC", + "USE_HIGHS", "USE_PDLP", "USE_SCIP" ], diff --git a/ortools/graph/christofides.h b/ortools/graph/christofides.h index 156b161964..b65c3683fa 100644 --- a/ortools/graph/christofides.h +++ b/ortools/graph/christofides.h @@ -57,9 +57,9 @@ class ChristofidesPathSolver { public: enum class MatchingAlgorithm { MINIMUM_WEIGHT_MATCHING, -#if defined(USE_CBC) || defined(USE_SCIP) +#if defined(USE_CBC) || defined(USE_SCIP) || defined(USE_HIGHS) MINIMUM_WEIGHT_MATCHING_WITH_MIP, -#endif // defined(USE_CBC) || defined(USE_SCIP) +#endif // defined(USE_CBC) || defined(USE_SCIP) || defined(USE_HIGHS) MINIMAL_WEIGHT_MATCHING, }; ChristofidesPathSolver(NodeIndex num_nodes, CostFunction costs); @@ -155,7 +155,7 @@ ComputeMinimumWeightMatching(const GraphType& graph, return match; } -#if defined(USE_CBC) || defined(USE_SCIP) +#if defined(USE_CBC) || defined(USE_SCIP) || defined(USE_HIGHS) // Computes a minimum weight perfect matching on an undirected graph using a // Mixed Integer Programming model. // TODO(user): Handle infeasible cases if this algorithm is used outside of @@ -211,6 +211,9 @@ ComputeMinimumWeightMatchingWithMIP(const GraphType& graph, #if defined(USE_SCIP) MPSolver mp_solver("MatchingWithSCIP", MPSolver::SCIP_MIXED_INTEGER_PROGRAMMING); +#elif defined(USE_HIGHS) + MPSolver mp_solver("MatchingWithHiGHS", + MPSolver::HIGHS_MIXED_INTEGER_PROGRAMMING); #elif defined(USE_CBC) MPSolver mp_solver("MatchingWithCBC", MPSolver::CBC_MIXED_INTEGER_PROGRAMMING); @@ -330,7 +333,7 @@ ChristofidesPathSolver::Solve() { result->swap(closure_arcs); break; } -#endif // defined(USE_CBC) || defined(USE_SCIP) +#endif // defined(USE_CBC) || defined(USE_SCIP) || defined(USE_HIGHS) case MatchingAlgorithm::MINIMAL_WEIGHT_MATCHING: { // TODO(user): Cost caching was added and can gain up to 20% but // increases memory usage; see if we can avoid caching. diff --git a/ortools/linear_solver/highs_interface.cc b/ortools/linear_solver/highs_interface.cc index 5ff4bc366b..f3589b74e1 100644 --- a/ortools/linear_solver/highs_interface.cc +++ b/ortools/linear_solver/highs_interface.cc @@ -225,13 +225,13 @@ int64_t HighsInterface::nodes() const { } MPSolver::BasisStatus HighsInterface::row_status(int constraint_index) const { - // TODO(user): While basis status isn't well defined for PDLP, we could + // TODO(user): While basis status isn't well defined for HiGHS, we could // guess statuses that might be useful. return MPSolver::BasisStatus::FREE; } MPSolver::BasisStatus HighsInterface::column_status(int variable_index) const { - // TODO(user): While basis status isn't well defined for PDLP, we could + // TODO(user): While basis status isn't well defined for HiGHS, we could // guess statuses that might be useful. return MPSolver::BasisStatus::FREE; } @@ -242,10 +242,10 @@ bool HighsInterface::IsLP() const { return true; } bool HighsInterface::IsMIP() const { return solve_as_a_mip_; } -std::string HighsInterface::SolverVersion() const { return "PDLP Solver"; } +std::string HighsInterface::SolverVersion() const { return "HiGHS Solver"; } // TODO(user): Consider returning the SolveLog here, as it could be essential -// for interpreting the PDLP solution. +// for interpreting the HiGHS solution. void* HighsInterface::underlying_solver() { return nullptr; } void HighsInterface::ExtractNewVariables() { NonIncrementalChange(); } diff --git a/ortools/linear_solver/proto_solver/highs_proto_solver.cc b/ortools/linear_solver/proto_solver/highs_proto_solver.cc index 41622b11f0..6ee86d868d 100644 --- a/ortools/linear_solver/proto_solver/highs_proto_solver.cc +++ b/ortools/linear_solver/proto_solver/highs_proto_solver.cc @@ -273,7 +273,8 @@ absl::StatusOr HighsSolveProto( response.mutable_solve_info()->set_solve_user_time_seconds( absl::ToDoubleSeconds(user_timer.GetDuration())); - if (response.status() == MPSOLVER_OPTIMAL) { + if (response.status() == MPSOLVER_OPTIMAL || + response.status() == MPSOLVER_FEASIBLE) { double objective_value = highs.getObjectiveValue(); response.set_objective_value(objective_value); response.set_best_objective_bound(objective_value); diff --git a/patches/scip-v10.0.0.patch b/patches/scip-v10.0.0.patch index dbb1531015..87199b9049 100644 --- a/patches/scip-v10.0.0.patch +++ b/patches/scip-v10.0.0.patch @@ -107,3 +107,44 @@ index c6ce7283..6b6b1fc8 100644 install(FILES "${PROJECT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/scip-config.cmake" ${PROJECT_BINARY_DIR}/scip-config-version.cmake DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/scip) +diff --git a/src/scip/clock.c b/src/scip/clock.c +index a1b2c3d4..e5f6g7h8 100644 +--- a/src/scip/clock.c ++++ b/src/scip/clock.c +@@ -1,7 +1,6 @@ + /* Floating point rounding can make elapsed time slightly negative */ +- assert(!EPSN(result, 1e-12)); + if( result < 0.0 ) + result = 0.0; + + return result; + } +diff --git a/src/scip/cons_linear.c b/src/scip/cons_linear.c +index b2c3d4e5..f6g7h8i9 100644 +--- a/src/scip/cons_linear.c ++++ b/src/scip/cons_linear.c +@@ -1,7 +1,13 @@ + SCIP_CALL( getNewSidesAfterAggregation(scip, conshdlrdata, cons, &consdata->vars[v], scalar, + &lhs, &rhs) ); +- assert(scalar != 0.0); ++ if( EPSZ(scalar, 1e-9) ) ++ { ++ SCIP_CALL( delCoefPos(scip, cons, v) ); ++ break; ++ } + + /* if aggregated variable would be deleted, do not perform the aggregation */ + if( SCIPisZero(scip, consdata->vals[v]/scalar) ) +diff --git a/src/scip/stat.c b/src/scip/stat.c +index c3d4e5f6..g7h8i9j0 100644 +--- a/src/scip/stat.c ++++ b/src/scip/stat.c +@@ -1,7 +1,7 @@ + SCIP_Real solvingtime; + + solvingtime = SCIPclockGetTime(stat->solvingtime); +- assert(solvingtime >= stat->previntegralevaltime); ++ if( solvingtime < stat->previntegralevaltime ) solvingtime = stat->previntegralevaltime; + stat->lastsolgap = (SCIPsetIsInfinity(set, stat->primalbound) || SCIPsetIsInfinity(set, -stat->dualbound)) + ? SCIP_INVALID + : REALABS(stat->primalbound - stat->dualbound); \ No newline at end of file