|
18 | 18 | #include <cstddef> |
19 | 19 | #include <cstdint> |
20 | 20 | #include <functional> |
| 21 | +#include <limits> |
21 | 22 | #include <memory> |
22 | 23 | #include <optional> |
23 | 24 | #include <random> |
|
30 | 31 | #include "absl/time/time.h" |
31 | 32 | #include "absl/types/span.h" |
32 | 33 | #include "google/protobuf/repeated_ptr_field.h" |
33 | | -#include "ortools/base/protoutil.h" |
34 | 34 | #include "ortools/constraint_solver/constraint_solver.h" |
35 | 35 | #include "ortools/constraint_solver/routing.h" |
36 | 36 | #include "ortools/constraint_solver/routing_ils.pb.h" |
@@ -468,6 +468,121 @@ class AllNodesPerformedAcceptanceCriterion |
468 | 468 | const RoutingModel& model_; |
469 | 469 | }; |
470 | 470 |
|
| 471 | +// Returns the number of performed non-start/end nodes in the given assignment. |
| 472 | +int CountPerformedNodes(const RoutingModel& model, |
| 473 | + const Assignment& assignment) { |
| 474 | + int count = 0; |
| 475 | + for (int v = 0; v < model.vehicles(); ++v) { |
| 476 | + int64_t current_node_index = model.Start(v); |
| 477 | + while (true) { |
| 478 | + current_node_index = assignment.Value(model.NextVar(current_node_index)); |
| 479 | + if (model.IsEnd(current_node_index)) { |
| 480 | + break; |
| 481 | + } |
| 482 | + count++; |
| 483 | + } |
| 484 | + } |
| 485 | + return count; |
| 486 | +} |
| 487 | + |
| 488 | +// Acceptance criterion in which a candidate assignment is accepted when it |
| 489 | +// performs at least one more node than the reference assignment. |
| 490 | +class MoreNodesPerformedAcceptanceCriterion |
| 491 | + : public NeighborAcceptanceCriterion { |
| 492 | + public: |
| 493 | + explicit MoreNodesPerformedAcceptanceCriterion(const RoutingModel& model) |
| 494 | + : model_(model) {} |
| 495 | + |
| 496 | + bool Accept([[maybe_unused]] const SearchState& search_state, |
| 497 | + const Assignment* candidate, |
| 498 | + const Assignment* reference) override { |
| 499 | + return CountPerformedNodes(model_, *candidate) > |
| 500 | + CountPerformedNodes(model_, *reference); |
| 501 | + } |
| 502 | + |
| 503 | + private: |
| 504 | + const RoutingModel& model_; |
| 505 | +}; |
| 506 | + |
| 507 | +class AbsencesBasedAcceptanceCriterion : public NeighborAcceptanceCriterion { |
| 508 | + public: |
| 509 | + explicit AbsencesBasedAcceptanceCriterion( |
| 510 | + const RoutingModel& model, bool remove_route_with_lowest_absences) |
| 511 | + : model_(model), |
| 512 | + remove_route_with_lowest_absences_(remove_route_with_lowest_absences), |
| 513 | + absences_(model.Size(), 0) {} |
| 514 | + |
| 515 | + bool Accept([[maybe_unused]] const SearchState& search_state, |
| 516 | + const Assignment* candidate, |
| 517 | + const Assignment* reference) override { |
| 518 | + int sum_candidate_absences = 0; |
| 519 | + int sum_reference_absences = 0; |
| 520 | + for (int node = 0; node < model_.Size(); ++node) { |
| 521 | + if (model_.IsStart(node) || model_.IsEnd(node)) continue; |
| 522 | + if (candidate->Value(model_.NextVar(node)) == node) { |
| 523 | + sum_candidate_absences += absences_[node]; |
| 524 | + } |
| 525 | + if (reference->Value(model_.NextVar(node)) == node) { |
| 526 | + sum_reference_absences += absences_[node]; |
| 527 | + } |
| 528 | + } |
| 529 | + return sum_candidate_absences < sum_reference_absences; |
| 530 | + } |
| 531 | + |
| 532 | + void OnIterationEnd(const Assignment* reference) override { |
| 533 | + for (int node = 0; node < model_.Size(); ++node) { |
| 534 | + if (model_.IsStart(node) || model_.IsEnd(node)) continue; |
| 535 | + if (reference->Value(model_.NextVar(node)) == node) { |
| 536 | + ++absences_[node]; |
| 537 | + } |
| 538 | + } |
| 539 | + } |
| 540 | + |
| 541 | + void OnBestSolutionFound(Assignment* reference) override { |
| 542 | + if (!remove_route_with_lowest_absences_) return; |
| 543 | + |
| 544 | + int candidate_route = -1; |
| 545 | + int min_sum_absences = std::numeric_limits<int>::max(); |
| 546 | + |
| 547 | + for (int route = 0; route < model_.vehicles(); ++route) { |
| 548 | + if (model_.Next(*reference, model_.Start(route)) == model_.End(route)) |
| 549 | + continue; |
| 550 | + int sum_absences = 0; |
| 551 | + for (int64_t node = reference->Value(model_.NextVar(model_.Start(route))); |
| 552 | + node != model_.End(route); |
| 553 | + node = reference->Value(model_.NextVar(node))) { |
| 554 | + sum_absences += absences_[node]; |
| 555 | + } |
| 556 | + |
| 557 | + if (sum_absences < min_sum_absences) { |
| 558 | + candidate_route = route; |
| 559 | + min_sum_absences = sum_absences; |
| 560 | + } |
| 561 | + } |
| 562 | + |
| 563 | + // Remove the route with the lowest sum of absences. |
| 564 | + if (candidate_route != -1) { |
| 565 | + // Set next pointers for inner nodes. |
| 566 | + int64_t node = |
| 567 | + reference->Value(model_.NextVar(model_.Start(candidate_route))); |
| 568 | + while (node != model_.End(candidate_route)) { |
| 569 | + const int64_t next_node = reference->Value(model_.NextVar(node)); |
| 570 | + reference->SetValue(model_.NextVar(node), node); |
| 571 | + reference->SetValue(model_.VehicleVar(node), -1); |
| 572 | + node = next_node; |
| 573 | + } |
| 574 | + // Set next pointer for start node. |
| 575 | + reference->SetValue(model_.NextVar(model_.Start(candidate_route)), |
| 576 | + model_.End(candidate_route)); |
| 577 | + } |
| 578 | + } |
| 579 | + |
| 580 | + private: |
| 581 | + const RoutingModel& model_; |
| 582 | + bool remove_route_with_lowest_absences_; |
| 583 | + std::vector<int> absences_; |
| 584 | +}; |
| 585 | + |
471 | 586 | // Returns whether the given assignment has at least one performed node. |
472 | 587 | bool HasPerformedNodes(const RoutingModel& model, |
473 | 588 | const Assignment& assignment) { |
@@ -1166,6 +1281,12 @@ std::unique_ptr<NeighborAcceptanceCriterion> MakeNeighborAcceptanceCriterion( |
1166 | 1281 | rnd); |
1167 | 1282 | case AcceptanceStrategy::kAllNodesPerformed: |
1168 | 1283 | return std::make_unique<AllNodesPerformedAcceptanceCriterion>(model); |
| 1284 | + case AcceptanceStrategy::kMoreNodesPerformed: |
| 1285 | + return std::make_unique<MoreNodesPerformedAcceptanceCriterion>(model); |
| 1286 | + case AcceptanceStrategy::kAbsencesBased: |
| 1287 | + return std::make_unique<AbsencesBasedAcceptanceCriterion>( |
| 1288 | + model, acceptance_strategy.absences_based() |
| 1289 | + .remove_route_with_lowest_absences()); |
1169 | 1290 | default: |
1170 | 1291 | LOG(DFATAL) << "Unsupported acceptance strategy."; |
1171 | 1292 | return nullptr; |
|
0 commit comments