diff --git a/cmake/docker/web/Dockerfile b/cmake/docker/web/Dockerfile index 1ebec3a45c..1dcefb864f 100644 --- a/cmake/docker/web/Dockerfile +++ b/cmake/docker/web/Dockerfile @@ -4,7 +4,7 @@ FROM archlinux:latest AS env # Install system build dependencies ENV PATH=/usr/local/bin:$PATH -RUN pacman -Syu --noconfirm git base-devel cmake +RUN pacman -Syu --noconfirm git base-devel glibc cmake RUN cmake -version RUN pacman -Syu --noconfirm nodejs emscripten ENV PATH=/usr/lib/emscripten:$PATH diff --git a/ortools/routing/csharp/routing.i b/ortools/routing/csharp/routing.i index 3b73692cf5..de6c137640 100644 --- a/ortools/routing/csharp/routing.i +++ b/ortools/routing/csharp/routing.i @@ -129,6 +129,33 @@ using System.Collections.Generic; using Google.OrTools.ConstraintSolver; using Domain = Google.OrTools.Util.Domain; %} +%typemap(cscode) RoutingDimension %{ + // Keep reference to delegate to avoid GC to collect them early. + private List limitCallbacks; + private IntIntToLong StoreIntIntToLong(IntIntToLong limit) { + if (limitCallbacks == null) + limitCallbacks = new List(); + limitCallbacks.Add(limit); + return limit; + } + + private List groupDelayCallbacks; + private LongLongToLong StoreLongLongToLong(LongLongToLong groupDelay) { + if (groupDelayCallbacks == null) + groupDelayCallbacks = new List(); + groupDelayCallbacks.Add(groupDelay); + return groupDelay; + } +%} +%ignore RoutingDimension::GetBreakDistanceDurationOfVehicle; + +// RoutingModel +%unignore RoutingModel; +%typemap(csimports) RoutingModel %{ +using System; +using System.Collections.Generic; +using Domain = Google.OrTools.Util.Domain; +%} %typemap(cscode) RoutingModel %{ // Keep reference to delegate to avoid GC to collect them early. private List unaryTransitCallbacks; diff --git a/ortools/routing/java/routing.i b/ortools/routing/java/routing.i index 36d1d25388..0746df8b94 100644 --- a/ortools/routing/java/routing.i +++ b/ortools/routing/java/routing.i @@ -71,6 +71,12 @@ namespace operations_research::routing { import com.google.ortools.constraintsolver.Constraint; %} +// GlobalVehicleBreaksConstraint +%typemap(javaimports) GlobalVehicleBreaksConstraint %{ +// Types from ConstraintSolver +import com.google.ortools.constraintsolver.Constraint; +%} + // RoutingModel %unignore RoutingModel; // Map transit callback to Java @FunctionalInterface types. diff --git a/ortools/routing/search.cc b/ortools/routing/search.cc index f545cbc964..f7e71c53ae 100644 --- a/ortools/routing/search.cc +++ b/ortools/routing/search.cc @@ -261,6 +261,189 @@ const Assignment* SolveFromAssignmentWithAlternativeSolversAndParameters( return best_assignment; } +namespace { +void ConvertAssignment(const RoutingModel* src_model, const Assignment* src, + const RoutingModel* dst_model, Assignment* dst) { + DCHECK_EQ(src_model->Nexts().size(), dst_model->Nexts().size()); + DCHECK_NE(src, nullptr); + DCHECK_EQ(src_model->solver(), src->solver()); + DCHECK_EQ(dst_model->solver(), dst->solver()); + for (int i = 0; i < src_model->Nexts().size(); i++) { + IntVar* const dst_next_var = dst_model->NextVar(i); + if (!dst->Contains(dst_next_var)) { + dst->Add(dst_next_var); + } + dst->SetValue(dst_next_var, src->Value(src_model->NextVar(i))); + } +} + +bool AreAssignmentsEquivalent(const RoutingModel& model, + const Assignment& assignment1, + const Assignment& assignment2) { + for (IntVar* next_var : model.Nexts()) { + if (assignment1.Value(next_var) != assignment2.Value(next_var)) { + return false; + } + } + return true; +} + +absl::Duration GetTimeLimit(const RoutingSearchParameters& search_parameters) { + return search_parameters.has_time_limit() + ? util_time::DecodeGoogleApiProto(search_parameters.time_limit()) + .value() + : absl::InfiniteDuration(); +} + +// TODO(user): Refactor this function with other versions living in +// routing.cc. +// TODO(user): Should we update solution limits as well? +bool UpdateTimeLimits(Solver* solver, int64_t start_time_ms, + absl::Duration time_limit, + RoutingSearchParameters& search_parameters) { + if (!search_parameters.has_time_limit()) return true; + const absl::Duration elapsed_time = + absl::Milliseconds(solver->wall_time() - start_time_ms); + const absl::Duration time_left = + std::min(time_limit - elapsed_time, GetTimeLimit(search_parameters)); + if (time_left <= absl::ZeroDuration()) return false; + const absl::Status status = util_time::EncodeGoogleApiProto( + time_left, search_parameters.mutable_time_limit()); + DCHECK_OK(status); + return true; +} +} // namespace + +const Assignment* SolveWithAlternativeSolvers( + RoutingModel* primary_model, + const std::vector& alternative_models, + const RoutingSearchParameters& parameters, + int max_non_improving_iterations) { + return SolveFromAssignmentWithAlternativeSolvers( + /*assignment=*/nullptr, primary_model, alternative_models, parameters, + max_non_improving_iterations); +} + +const Assignment* SolveFromAssignmentWithAlternativeSolvers( + const Assignment* assignment, RoutingModel* primary_model, + const std::vector& alternative_models, + const RoutingSearchParameters& parameters, + int max_non_improving_iterations) { + return SolveFromAssignmentWithAlternativeSolversAndParameters( + assignment, primary_model, parameters, alternative_models, {}, + max_non_improving_iterations); +} + +const Assignment* SolveFromAssignmentWithAlternativeSolversAndParameters( + const Assignment* assignment, RoutingModel* primary_model, + const RoutingSearchParameters& primary_parameters, + const std::vector& alternative_models, + const std::vector& alternative_parameters, + int max_non_improving_iterations) { + const int64_t start_time_ms = primary_model->solver()->wall_time(); + if (max_non_improving_iterations < 0) return nullptr; + // Shortcut if no alternative models will be explored. + if (max_non_improving_iterations == 0 || alternative_models.empty()) { + return primary_model->SolveFromAssignmentWithParameters(assignment, + primary_parameters); + } + RoutingSearchParameters mutable_search_parameters = primary_parameters; + // The first alternating phases are limited to greedy descent. The final pass + // at the end of this function will actually use the metaheuristic if needed. + // TODO(user): Add support for multiple metaheuristics. + const LocalSearchMetaheuristic_Value metaheuristic = + mutable_search_parameters.local_search_metaheuristic(); + const bool run_metaheuristic_phase = + metaheuristic != LocalSearchMetaheuristic::GREEDY_DESCENT && + metaheuristic != LocalSearchMetaheuristic::AUTOMATIC; + if (run_metaheuristic_phase) { + mutable_search_parameters.set_local_search_metaheuristic( + LocalSearchMetaheuristic::GREEDY_DESCENT); + } + std::vector mutable_alternative_parameters = + alternative_parameters; + DCHECK(mutable_alternative_parameters.empty() || + mutable_alternative_parameters.size() == alternative_models.size()); + for (RoutingSearchParameters& mutable_alternative_parameter : + mutable_alternative_parameters) { + if (!mutable_alternative_parameter.has_time_limit() && + mutable_search_parameters.has_time_limit()) { + *mutable_alternative_parameter.mutable_time_limit() = + mutable_search_parameters.time_limit(); + } + } + const absl::Duration time_limit = GetTimeLimit(mutable_search_parameters); + Assignment* best_assignment = primary_model->solver()->MakeAssignment(); + std::vector first_solutions; + first_solutions.reserve(alternative_models.size()); + for (RoutingModel* model : alternative_models) { + first_solutions.push_back(model->solver()->MakeAssignment()); + } + Assignment* primary_first_solution = + primary_model->solver()->MakeAssignment(); + Assignment* current_solution = primary_model->solver()->MakeAssignment(); + DCHECK(assignment == nullptr || + assignment->solver() == primary_model->solver()); + const Assignment* solution = primary_model->SolveFromAssignmentWithParameters( + assignment, mutable_search_parameters); + if (solution == nullptr) return nullptr; + best_assignment->Copy(solution); + int iteration = 0; + while (iteration < max_non_improving_iterations) { + if (best_assignment->ObjectiveValue() > solution->ObjectiveValue()) { + best_assignment->Copy(solution); + } + current_solution->Copy(solution); + for (int i = 0; i < alternative_models.size(); ++i) { + RoutingSearchParameters& mutable_alternative_parameter = + mutable_alternative_parameters.empty() + ? mutable_search_parameters + : mutable_alternative_parameters[i]; + if (!UpdateTimeLimits(primary_model->solver(), start_time_ms, time_limit, + mutable_alternative_parameter)) { + return best_assignment; + } + ConvertAssignment(primary_model, solution, alternative_models[i], + first_solutions[i]); + solution = alternative_models[i]->SolveFromAssignmentWithParameters( + first_solutions[i], mutable_alternative_parameter); + if (solution == nullptr) { + solution = first_solutions[i]; + } + if (!UpdateTimeLimits(primary_model->solver(), start_time_ms, time_limit, + mutable_search_parameters)) { + return best_assignment; + } + ConvertAssignment(alternative_models[i], solution, primary_model, + primary_first_solution); + solution = primary_model->SolveFromAssignmentWithParameters( + primary_first_solution, mutable_search_parameters); + if (solution == nullptr) return best_assignment; + } + // No modifications done in this iteration, no need to continue. + if (AreAssignmentsEquivalent(*primary_model, *solution, + *current_solution)) { + break; + } + // We're back to the best assignment which means we will cycle if we + // continue the search. + if (AreAssignmentsEquivalent(*primary_model, *solution, *best_assignment)) { + break; + } + if (solution->ObjectiveValue() >= best_assignment->ObjectiveValue()) { + ++iteration; + } + } + if (run_metaheuristic_phase && + UpdateTimeLimits(primary_model->solver(), start_time_ms, time_limit, + mutable_search_parameters)) { + mutable_search_parameters.set_local_search_metaheuristic(metaheuristic); + return primary_model->SolveFromAssignmentWithParameters( + best_assignment, mutable_search_parameters); + } + return best_assignment; +} + // --- VehicleTypeCurator --- void VehicleTypeCurator::Reset(const std::function& store_vehicle) { diff --git a/ortools/routing/search.h b/ortools/routing/search.h index 3aea0144fb..187ab757fe 100644 --- a/ortools/routing/search.h +++ b/ortools/routing/search.h @@ -48,6 +48,48 @@ namespace operations_research::routing { +// Solves a routing model using alternative models. This assumes that the models +// are equivalent in the sense that a solution to one model is also a solution +// to the other models. This is true for models that differ only by their arc +// costs or objective for instance. +// The primary model is the main model, to which the returned solution will +// correspond. +// The method solves the primary model and alternative models alternatively. +// It works as follows (all solves use 'parameters'): +// 1) solve the primary model with a greedy descent, +// 2) let 'alt' be the first alternative model, +// 3) solve 'alt' starting from the solution to the primary model with a greedy +// descent, +// 4) solve the primary model from the solution to 'alt' with a greedy descent, +// 5) if the new solution improves the best solution found so far, update it, +// otherwise increase the iteration counter, +// 6) if the iteration counter is less than 'max_non_improving_iterations', let +// 'alt' be the next "round-robin" alternative model, and go to step 3, +// 7) if 'parameters' specified a metaheuristic, solve the primary model using +// that metaheuristic starting from the best solution found so far, +// 8) return the best solution found. +// Note that if the time limit is reached at any stage, the search is +// interrupted and the best solution found will be returned immediately. +// TODO(user): Add a version taking search parameters for alternative models. +const Assignment* SolveWithAlternativeSolvers( + RoutingModel* primary_model, + const std::vector& alternative_models, + const RoutingSearchParameters& parameters, + int max_non_improving_iterations); +// Same as above, but taking an initial solution. +const Assignment* SolveFromAssignmentWithAlternativeSolvers( + const Assignment* assignment, RoutingModel* primary_model, + const std::vector& alternative_models, + const RoutingSearchParameters& parameters, + int max_non_improving_iterations); +// Same as above but taking alternative parameters for each alternative model. +const Assignment* SolveFromAssignmentWithAlternativeSolversAndParameters( + const Assignment* assignment, RoutingModel* primary_model, + const RoutingSearchParameters& parameters, + const std::vector& alternative_models, + const std::vector& alternative_parameters, + int max_non_improving_iterations); + // Solves a routing model using alternative models. This assumes that the models // are equivalent in the sense that a solution to one model is also a solution // to the other models. This is true for models that differ only by their arc