diff --git a/ortools/math_opt/solvers/gurobi_solver.cc b/ortools/math_opt/solvers/gurobi_solver.cc index 964864db0e..9c809f5e3f 100644 --- a/ortools/math_opt/solvers/gurobi_solver.cc +++ b/ortools/math_opt/solvers/gurobi_solver.cc @@ -31,7 +31,6 @@ #include "absl/log/check.h" #include "absl/log/log.h" #include "absl/memory/memory.h" -#include "absl/meta/type_traits.h" #include "absl/status/status.h" #include "absl/status/statusor.h" #include "absl/strings/escaping.h" @@ -2756,6 +2755,7 @@ absl::StatusOr GurobiSolver::Update( absl::StatusOr> GurobiSolver::New( const ModelProto& input_model, const SolverInterface::InitArgs& init_args) { + // TODO(user): Correctly load the gurobi library in open source. RETURN_IF_ERROR( ModelIsSupported(input_model, kGurobiSupportedStructures, "Gurobi")); if (!input_model.auxiliary_objectives().empty() && diff --git a/ortools/math_opt/solvers/gurobi_solver.h b/ortools/math_opt/solvers/gurobi_solver.h index 03478bcd76..81a8b24558 100644 --- a/ortools/math_opt/solvers/gurobi_solver.h +++ b/ortools/math_opt/solvers/gurobi_solver.h @@ -46,7 +46,6 @@ #include "ortools/third_party_solvers/gurobi_environment.h" #include "ortools/util/solve_interrupter.h" - namespace operations_research { namespace math_opt { diff --git a/ortools/sat/2d_distances_propagator.cc b/ortools/sat/2d_distances_propagator.cc index 65e949c206..2053e29581 100644 --- a/ortools/sat/2d_distances_propagator.cc +++ b/ortools/sat/2d_distances_propagator.cc @@ -32,7 +32,6 @@ #include "ortools/sat/precedences.h" #include "ortools/sat/scheduling_helpers.h" #include "ortools/sat/synchronization.h" -#include "ortools/util/bitset.h" namespace operations_research { namespace sat { @@ -42,22 +41,14 @@ Precedences2DPropagator::Precedences2DPropagator( : helper_(*helper), linear2_bounds_(model->GetOrCreate()), linear2_watcher_(model->GetOrCreate()), - shared_stats_(model->GetOrCreate()) { + shared_stats_(model->GetOrCreate()), + non_trivial_bounds_( + model->GetOrCreate()) { model->GetOrCreate()->SetPushAffineUbForBinaryRelation(); } -void Precedences2DPropagator::CollectPairsOfBoxesWithNonTrivialDistance() { - helper_.SynchronizeAndSetDirection(); - non_trivial_pairs_.clear(); - - struct VarUsage { - // boxes[0=x, 1=y][0=start, 1=end] - std::vector boxes[2][2]; - }; - absl::flat_hash_map var_to_box_and_coeffs; - SparseBitset& var_set = - *linear2_bounds_->GetTemporyClearedAndResizedBitset(); - +void Precedences2DPropagator::UpdateVarLookups() { + var_to_box_and_coeffs_.clear(); for (int dim = 0; dim < 2; ++dim) { const SchedulingConstraintHelper& dim_helper = dim == 0 ? helper_.x_helper() : helper_.y_helper(); @@ -67,41 +58,52 @@ void Precedences2DPropagator::CollectPairsOfBoxesWithNonTrivialDistance() { for (int i = 0; i < helper_.NumBoxes(); ++i) { const IntegerVariable var = interval_points[i].var; if (var != kNoIntegerVariable) { - var_set.Set(PositiveVariable(var)); - var_to_box_and_coeffs[PositiveVariable(var)].boxes[dim][j].push_back( + var_to_box_and_coeffs_[PositiveVariable(var)].boxes[dim][j].push_back( i); } } } } +} +void Precedences2DPropagator::CollectNewPairsOfBoxesWithNonTrivialDistance() { const absl::Span exprs = - linear2_bounds_->GetAllExpressionsWithPotentialNonTrivialBounds( - var_set.BitsetConstView()); - VLOG(2) << "CollectPairsOfBoxesWithNonTrivialDistance called, num_exprs: " - << exprs.size(); - for (const LinearExpression2& expr : exprs) { - auto it1 = var_to_box_and_coeffs.find(PositiveVariable(expr.vars[0])); - auto it2 = var_to_box_and_coeffs.find(PositiveVariable(expr.vars[1])); - DCHECK(it1 != var_to_box_and_coeffs.end()); - DCHECK(it2 != var_to_box_and_coeffs.end()); + non_trivial_bounds_->GetLinear2WithPotentialNonTrivalBounds(); + if (exprs.size() != num_known_linear2_) { + VLOG(2) << "CollectPairsOfBoxesWithNonTrivialDistance called, num_exprs: " + << exprs.size(); + } + for (; num_known_linear2_ < exprs.size(); ++num_known_linear2_) { + const LinearExpression2& positive_expr = exprs[num_known_linear2_]; + LinearExpression2 negated_expr = positive_expr; + negated_expr.Negate(); + for (const LinearExpression2& expr : {positive_expr, negated_expr}) { + auto it1 = var_to_box_and_coeffs_.find(PositiveVariable(expr.vars[0])); + auto it2 = var_to_box_and_coeffs_.find(PositiveVariable(expr.vars[1])); + if (it1 == var_to_box_and_coeffs_.end()) { + continue; + } + if (it2 == var_to_box_and_coeffs_.end()) { + continue; + } - const VarUsage& usage1 = it1->second; - const VarUsage& usage2 = it2->second; - for (int dim = 0; dim < 2; ++dim) { - const SchedulingConstraintHelper& dim_helper = - dim == 0 ? helper_.x_helper() : helper_.y_helper(); - for (const int box1 : usage1.boxes[dim][0 /* start */]) { - for (const int box2 : usage2.boxes[dim][1 /* end */]) { - if (box1 == box2) continue; - const auto [expr2, unused] = EncodeDifferenceLowerThan( - dim_helper.Starts()[box1], dim_helper.Ends()[box2], - /*ub=unused*/ 0); - if (expr == expr2) { - if (box1 < box2) { - non_trivial_pairs_.push_back({box1, box2}); - } else { - non_trivial_pairs_.push_back({box2, box1}); + const VarUsage& usage1 = it1->second; + const VarUsage& usage2 = it2->second; + for (int dim = 0; dim < 2; ++dim) { + const SchedulingConstraintHelper& dim_helper = + dim == 0 ? helper_.x_helper() : helper_.y_helper(); + for (const int box1 : usage1.boxes[dim][0 /* start */]) { + for (const int box2 : usage2.boxes[dim][1 /* end */]) { + if (box1 == box2) continue; + const auto [expr2, unused] = EncodeDifferenceLowerThan( + dim_helper.Starts()[box1], dim_helper.Ends()[box2], + /*ub=unused*/ 0); + if (expr == expr2) { + if (box1 < box2) { + non_trivial_pairs_.push_back({box1, box2}); + } else { + non_trivial_pairs_.push_back({box2, box1}); + } } } } @@ -114,13 +116,13 @@ void Precedences2DPropagator::CollectPairsOfBoxesWithNonTrivialDistance() { bool Precedences2DPropagator::Propagate() { if (!helper_.SynchronizeAndSetDirection()) return false; - if (last_helper_inprocessing_count_ != helper_.InProcessingCount() || - helper_.x_helper().CurrentDecisionLevel() == 0 || - last_linear2_timestamp_ != linear2_watcher_->Timestamp()) { + if (last_helper_inprocessing_count_ != helper_.InProcessingCount()) { last_helper_inprocessing_count_ = helper_.InProcessingCount(); - last_linear2_timestamp_ = linear2_watcher_->Timestamp(); - CollectPairsOfBoxesWithNonTrivialDistance(); + UpdateVarLookups(); + num_known_linear2_ = 0; + non_trivial_pairs_.clear(); } + CollectNewPairsOfBoxesWithNonTrivialDistance(); num_calls_++; diff --git a/ortools/sat/2d_distances_propagator.h b/ortools/sat/2d_distances_propagator.h index e8ca1066c9..6c47f37f64 100644 --- a/ortools/sat/2d_distances_propagator.h +++ b/ortools/sat/2d_distances_propagator.h @@ -18,7 +18,9 @@ #include #include +#include "absl/container/flat_hash_map.h" #include "ortools/sat/integer.h" +#include "ortools/sat/integer_base.h" #include "ortools/sat/model.h" #include "ortools/sat/no_overlap_2d_helper.h" #include "ortools/sat/precedences.h" @@ -30,7 +32,7 @@ namespace sat { // This class implements a propagator for non_overlap_2d constraints that uses // the Linear2Bounds to detect precedences between pairs of boxes and // detect a conflict if the precedences implies an overlap between the two -// boxes. For doing this efficiently, it keep track of pairs of boxes that have +// boxes. For doing this efficiently, it keeps track of pairs of boxes that have // non-fixed precedences in the Linear2Bounds and only check those in the // propagation. class Precedences2DPropagator : public PropagatorInterface { @@ -43,16 +45,25 @@ class Precedences2DPropagator : public PropagatorInterface { int RegisterWith(GenericLiteralWatcher* watcher); private: - void CollectPairsOfBoxesWithNonTrivialDistance(); + void CollectNewPairsOfBoxesWithNonTrivialDistance(); + void UpdateVarLookups(); std::vector> non_trivial_pairs_; + struct VarUsage { + // boxes[0=x, 1=y][0=start, 1=end] + std::vector boxes[2][2]; + }; + + absl::flat_hash_map var_to_box_and_coeffs_; NoOverlap2DConstraintHelper& helper_; Linear2Bounds* linear2_bounds_; Linear2Watcher* linear2_watcher_; SharedStatistics* shared_stats_; + Linear2WithPotentialNonTrivalBounds* non_trivial_bounds_; int last_helper_inprocessing_count_ = -1; + int num_known_linear2_ = 0; int64_t last_linear2_timestamp_ = -1; int64_t num_conflicts_ = 0; diff --git a/ortools/sat/BUILD.bazel b/ortools/sat/BUILD.bazel index a3321cdee7..771c6c010a 100644 --- a/ortools/sat/BUILD.bazel +++ b/ortools/sat/BUILD.bazel @@ -129,7 +129,6 @@ cc_library( ":scheduling_helpers", ":synchronization", "//ortools/base:stl_util", - "//ortools/util:bitset", "@abseil-cpp//absl/container:flat_hash_map", "@abseil-cpp//absl/log", "@abseil-cpp//absl/log:check", diff --git a/ortools/sat/precedences.cc b/ortools/sat/precedences.cc index 33a3c29318..017cc45550 100644 --- a/ortools/sat/precedences.cc +++ b/ortools/sat/precedences.cc @@ -100,6 +100,7 @@ std::pair RootLevelLinear2Bounds::Add(LinearExpression2 expr, status_ub == AddResult::ADDED || status_ub == AddResult::UPDATED; if (!lb_restricted && !ub_restricted) return {false, false}; + non_trivial_bounds_->AddOrGet(expr); ++num_updates_; linear2_watcher_->NotifyBoundChanged(expr); @@ -348,6 +349,7 @@ void EnforcedLinear2Bounds::PushConditionalRelation( const int new_index = conditional_stack_.size(); const auto [it, inserted] = conditional_relations_.insert({expr, new_index}); if (inserted) { + non_trivial_bounds_->AddOrGet(expr); CreateLevelEntryIfNeeded(); conditional_stack_.emplace_back(/*prev_entry=*/-1, rhs, expr, enforcements); @@ -1815,7 +1817,9 @@ Linear2BoundsFromLinear3::Linear2BoundsFromLinear3(Model* model) linear2_watcher_(model->GetOrCreate()), watcher_(model->GetOrCreate()), shared_stats_(model->GetOrCreate()), - root_level_bounds_(model->GetOrCreate()) {} + root_level_bounds_(model->GetOrCreate()), + non_trivial_bounds_( + model->GetOrCreate()) {} // Note that for speed we do not compare to the trivial or root level bounds. // @@ -1857,6 +1861,7 @@ bool Linear2BoundsFromLinear3::AddAffineUpperBound(LinearExpression2 expr, it->second = {affine_ub, divisor}; // Overwrite. } else { // Note that this should almost never happen (only once per lin2). + non_trivial_bounds_->AddOrGet(expr); best_affine_ub_[expr] = {affine_ub, divisor}; } diff --git a/ortools/sat/precedences.h b/ortools/sat/precedences.h index 57fd147999..392943ce63 100644 --- a/ortools/sat/precedences.h +++ b/ortools/sat/precedences.h @@ -14,10 +14,10 @@ #ifndef OR_TOOLS_SAT_PRECEDENCES_H_ #define OR_TOOLS_SAT_PRECEDENCES_H_ -#include #include #include #include +#include #include #include #include @@ -45,6 +45,63 @@ namespace operations_research { namespace sat { +DEFINE_STRONG_INDEX_TYPE(LinearExpression2Index); +const LinearExpression2Index kNoLinearExpression2Index(-1); +inline LinearExpression2Index NegationOf(LinearExpression2Index i) { + return LinearExpression2Index(i.value() ^ 1); +} + +inline bool Linear2IsPositive(LinearExpression2Index i) { + return (i.value() & 1) == 0; +} + +inline LinearExpression2Index PositiveLinear2(LinearExpression2Index i) { + return LinearExpression2Index(i.value() & (~1)); +} + +// Class to hold a list of LinearExpression2 that have (potentially) non-trivial +// bounds. This class is overzealous, in the sense that if a linear2 is in the +// list, it does not necessarily mean that it has a non-trivial bound, but the +// converse is true: if a linear2 is not in the list, +// Linear2Bounds::GetUpperBound() will return a trivial bound. +class Linear2WithPotentialNonTrivalBounds { + public: + Linear2WithPotentialNonTrivalBounds() = default; + + // Returns a never-changing index for the given linear expression. + // The expression must already be canonicalized and divided by its GCD. + LinearExpression2Index AddOrGet(LinearExpression2 expr) { + DCHECK(expr.IsCanonicalized()); + DCHECK_EQ(expr.DivideByGcd(), 1); + const bool negated = expr.NegateForCanonicalization(); + auto [it, inserted] = expr_to_index_.insert({expr, exprs_.size()}); + if (inserted) { + CHECK_LT(2 * exprs_.size() + 1, + std::numeric_limits::max()); + exprs_.push_back(expr); + } + const LinearExpression2Index positive_index(2 * it->second); + if (negated) { + return NegationOf(positive_index); + } else { + return positive_index; + } + } + + // Return all positive linear2 expressions that have a potentially non-trivial + // bound. When calling this code it is often a good idea to check both the + // expression on the span and its negation. The order is fixed forever and + // this span can only grow by appending new expressions. + absl::Span GetLinear2WithPotentialNonTrivalBounds() + const { + return exprs_; + } + + private: + util_intops::StrongVector exprs_; + absl::flat_hash_map expr_to_index_; +}; + // Simple "watcher" class that will be notified if a linear2 bound changed. It // can also be queried to see if LinearExpression2 involving a specific variable // changed since last time. @@ -79,7 +136,9 @@ class RootLevelLinear2Bounds { explicit RootLevelLinear2Bounds(Model* model) : integer_trail_(model->GetOrCreate()), linear2_watcher_(model->GetOrCreate()), - shared_stats_(model->GetOrCreate()) {} + shared_stats_(model->GetOrCreate()), + non_trivial_bounds_( + model->GetOrCreate()) {} ~RootLevelLinear2Bounds(); @@ -148,6 +207,7 @@ class RootLevelLinear2Bounds { IntegerTrail* integer_trail_; Linear2Watcher* linear2_watcher_; SharedStatistics* shared_stats_; + Linear2WithPotentialNonTrivalBounds* non_trivial_bounds_; // Lookup table to find all the LinearExpression2 with a given variable and // having both coefficient 1. @@ -258,7 +318,9 @@ class EnforcedLinear2Bounds : public ReversibleInterface { integer_trail_(model->GetOrCreate()), linear2_watcher_(model->GetOrCreate()), root_level_bounds_(model->GetOrCreate()), - shared_stats_(model->GetOrCreate()) { + shared_stats_(model->GetOrCreate()), + non_trivial_bounds_( + model->GetOrCreate()) { integer_trail_->RegisterReversibleClass(this); } @@ -324,6 +386,7 @@ class EnforcedLinear2Bounds : public ReversibleInterface { Linear2Watcher* linear2_watcher_; RootLevelLinear2Bounds* root_level_bounds_; SharedStatistics* shared_stats_; + Linear2WithPotentialNonTrivalBounds* non_trivial_bounds_; int64_t num_conditional_relation_updates_ = 0; @@ -476,6 +539,7 @@ class Linear2BoundsFromLinear3 { GenericLiteralWatcher* watcher_; SharedStatistics* shared_stats_; RootLevelLinear2Bounds* root_level_bounds_; + Linear2WithPotentialNonTrivalBounds* non_trivial_bounds_; int64_t num_affine_updates_ = 0; @@ -558,7 +622,8 @@ class Linear2Bounds { GetAllExpressionsWithPotentialNonTrivialBounds( Bitset64::ConstView var_set) const; - // Returns a temporay bitset, cleared, and resized for all existing variables. + // Returns a temporary bitset, cleared, and resized for all existing + // variables. // // If we have many class calling // GetAllExpressionsWithPotentialNonTrivialBounds() it is important that not diff --git a/ortools/third_party_solvers/BUILD.bazel b/ortools/third_party_solvers/BUILD.bazel index a931395271..7b2ee8fefb 100644 --- a/ortools/third_party_solvers/BUILD.bazel +++ b/ortools/third_party_solvers/BUILD.bazel @@ -57,6 +57,7 @@ cc_library( "//ortools/base", "//ortools/base:file", "//ortools/base:status_macros", + "@abseil-cpp//absl/base:core_headers", "@abseil-cpp//absl/status", "@abseil-cpp//absl/status:statusor", "@abseil-cpp//absl/strings", diff --git a/ortools/third_party_solvers/CMakeLists.txt b/ortools/third_party_solvers/CMakeLists.txt index ac5bf08d0d..97232c4000 100644 --- a/ortools/third_party_solvers/CMakeLists.txt +++ b/ortools/third_party_solvers/CMakeLists.txt @@ -32,5 +32,5 @@ target_link_libraries(${NAME} PRIVATE absl::status absl::strings absl::str_format - absl::synchronization - ) + absl::synchronization) + diff --git a/ortools/third_party_solvers/gurobi_environment.cc b/ortools/third_party_solvers/gurobi_environment.cc index ba2d3ae212..007e766527 100644 --- a/ortools/third_party_solvers/gurobi_environment.cc +++ b/ortools/third_party_solvers/gurobi_environment.cc @@ -13,16 +13,16 @@ #include "ortools/third_party_solvers/gurobi_environment.h" +#include #include -#include #include #include +#include "absl/base/no_destructor.h" #include "absl/status/status.h" #include "absl/strings/str_cat.h" #include "absl/strings/str_join.h" #include "absl/strings/string_view.h" -#include "absl/synchronization/mutex.h" #include "ortools/base/logging.h" #include "ortools/third_party_solvers/dynamic_library.h" @@ -332,12 +332,14 @@ void LoadGurobiFunctions(DynamicLibrary* gurobi_dynamic_library) { gurobi_dynamic_library->GetFunction(&GRBplatform, "GRBplatform"); } +// clang-format on + std::vector GurobiDynamicLibraryPotentialPaths() { std::vector potential_paths; const std::vector kGurobiVersions = { - "1202", "1201", "1200", "1103", "1102", "1101", "1100", "1003", - "1002", "1001", "1000", "952", "951", "950", "911", - "910", "903", "902", "811", "801", "752"}; + "1202", "1201", "1200", "1103", "1102", "1101", "1100", + "1003", "1002", "1001", "1000", "952", "951", "950", + "911", "910", "903", "902", "811", "801", "752"}; potential_paths.reserve(kGurobiVersions.size() * 3); // Look for libraries pointed by GUROBI_HOME first. @@ -396,7 +398,7 @@ std::vector GurobiDynamicLibraryPotentialPaths() { #if defined(__GNUC__) // path in linux64 gurobi/optimizer docker image. for (const absl::string_view version : - {"12.0.2","12.0.1", "12.0.0", "11.0.3", "11.0.2", "11.0.1", "11.0.0", + {"12.0.2", "12.0.1", "12.0.0", "11.0.3", "11.0.2", "11.0.1", "11.0.0", "10.0.3", "10.0.2", "10.0.1", "10.0.0", "9.5.2", "9.5.1", "9.5.0"}) { potential_paths.push_back( absl::StrCat("/opt/gurobi/linux64/lib/libgurobi.so.", version)); @@ -407,37 +409,47 @@ std::vector GurobiDynamicLibraryPotentialPaths() { absl::Status LoadGurobiDynamicLibrary( std::vector potential_paths) { - static std::once_flag gurobi_loading_done; - static absl::Status gurobi_load_status; - static DynamicLibrary gurobi_library; - static absl::Mutex mutex; + struct GurobiLibraryStruct { + absl::Status gurobi_load_status; + DynamicLibrary gurobi_library; + }; - absl::MutexLock lock(&mutex); - - std::call_once(gurobi_loading_done, [&potential_paths]() { - const std::vector canonical_paths = - GurobiDynamicLibraryPotentialPaths(); - potential_paths.insert(potential_paths.end(), canonical_paths.begin(), - canonical_paths.end()); + static absl::NoDestructor loaded([&potential_paths]() { + GurobiLibraryStruct result; + // Try to load the library from the potential paths. for (const absl::string_view path : potential_paths) { - if (gurobi_library.TryToLoad(path)) { - LOG(INFO) << "Found the Gurobi library in '" << path << "."; + if (result.gurobi_library.TryToLoad(path)) { + VLOG(1) << "Found the Gurobi library in '" << path << "."; break; } } - if (gurobi_library.LibraryIsLoaded()) { - LoadGurobiFunctions(&gurobi_library); - gurobi_load_status = absl::OkStatus(); + // Fallback to the canonical paths. + if (!result.gurobi_library.LibraryIsLoaded()) { + const std::vector canonical_paths = + GurobiDynamicLibraryPotentialPaths(); + for (const absl::string_view path : canonical_paths) { + if (result.gurobi_library.TryToLoad(path)) { + VLOG(1) << "Found the Gurobi library in '" << path << "."; + break; + } + } + } + + if (result.gurobi_library.LibraryIsLoaded()) { + LoadGurobiFunctions(&result.gurobi_library); + result.gurobi_load_status = absl::OkStatus(); } else { - gurobi_load_status = absl::NotFoundError(absl::StrCat( + result.gurobi_load_status = absl::NotFoundError(absl::StrCat( "Could not find the Gurobi shared library. Looked in: [", absl::StrJoin(potential_paths, "', '"), "]. If you know where it" " is, pass the full path to 'LoadGurobiDynamicLibrary()'.")); } - }); - return gurobi_load_status; + return result; + }()); + + return loaded->gurobi_load_status; } } // namespace operations_research diff --git a/ortools/third_party_solvers/gurobi_environment.h b/ortools/third_party_solvers/gurobi_environment.h index 6bb4e10ec4..c1985daf3f 100644 --- a/ortools/third_party_solvers/gurobi_environment.h +++ b/ortools/third_party_solvers/gurobi_environment.h @@ -27,7 +27,6 @@ #endif extern "C" { - typedef struct _GRBmodel GRBmodel; typedef struct _GRBenv GRBenv; typedef struct _GRBsvec {