Skip to content

Commit 758fc34

Browse files
committed
constraint_solver: Make Relocate and Exchange aware of path constraints.
1 parent 40f74a0 commit 758fc34

3 files changed

Lines changed: 108 additions & 26 deletions

File tree

ortools/constraint_solver/java/constraint_solver.swig

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,6 @@ PROTECT_FROM_FAILURE(Solver::Fail(), arg1);
147147

148148
#include <vector>
149149

150-
#include "ortools/base/types.h"
151150
#include "ortools/constraint_solver/constraint_solver.h"
152151

153152
class RAIIExceptionChecker {

ortools/constraint_solver/local_search.cc

Lines changed: 48 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -556,8 +556,14 @@ bool Relocate<ignore_path_vars>::MakeNeighbor() {
556556
const int64_t destination = this->BaseNode(1);
557557
std::optional<int64_t> chain_end =
558558
this->GetChainEnd(before_chain, destination, chain_length_);
559-
return chain_end.has_value() &&
560-
this->MoveChain(before_chain, *chain_end, destination);
559+
if (!chain_end.has_value()) return false;
560+
const auto [has_change, is_feasible] = this->MoveChainWithCheck(
561+
before_chain, *chain_end, destination, /*check_path_compatibility=*/true);
562+
if (!is_feasible) {
563+
this->SetNextBaseToIncrement(0);
564+
return false;
565+
}
566+
return has_change;
561567
}
562568

563569
template <bool ignore_path_vars>
@@ -594,7 +600,10 @@ bool RelocateWithNeighbors<ignore_path_vars>::MakeNeighbor() {
594600
std::optional<int64_t> chain_end =
595601
this->GetChainEnd(before_chain, destination, this->chain_length_);
596602
if (!chain_end.has_value()) return false;
597-
return this->MoveChain(before_chain, *chain_end, destination);
603+
const auto [has_change, is_feasible] =
604+
this->MoveChainWithCheck(before_chain, *chain_end, destination,
605+
/*check_path_compatibility=*/true);
606+
return has_change && is_feasible;
598607
};
599608
const int64_t node0 = this->BaseNode(0);
600609
const auto [neighbor, outgoing] = this->GetNeighborForBaseNode(0);
@@ -663,14 +672,23 @@ bool Exchange<ignore_path_vars>::MakeNeighbor() {
663672
if (neighbor < 0 || this->IsInactive(neighbor)) return false;
664673
if (outgoing) {
665674
// Exchange node0's next with 'neighbor'.
666-
return this->SwapNodes(this->Next(node0), neighbor);
675+
const auto [has_change, is_feasible] =
676+
this->SwapNodesWithCheck(this->Next(node0), neighbor,
677+
/*check_path_compatibility=*/true);
678+
return has_change && is_feasible;
667679
}
668680
DCHECK(!this->IsPathStart(node0))
669681
<< "Path starts have no incoming neighbors.";
670682
// Exchange node0's prev with 'neighbor'.
671-
return this->SwapNodes(this->Prev(node0), neighbor);
683+
const auto [has_change, is_feasible] =
684+
this->SwapNodesWithCheck(this->Prev(node0), neighbor,
685+
/*check_path_compatibility=*/true);
686+
return has_change && is_feasible;
672687
}
673-
return this->SwapNodes(this->Next(node0), this->Next(this->BaseNode(1)));
688+
const auto [has_change, is_feasible] =
689+
this->SwapNodesWithCheck(this->Next(node0), this->Next(this->BaseNode(1)),
690+
/*check_path_compatibility=*/true);
691+
return has_change && is_feasible;
674692
}
675693

676694
LocalSearchOperator* MakeExchange(
@@ -741,6 +759,7 @@ bool Cross<ignore_path_vars>::MakeNeighbor() {
741759
if (start1 == start0 || node1 == start1) return false;
742760

743761
bool moved = false;
762+
bool feasible = true;
744763
if (cross_path_starts) {
745764
// Cross path starts.
746765
// If two paths are equivalent don't exchange the full paths.
@@ -751,10 +770,18 @@ bool Cross<ignore_path_vars>::MakeNeighbor() {
751770
return false;
752771
}
753772
const int first1 = this->Next(start1);
754-
if (!this->IsPathEnd(node0))
755-
moved |= this->MoveChain(start0, node0, start1);
756-
if (!this->IsPathEnd(node1))
757-
moved |= this->MoveChain(this->Prev(first1), node1, start0);
773+
if (!this->IsPathEnd(node0)) {
774+
const auto [has_change, is_feasible] =
775+
this->MoveChainWithCheck(start0, node0, start1, true);
776+
moved |= has_change;
777+
feasible &= is_feasible;
778+
}
779+
if (!this->IsPathEnd(node1)) {
780+
const auto [has_change, is_feasible] =
781+
this->MoveChainWithCheck(this->Prev(first1), node1, start0, true);
782+
moved |= has_change;
783+
feasible &= is_feasible;
784+
}
758785
} else {
759786
// Cross path ends.
760787
// If paths are equivalent, every end crossing has a corresponding start
@@ -773,15 +800,21 @@ bool Cross<ignore_path_vars>::MakeNeighbor() {
773800

774801
const int prev_end_node1 = this->Prev(this->CurrentNodePathEnd(node1));
775802
if (!this->IsPathEnd(node0)) {
776-
moved |= this->MoveChain(this->Prev(node0), this->Prev(this->EndNode(0)),
777-
prev_end_node1);
803+
const auto [has_change, is_feasible] = this->MoveChainWithCheck(
804+
this->Prev(node0), this->Prev(this->EndNode(0)), prev_end_node1,
805+
true);
806+
moved |= has_change;
807+
feasible &= is_feasible;
778808
}
779809
if (!this->IsPathEnd(node1)) {
780-
moved |= this->MoveChain(this->Prev(node1), prev_end_node1,
781-
this->Prev(this->EndNode(0)));
810+
const auto [has_change, is_feasible] =
811+
this->MoveChainWithCheck(this->Prev(node1), prev_end_node1,
812+
this->Prev(this->EndNode(0)), true);
813+
moved |= has_change;
814+
feasible &= is_feasible;
782815
}
783816
}
784-
return moved;
817+
return moved && feasible;
785818
}
786819

787820
LocalSearchOperator* MakeCross(

ortools/constraint_solver/local_search.h

Lines changed: 60 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -718,7 +718,7 @@ class PathOperator : public IntVarLocalSearchOperator {
718718
/// Only returns a valid value if path variables are taken into account.
719719
int64_t Path(int64_t node) const {
720720
if constexpr (ignore_path_vars) return 0LL;
721-
return Value(node + number_of_nexts_);
721+
return Value(PathIndex(node));
722722
}
723723

724724
/// Number of next variables.
@@ -802,6 +802,20 @@ class PathOperator : public IntVarLocalSearchOperator {
802802
virtual void SetNextBaseToIncrement(int64_t base_index) {
803803
next_base_to_increment_ = base_index;
804804
}
805+
/// Returns true if the node can be moved to the given path.
806+
/// If paths are ignored, always returns true.
807+
bool IsCompatibleWithPath(int64_t node, int64_t path) const {
808+
if constexpr (ignore_path_vars) return true;
809+
return this->Var(PathIndex(node))->Contains(path);
810+
}
811+
bool CheckPathCompatibility(absl::Span<const int64_t> path,
812+
int path_id) const {
813+
for (int64_t node : path) {
814+
if (!IsCompatibleWithPath(node, path_id)) return false;
815+
}
816+
return true;
817+
}
818+
805819
/// Indicates if alternatives should be considered when iterating over base
806820
/// nodes.
807821
virtual bool ConsiderAlternatives(int64_t) const { return false; }
@@ -823,7 +837,7 @@ class PathOperator : public IntVarLocalSearchOperator {
823837

824838
int64_t OldPath(int64_t node) const {
825839
if constexpr (ignore_path_vars) return 0LL;
826-
return OldValue(node + number_of_nexts_);
840+
return OldValue(PathIndex(node));
827841
}
828842

829843
int CurrentNodePathStart(int64_t node) const {
@@ -834,26 +848,47 @@ class PathOperator : public IntVarLocalSearchOperator {
834848

835849
/// Moves the chain starting after the node before_chain and ending at the
836850
/// node chain_end after the node destination
837-
bool MoveChain(int64_t before_chain, int64_t chain_end, int64_t destination) {
838-
if (destination == before_chain || destination == chain_end) return false;
851+
struct ChangeResult {
852+
bool has_change;
853+
bool is_feasible;
854+
};
855+
ChangeResult MoveChainWithCheck(int64_t before_chain, int64_t chain_end,
856+
int64_t destination,
857+
bool check_path_compatibility) {
858+
if (destination == before_chain || destination == chain_end) {
859+
return {.has_change = false, .is_feasible = true};
860+
}
839861
DCHECK(CheckChainValidity(before_chain, chain_end, destination) &&
840862
!IsPathEnd(chain_end) && !IsPathEnd(destination));
841863
const int64_t destination_path = Path(destination);
842864
const int64_t after_chain = Next(chain_end);
843865
SetNext(chain_end, Next(destination), destination_path);
866+
bool is_path_compatible =
867+
!check_path_compatibility ||
868+
this->IsCompatibleWithPath(chain_end, destination_path);
844869
if constexpr (!ignore_path_vars) {
845870
int current = destination;
846871
int next = Next(before_chain);
847872
while (current != chain_end) {
848873
SetNext(current, next, destination_path);
874+
if (check_path_compatibility && is_path_compatible &&
875+
!this->IsCompatibleWithPath(next, destination_path)) {
876+
is_path_compatible = false;
877+
// TODO(user): Investigate if we could break here (making sure the
878+
// path inconsistencies don't break the operator).
879+
}
849880
current = next;
850881
next = Next(next);
851882
}
852883
} else {
853884
SetNext(destination, Next(before_chain), destination_path);
854885
}
855886
SetNext(before_chain, after_chain, Path(before_chain));
856-
return true;
887+
return {.has_change = true, .is_feasible = is_path_compatible};
888+
}
889+
bool MoveChain(int64_t before_chain, int64_t chain_end, int64_t destination) {
890+
return MoveChainWithCheck(before_chain, chain_end, destination, false)
891+
.has_change;
857892
}
858893

859894
/// Reverses the chain starting after before_chain and ending before
@@ -896,15 +931,28 @@ class PathOperator : public IntVarLocalSearchOperator {
896931
}
897932

898933
/// Swaps the nodes node1 and node2.
899-
bool SwapNodes(int64_t node1, int64_t node2) {
934+
ChangeResult SwapNodesWithCheck(int64_t node1, int64_t node2,
935+
bool check_path_compatibility) {
900936
if (IsPathEnd(node1) || IsPathEnd(node2) || IsPathStart(node1) ||
901937
IsPathStart(node2)) {
902-
return false;
938+
return {.has_change = false, .is_feasible = true};
903939
}
904-
if (node1 == node2) return false;
940+
if (node1 == node2) return {.has_change = false, .is_feasible = true};
905941
const int64_t prev_node1 = Prev(node1);
906-
const bool ok = MoveChain(prev_node1, node1, Prev(node2));
907-
return MoveChain(Prev(node2), node2, prev_node1) || ok;
942+
if constexpr (!ignore_path_vars) {
943+
const auto [has_change1, is_feasible1] = MoveChainWithCheck(
944+
prev_node1, node1, Prev(node2), check_path_compatibility);
945+
const auto [has_change2, is_feasible2] = MoveChainWithCheck(
946+
Prev(node2), node2, prev_node1, check_path_compatibility);
947+
return {.has_change = has_change1 || has_change2,
948+
.is_feasible = is_feasible1 && is_feasible2};
949+
}
950+
bool ok = MoveChain(prev_node1, node1, Prev(node2));
951+
ok = MoveChain(Prev(node2), node2, prev_node1) || ok;
952+
return {.has_change = ok, .is_feasible = true};
953+
}
954+
bool SwapNodes(int64_t node1, int64_t node2) {
955+
return SwapNodesWithCheck(node1, node2, false).has_change;
908956
}
909957

910958
/// Insert the inactive node after destination.
@@ -1085,6 +1133,8 @@ class PathOperator : public IntVarLocalSearchOperator {
10851133
return node >= 0 ? node : default_value;
10861134
}
10871135

1136+
int64_t PathIndex(int64_t node) const { return node + number_of_nexts_; }
1137+
10881138
void OnStart() override {
10891139
optimal_paths_enabled_ = false;
10901140
if (!iterators_initialized_) {

0 commit comments

Comments
 (0)