diff --git a/ortools/constraint_solver/routing.cc b/ortools/constraint_solver/routing.cc index 3e91a0d867..2f2e3c08d8 100644 --- a/ortools/constraint_solver/routing.cc +++ b/ortools/constraint_solver/routing.cc @@ -3580,12 +3580,29 @@ const Assignment* RoutingModel::SolveWithIteratedLocalSearch( return true; }; - std::unique_ptr acceptance_criterion = - MakeNeighborAcceptanceCriterion(*this, parameters, &rnd); + const IteratedLocalSearchParameters& ils_parameters = + parameters.iterated_local_search_parameters(); + + const absl::Duration final_duration = + !parameters.has_time_limit() + ? absl::InfiniteDuration() + : util_time::DecodeGoogleApiProto(parameters.time_limit()).value(); + + NeighborAcceptanceCriterion::SearchState final_search_state = { + final_duration, parameters.solution_limit()}; + + std::unique_ptr reference_acceptance_criterion = + MakeNeighborAcceptanceCriterion( + *this, ils_parameters.reference_solution_acceptance_strategy(), + final_search_state, &rnd); + + std::unique_ptr best_acceptance_criterion = + MakeNeighborAcceptanceCriterion( + *this, ils_parameters.best_solution_acceptance_strategy(), + final_search_state, &rnd); const bool improve_perturbed_solution = - parameters.iterated_local_search_parameters() - .improve_perturbed_solution(); + ils_parameters.improve_perturbed_solution(); while (update_time_limits() && explored_solutions < parameters.solution_limit()) { @@ -3610,15 +3627,17 @@ const Assignment* RoutingModel::SolveWithIteratedLocalSearch( } } - if (neighbor_solution->ObjectiveValue() < best_solution->ObjectiveValue()) { + absl::Duration elapsed_time = + absl::Milliseconds(solver_->wall_time() - start_time_ms); + + if (best_acceptance_criterion->Accept({elapsed_time, explored_solutions}, + neighbor_solution, best_solution)) { best_solution->CopyIntersection(neighbor_solution); } - absl::Duration elapsed_time = - absl::Milliseconds(solver_->wall_time() - start_time_ms); - if (acceptance_criterion->Accept({elapsed_time, explored_solutions}, - neighbor_solution, - last_accepted_solution)) { + if (reference_acceptance_criterion->Accept( + {elapsed_time, explored_solutions}, neighbor_solution, + last_accepted_solution)) { // Note that the perturbation_db is using last_accepted_solution as // reference assignment. By updating last_accepted_solution here we thus // also keep the perturbation_db reference assignment up to date. diff --git a/ortools/constraint_solver/routing_ils.cc b/ortools/constraint_solver/routing_ils.cc index a8cc1801fe..ddfcebcf19 100644 --- a/ortools/constraint_solver/routing_ils.cc +++ b/ortools/constraint_solver/routing_ils.cc @@ -385,33 +385,20 @@ class LinearCoolingSchedule : public CoolingSchedule { // Returns a cooling schedule based on the given input parameters. std::unique_ptr MakeCoolingSchedule( - const RoutingModel& model, const RoutingSearchParameters& parameters, + const RoutingModel& model, + const SimulatedAnnealingAcceptanceStrategy& sa_params, + const NeighborAcceptanceCriterion::SearchState& final_search_state, std::mt19937* rnd) { - const absl::Duration final_duration = - !parameters.has_time_limit() - ? absl::InfiniteDuration() - : util_time::DecodeGoogleApiProto(parameters.time_limit()).value(); - - const SimulatedAnnealingParameters& sa_params = - parameters.iterated_local_search_parameters() - .simulated_annealing_parameters(); - - NeighborAcceptanceCriterion::SearchState final_search_state{ - final_duration, parameters.solution_limit()}; - const auto [initial_temperature, final_temperature] = GetSimulatedAnnealingTemperatures(model, sa_params, rnd); switch (sa_params.cooling_schedule_strategy()) { case CoolingScheduleStrategy::EXPONENTIAL: return std::make_unique( - NeighborAcceptanceCriterion::SearchState{final_duration, - parameters.solution_limit()}, - initial_temperature, final_temperature); + final_search_state, initial_temperature, final_temperature); case CoolingScheduleStrategy::LINEAR: return std::make_unique( - std::move(final_search_state), initial_temperature, - final_temperature); + final_search_state, initial_temperature, final_temperature); default: LOG(DFATAL) << "Unsupported cooling schedule strategy."; return nullptr; @@ -444,6 +431,43 @@ class SimulatedAnnealingAcceptanceCriterion std::uniform_real_distribution probability_distribution_; }; +// Acceptance criterion in which a candidate assignment is accepted when it has +// all nodes performed. +class AllNodesPerformedAcceptanceCriterion + : public NeighborAcceptanceCriterion { + public: + explicit AllNodesPerformedAcceptanceCriterion(const RoutingModel& model) + : model_(model) {} + + bool Accept([[maybe_unused]] const SearchState& search_state, + const Assignment* candidate, + [[maybe_unused]] const Assignment* reference) override { + for (RoutingModel::DisjunctionIndex d(0); + d < model_.GetNumberOfDisjunctions(); ++d) { + // This solution avoids counting non-fixed variables as inactive. + int num_possible_actives = model_.GetDisjunctionNodeIndices(d).size(); + for (const int64_t node : model_.GetDisjunctionNodeIndices(d)) { + if (candidate->Value(model_.NextVar(node)) == node) { + --num_possible_actives; + } + } + if (num_possible_actives < model_.GetDisjunctionMaxCardinality(d)) { + return false; + } + } + + for (int node = 0; node < model_.Size(); ++node) { + if (model_.IsStart(node) || model_.IsEnd(node)) continue; + if (!model_.GetDisjunctionIndices(node).empty()) continue; + if (candidate->Value(model_.NextVar(node)) == node) return false; + } + return true; + } + + private: + const RoutingModel& model_; +}; + // Returns whether the given assignment has at least one performed node. bool HasPerformedNodes(const RoutingModel& model, const Assignment& assignment) { @@ -479,21 +503,21 @@ double ComputeAverageNonEmptyRouteSize(const RoutingModel& model, // performed visits. int64_t PickRandomPerformedVisit( const RoutingModel& model, const Assignment& assignment, std::mt19937& rnd, - std::uniform_int_distribution& customer_dist) { - DCHECK_EQ(customer_dist.min(), 0); - DCHECK_EQ(customer_dist.max(), model.Size() - model.vehicles()); + std::uniform_int_distribution& node_dist) { + DCHECK_EQ(node_dist.min(), 0); + DCHECK_EQ(node_dist.max(), model.Size() - model.vehicles()); if (!HasPerformedNodes(model, assignment)) { return -1; } - int64_t customer; + int64_t node; do { - customer = customer_dist(rnd); - } while (model.IsStart(customer) || - assignment.Value(model.VehicleVar(customer)) == -1); - DCHECK(!model.IsEnd(customer)); - return customer; + node = node_dist(rnd); + } while (model.IsStart(node) || + assignment.Value(model.VehicleVar(node)) == -1); + DCHECK(!model.IsEnd(node)); + return node; } } // namespace @@ -546,20 +570,20 @@ void RoutingSolution::InitializeRouteInfoIfNeeded(int vehicle) { prevs_[end] = prev; } -bool RoutingSolution::BelongsToInitializedRoute(int64_t node_index) const { - DCHECK_EQ(nexts_[node_index] != -1, prevs_[node_index] != -1); - return nexts_[node_index] != -1; +bool RoutingSolution::BelongsToInitializedRoute(int64_t node) const { + DCHECK_EQ(nexts_[node] != -1, prevs_[node] != -1); + return nexts_[node] != -1; } -int64_t RoutingSolution::GetNextNodeIndex(int64_t node_index) const { - return BelongsToInitializedRoute(node_index) - ? nexts_[node_index] - : assignment_->Value(model_.NextVar(node_index)); +int64_t RoutingSolution::GetNextNodeIndex(int64_t node) const { + return BelongsToInitializedRoute(node) + ? nexts_[node] + : assignment_->Value(model_.NextVar(node)); } -int64_t RoutingSolution::GetInitializedPrevNodeIndex(int64_t node_index) const { - DCHECK(BelongsToInitializedRoute(node_index)); - return prevs_[node_index]; +int64_t RoutingSolution::GetInitializedPrevNodeIndex(int64_t node) const { + DCHECK(BelongsToInitializedRoute(node)); + return prevs_[node]; } int RoutingSolution::GetRouteSize(int vehicle) const { @@ -567,37 +591,40 @@ int RoutingSolution::GetRouteSize(int vehicle) const { return route_sizes_[vehicle]; } -bool RoutingSolution::CanBeRemoved(int64_t node_index) const { - return !model_.IsStart(node_index) && !model_.IsEnd(node_index) && - GetNextNodeIndex(node_index) != node_index; +bool RoutingSolution::CanBeRemoved(int64_t node) const { + return !model_.IsStart(node) && !model_.IsEnd(node) && + GetNextNodeIndex(node) != node; } -void RoutingSolution::RemoveNode(int64_t node_index) { - DCHECK(BelongsToInitializedRoute(node_index)); +void RoutingSolution::RemoveNode(int64_t node) { + DCHECK(BelongsToInitializedRoute(node)); - DCHECK_NE(nexts_[node_index], node_index); - DCHECK_NE(prevs_[node_index], node_index); + DCHECK_NE(nexts_[node], node); + DCHECK_NE(prevs_[node], node); - const int64_t next = nexts_[node_index]; - const int64_t prev = prevs_[node_index]; + const int64_t next = nexts_[node]; + const int64_t prev = prevs_[node]; - const int vehicle = assignment_->Value(model_.VehicleVar(node_index)); + const int vehicle = assignment_->Value(model_.VehicleVar(node)); --route_sizes_[vehicle]; DCHECK_GE(route_sizes_[vehicle], 0); nexts_[prev] = next; prevs_[next] = prev; - nexts_[node_index] = node_index; - prevs_[node_index] = node_index; + nexts_[node] = node; + prevs_[node] = node; } -void RoutingSolution::RemovePerformedPickupDeliverySibling(int64_t customer) { - DCHECK(!model_.IsStart(customer)); - DCHECK(!model_.IsEnd(customer)); +void RoutingSolution::RemovePerformedPickupDeliverySibling(int64_t node) { + DCHECK(!model_.IsStart(node)); + DCHECK(!model_.IsEnd(node)); if (const std::optional sibling_node = model_.GetFirstMatchingPickupDeliverySibling( - customer, [this](int64_t node) { return CanBeRemoved(node); }); + node, + [this](int64_t candidate_node) { + return CanBeRemoved(candidate_node); + }); sibling_node.has_value()) { const int sibling_vehicle = assignment_->Value(model_.VehicleVar(sibling_node.value())); @@ -753,7 +780,7 @@ CloseRoutesRemovalRuinProcedure::CloseRoutesRemovalRuinProcedure( /*only_sort_neighbors_for_partial_neighborhoods=*/false})), num_routes_(num_routes), rnd_(*rnd), - customer_dist_(0, model->Size() - model->vehicles()), + node_dist_(0, model->Size() - model->vehicles()), removed_routes_(model->vehicles()) {} std::function CloseRoutesRemovalRuinProcedure::Ruin( @@ -765,7 +792,7 @@ std::function CloseRoutesRemovalRuinProcedure::Ruin( } const int64_t seed_node = - PickRandomPerformedVisit(model_, *assignment, rnd_, customer_dist_); + PickRandomPerformedVisit(model_, *assignment, rnd_, node_dist_); if (seed_node == -1) { return [this, assignment](int64_t node) { return assignment->Value(model_.NextVar(node)); @@ -799,7 +826,7 @@ std::function CloseRoutesRemovalRuinProcedure::Ruin( } return [this, assignment](int64_t node) { - // Shortcut removed routes to remove associated customers. + // Shortcut removed routes to remove associated nodes. if (model_.IsStart(node)) { const int route = assignment->Value(model_.VehicleVar(node)); if (removed_routes_[route]) { @@ -822,7 +849,7 @@ RandomWalkRemovalRuinProcedure::RandomWalkRemovalRuinProcedure( /*only_sort_neighbors_for_partial_neighborhoods=*/false})), rnd_(*rnd), walk_length_(walk_length), - customer_dist_(0, model->Size() - model->vehicles()) {} + node_dist_(0, model->Size() - model->vehicles()) {} std::function RandomWalkRemovalRuinProcedure::Ruin( const Assignment* assignment) { @@ -833,7 +860,7 @@ std::function RandomWalkRemovalRuinProcedure::Ruin( } int64_t curr_node = - PickRandomPerformedVisit(model_, *assignment, rnd_, customer_dist_); + PickRandomPerformedVisit(model_, *assignment, rnd_, node_dist_); if (curr_node == -1) { return [this, assignment](int64_t node) { return assignment->Value(model_.NextVar(node)); @@ -908,8 +935,8 @@ int64_t RandomWalkRemovalRuinProcedure::GetNextNodeToRemove( return neighbor; } - // If we are not able to find a customer in another route, we are ok - // with taking a customer from the current one. + // If we are not able to find a node in another route, we are ok + // with taking a node from the current one. // Note that it can be -1 if no removable neighbor was found for the input // node. return same_route_closest_neighbor; @@ -929,7 +956,7 @@ SISRRuinProcedure::SISRRuinProcedure(RoutingModel* model, std::mt19937* rnd, /*add_vehicle_starts_to_neighbors=*/false, /*add_vehicle_ends_to_neighbors=*/false, /*only_sort_neighbors_for_partial_neighborhoods=*/false})), - customer_dist_(0, model->Size() - model->vehicles()), + node_dist_(0, model->Size() - model->vehicles()), probability_dist_(0.0, 1.0), ruined_routes_(model->vehicles()), routing_solution_(*model) {} @@ -937,7 +964,7 @@ SISRRuinProcedure::SISRRuinProcedure(RoutingModel* model, std::mt19937* rnd, std::function SISRRuinProcedure::Ruin( const Assignment* assignment) { const int64_t seed_node = - PickRandomPerformedVisit(model_, *assignment, rnd_, customer_dist_); + PickRandomPerformedVisit(model_, *assignment, rnd_, node_dist_); if (seed_node == -1) { return [this, assignment](int64_t node) { return assignment->Value(model_.NextVar(node)); @@ -1126,15 +1153,19 @@ DecisionBuilder* MakePerturbationDecisionBuilder( } std::unique_ptr MakeNeighborAcceptanceCriterion( - const RoutingModel& model, const RoutingSearchParameters& parameters, + const RoutingModel& model, const AcceptanceStrategy& acceptance_strategy, + const NeighborAcceptanceCriterion::SearchState& final_search_state, std::mt19937* rnd) { - CHECK(parameters.has_iterated_local_search_parameters()); - switch (parameters.iterated_local_search_parameters().acceptance_strategy()) { - case AcceptanceStrategy::GREEDY_DESCENT: + switch (acceptance_strategy.strategy_case()) { + case AcceptanceStrategy::kGreedyDescent: return std::make_unique(); - case AcceptanceStrategy::SIMULATED_ANNEALING: + case AcceptanceStrategy::kSimulatedAnnealing: return std::make_unique( - MakeCoolingSchedule(model, parameters, rnd), rnd); + MakeCoolingSchedule(model, acceptance_strategy.simulated_annealing(), + final_search_state, rnd), + rnd); + case AcceptanceStrategy::kAllNodesPerformed: + return std::make_unique(model); default: LOG(DFATAL) << "Unsupported acceptance strategy."; return nullptr; @@ -1142,8 +1173,8 @@ std::unique_ptr MakeNeighborAcceptanceCriterion( } std::pair GetSimulatedAnnealingTemperatures( - const RoutingModel& model, const SimulatedAnnealingParameters& sa_params, - std::mt19937* rnd) { + const RoutingModel& model, + const SimulatedAnnealingAcceptanceStrategy& sa_params, std::mt19937* rnd) { if (!sa_params.automatic_temperatures()) { return {sa_params.initial_temperature(), sa_params.final_temperature()}; } diff --git a/ortools/constraint_solver/routing_ils.h b/ortools/constraint_solver/routing_ils.h index 8d5800d18a..c783355c4f 100644 --- a/ortools/constraint_solver/routing_ils.h +++ b/ortools/constraint_solver/routing_ils.h @@ -43,30 +43,30 @@ class RoutingSolution { // vehicle, if not already done. void InitializeRouteInfoIfNeeded(int vehicle); - // Returns whether node_index belongs to a route that has been initialized. - bool BelongsToInitializedRoute(int64_t node_index) const; + // Returns whether node belongs to a route that has been initialized. + bool BelongsToInitializedRoute(int64_t node) const; - // Returns the next node index of the given node_index. - int64_t GetNextNodeIndex(int64_t node_index) const; + // Returns the next node index of the given node. + int64_t GetNextNodeIndex(int64_t node) const; - // Returns the previous node index of the given node_index. - // This must be called for node_index belonging to initialized routes. - int64_t GetInitializedPrevNodeIndex(int64_t node_index) const; + // Returns the previous node index of the given node. + // This must be called for node belonging to initialized routes. + int64_t GetInitializedPrevNodeIndex(int64_t node) const; // Returns the number of visits performed by the given vehicle. // This must be called for a vehicle associated with an initialized route. int GetRouteSize(int vehicle) const; - // Returns whether node_index can be removed from the solution. - // This must be called for node_index belonging to initialized routes. - bool CanBeRemoved(int64_t node_index) const; + // Returns whether node can be removed from the solution. + // This must be called for node belonging to initialized routes. + bool CanBeRemoved(int64_t node) const; - // Removes the node with the given node_index. - // This must be called for node_index belonging to initialized routes. - void RemoveNode(int64_t node_index); + // Removes the node with the given node. + // This must be called for node belonging to initialized routes. + void RemoveNode(int64_t node); - // Removes the performed sibling pickup or delivery of customer, if any. - void RemovePerformedPickupDeliverySibling(int64_t customer); + // Removes the performed sibling pickup or delivery of node, if any. + void RemovePerformedPickupDeliverySibling(int64_t node); // Randomly returns the next or previous visit of the given performed // visit. Returns -1 if there are no other available visits. When the @@ -115,7 +115,7 @@ class CloseRoutesRemovalRuinProcedure : public RuinProcedure { int num_neighbors_for_route_selection); // Returns next accessors where at most num_routes routes have been shortcut, // i.e., next(shortcut route begin) = shortcut route end. - // Next accessors for customers belonging to shortcut routes are still set to + // Next accessors for nodes belonging to shortcut routes are still set to // their original value and should not be used. std::function Ruin(const Assignment* assignment) override; @@ -124,7 +124,7 @@ class CloseRoutesRemovalRuinProcedure : public RuinProcedure { const RoutingModel::NodeNeighborsByCostClass* const neighbors_manager_; const size_t num_routes_; std::mt19937& rnd_; - std::uniform_int_distribution customer_dist_; + std::uniform_int_distribution node_dist_; SparseBitset removed_routes_; }; @@ -149,7 +149,7 @@ class RandomWalkRemovalRuinProcedure : public RuinProcedure { const RoutingModel::NodeNeighborsByCostClass* const neighbors_manager_; std::mt19937& rnd_; const int walk_length_; - std::uniform_int_distribution customer_dist_; + std::uniform_int_distribution node_dist_; std::bernoulli_distribution boolean_dist_; }; @@ -233,7 +233,7 @@ class SISRRuinProcedure : public RuinProcedure { int avg_num_removed_visits_; double bypass_factor_; const RoutingModel::NodeNeighborsByCostClass* const neighbors_manager_; - std::uniform_int_distribution customer_dist_; + std::uniform_int_distribution node_dist_; std::bernoulli_distribution boolean_dist_; std::uniform_real_distribution probability_dist_; SparseBitset ruined_routes_; @@ -269,14 +269,15 @@ class NeighborAcceptanceCriterion { // Returns a neighbor acceptance criterion based on the given parameters. std::unique_ptr MakeNeighborAcceptanceCriterion( - const RoutingModel& model, const RoutingSearchParameters& parameters, + const RoutingModel& model, const AcceptanceStrategy& acceptance_strategy, + const NeighborAcceptanceCriterion::SearchState& final_search_state, std::mt19937* rnd); // Returns initial and final simulated annealing temperatures according to the // given simulated annealing input parameters. std::pair GetSimulatedAnnealingTemperatures( - const RoutingModel& model, const SimulatedAnnealingParameters& sa_params, - std::mt19937* rnd); + const RoutingModel& model, + const SimulatedAnnealingAcceptanceStrategy& sa_params, std::mt19937* rnd); } // namespace operations_research diff --git a/ortools/constraint_solver/routing_ils.proto b/ortools/constraint_solver/routing_ils.proto index 468c52fb65..0a1369e007 100644 --- a/ortools/constraint_solver/routing_ils.proto +++ b/ortools/constraint_solver/routing_ils.proto @@ -36,7 +36,7 @@ message SpatiallyCloseRoutesRuinStrategy { optional uint32 num_ruined_routes = 3; } -// Ruin strategy that removes a number of customers by performing a random walk +// Ruin strategy that removes a number of nodes by performing a random walk // on the underlying routing solution. More precisely, starting from a randomly // selected seed visit, the walk is extended by either moving within the // same route or by jumping to a visit served by a different neighboring @@ -132,7 +132,7 @@ message SISRRuinStrategy { // paper is \bar{c} and the suggested value is 10. optional uint32 avg_num_removed_visits = 2; - // Value in [0, 1] ruling the number of preserved customers in the split + // Value in [0, 1] ruling the number of preserved nodes in the split // sequence removal. The parameter name in the paper is \alpha and the // suggested value is 0.01. optional double bypass_factor = 3; @@ -262,8 +262,12 @@ message CoolingScheduleStrategy { } } -// Specifies the behavior of a simulated annealing acceptance strategy. -message SimulatedAnnealingParameters { +// Acceptance strategy in which only improving solutions are accepted. +message GreedyDescentAcceptanceStrategy {} + +// Acceptance strategy in which solutions are accepted with a probability that +// depends on its quality and on the current state of the search. +message SimulatedAnnealingAcceptanceStrategy { // Determines the speed at which the temperature changes from initial to // final. CoolingScheduleStrategy.Value cooling_schedule_strategy = 1; @@ -287,21 +291,17 @@ message SimulatedAnnealingParameters { optional bool automatic_temperatures = 4; } -// Determines when a neighbor solution, obtained by the application of a -// perturbation and improvement step to a reference solution, is used to -// replace the reference solution. +// Acceptance strategy in which a solution is accepted only if all nodes +// are performed. Disjunctions are respected when several nodes can be +// performed. +message AllNodesPerformedAcceptanceStrategy {} + +// Determines when a candidate solution replaces another one. message AcceptanceStrategy { - enum Value { - // Unspecified value. - UNSET = 0; - - // Accepts only solutions that are improving with respect to the reference - // one. - GREEDY_DESCENT = 1; - - // Accepts a candidate solution with a probability that depends on its - // quality and on the current state of the search. - SIMULATED_ANNEALING = 2; + oneof strategy { + GreedyDescentAcceptanceStrategy greedy_descent = 1; + SimulatedAnnealingAcceptanceStrategy simulated_annealing = 2; + AllNodesPerformedAcceptanceStrategy all_nodes_performed = 3; } } @@ -320,9 +320,9 @@ message IteratedLocalSearchParameters { // Determines when the neighbor solution S', possibly improved if // `improve_perturbed_solution` is true, replaces the reference solution S. - AcceptanceStrategy.Value acceptance_strategy = 4; + AcceptanceStrategy reference_solution_acceptance_strategy = 4; - // Parameters to customize a simulated annealing acceptance strategy. These - // parameters are required iff the acceptance_strategy is SIMULATED_ANNEALING. - SimulatedAnnealingParameters simulated_annealing_parameters = 5; + // Determines when the neighbor solution S' replaces the best solution found + // so far. + AcceptanceStrategy best_solution_acceptance_strategy = 5; } diff --git a/ortools/constraint_solver/routing_parameters.cc b/ortools/constraint_solver/routing_parameters.cc index 92a1f90d52..c973351662 100644 --- a/ortools/constraint_solver/routing_parameters.cc +++ b/ortools/constraint_solver/routing_parameters.cc @@ -78,9 +78,10 @@ IteratedLocalSearchParameters CreateDefaultIteratedLocalSearchParameters() { rr->set_route_selection_min_neighbors(10); rr->set_route_selection_max_neighbors(100); ils.set_improve_perturbed_solution(true); - ils.set_acceptance_strategy(AcceptanceStrategy::GREEDY_DESCENT); - SimulatedAnnealingParameters* sa = - ils.mutable_simulated_annealing_parameters(); + ils.mutable_best_solution_acceptance_strategy()->mutable_greedy_descent(); + SimulatedAnnealingAcceptanceStrategy* sa = + ils.mutable_reference_solution_acceptance_strategy() + ->mutable_simulated_annealing(); sa->set_cooling_schedule_strategy(CoolingScheduleStrategy::EXPONENTIAL); sa->set_initial_temperature(100.0); sa->set_final_temperature(0.01); @@ -506,55 +507,70 @@ void FindErrorsInIteratedLocalSearchParameters( } } - if (ils.acceptance_strategy() == AcceptanceStrategy::UNSET) { + struct NamedAcceptanceStrategy { + std::string name; + AcceptanceStrategy acceptance_strategy; + }; + std::vector named_acceptance_strategies; + + if (!ils.has_reference_solution_acceptance_strategy()) { errors.emplace_back( - StrCat("Invalid value for " - "iterated_local_search_parameters.acceptance_strategy: ", - ils.acceptance_strategy())); + StrCat("Unset value for " + "iterated_local_search_parameters.reference_solution_acceptance_" + "strategy.")); + } else { + named_acceptance_strategies.push_back( + {"reference_solution", ils.reference_solution_acceptance_strategy()}); } - if (ils.acceptance_strategy() == AcceptanceStrategy::SIMULATED_ANNEALING) { - if (!ils.has_simulated_annealing_parameters()) { - errors.emplace_back( - StrCat("iterated_local_search_parameters.acceptance_strategy is ", - AcceptanceStrategy::SIMULATED_ANNEALING, - " but " - "iterated_local_search_parameters.simulated_annealing_" - "parameters are missing.")); - return; - } + if (!ils.has_best_solution_acceptance_strategy()) { + errors.emplace_back(StrCat( + "Unset value for " + "iterated_local_search_parameters.best_solution_acceptance_strategy.")); + } else { + named_acceptance_strategies.push_back( + {"best_solution", ils.best_solution_acceptance_strategy()}); + } - const SimulatedAnnealingParameters& sa_params = - ils.simulated_annealing_parameters(); + for (const auto& [name, acceptance_strategy] : named_acceptance_strategies) { + if (acceptance_strategy.has_simulated_annealing()) { + const SimulatedAnnealingAcceptanceStrategy& sa_params = + acceptance_strategy.simulated_annealing(); - if (sa_params.cooling_schedule_strategy() == - CoolingScheduleStrategy::UNSET) { - errors.emplace_back( - StrCat("Invalid value for " - "iterated_local_search_parameters.simulated_annealing_" - "parameters.cooling_schedule_strategy: ", - sa_params.cooling_schedule_strategy())); - } - - if (!sa_params.automatic_temperatures()) { - if (sa_params.initial_temperature() < sa_params.final_temperature()) { + if (sa_params.cooling_schedule_strategy() == + CoolingScheduleStrategy::UNSET) { errors.emplace_back( - "iterated_local_search_parameters.simulated_annealing_parameters." - "initial_temperature cannot be lower than " - "iterated_local_search_parameters.simulated_annealing_parameters." - "final_temperature."); + StrCat("Invalid value for " + "iterated_local_search_parameters.", + name, + "_acceptance_strategy.simulated_annealing.cooling_schedule_" + "strategy: ", + sa_params.cooling_schedule_strategy())); } - if (sa_params.initial_temperature() < 1e-9) { - errors.emplace_back( - "iterated_local_search_parameters.simulated_annealing_parameters." - "initial_temperature cannot be lower than 1e-9."); - } + if (!sa_params.automatic_temperatures()) { + if (sa_params.initial_temperature() < sa_params.final_temperature()) { + errors.emplace_back(StrCat( + "iterated_local_search_parameters.", name, + "_acceptance_strategy.simulated_annealing." + "initial_temperature cannot be lower than " + "iterated_local_search_parameters.simulated_annealing_parameters." + "final_temperature.")); + } - if (sa_params.final_temperature() < 1e-9) { - errors.emplace_back( - "iterated_local_search_parameters.simulated_annealing_parameters." - "final_temperature cannot be lower than 1e-9."); + if (sa_params.initial_temperature() < 1e-9) { + errors.emplace_back( + StrCat("iterated_local_search_parameters.", name, + "_acceptance_strategy.simulated_annealing." + "initial_temperature cannot be lower than 1e-9.")); + } + + if (sa_params.final_temperature() < 1e-9) { + errors.emplace_back( + StrCat("iterated_local_search_parameters.", name, + "_acceptance_strategy.simulated_annealing." + "final_temperature cannot be lower than 1e-9.")); + } } } }