diff --git a/examples/contrib/3_jugs_regular.csproj b/examples/contrib/3_jugs_regular.csproj
index 8f8ebba6a6..857f03a370 100644
--- a/examples/contrib/3_jugs_regular.csproj
+++ b/examples/contrib/3_jugs_regular.csproj
@@ -19,6 +19,6 @@
-
+
diff --git a/examples/contrib/SimpleProgramFSharp.fsproj b/examples/contrib/SimpleProgramFSharp.fsproj
index 3d22e9f923..2771d32a30 100644
--- a/examples/contrib/SimpleProgramFSharp.fsproj
+++ b/examples/contrib/SimpleProgramFSharp.fsproj
@@ -19,6 +19,6 @@
-
+
diff --git a/examples/contrib/a_puzzle.csproj b/examples/contrib/a_puzzle.csproj
index e0e7501ff3..02c4eb3caa 100644
--- a/examples/contrib/a_puzzle.csproj
+++ b/examples/contrib/a_puzzle.csproj
@@ -19,6 +19,6 @@
-
+
diff --git a/examples/contrib/a_round_of_golf.csproj b/examples/contrib/a_round_of_golf.csproj
index 756a8fd1b6..74924e63f7 100644
--- a/examples/contrib/a_round_of_golf.csproj
+++ b/examples/contrib/a_round_of_golf.csproj
@@ -19,6 +19,6 @@
-
+
diff --git a/examples/contrib/all_interval.csproj b/examples/contrib/all_interval.csproj
index a44f8cc286..b307812dd6 100644
--- a/examples/contrib/all_interval.csproj
+++ b/examples/contrib/all_interval.csproj
@@ -19,6 +19,6 @@
-
+
diff --git a/examples/contrib/alldifferent_except_0.csproj b/examples/contrib/alldifferent_except_0.csproj
index 2b5d1d9194..89bdc21343 100644
--- a/examples/contrib/alldifferent_except_0.csproj
+++ b/examples/contrib/alldifferent_except_0.csproj
@@ -19,6 +19,6 @@
-
+
diff --git a/examples/contrib/assignment.csproj b/examples/contrib/assignment.csproj
index ced771fc96..98230c3fef 100644
--- a/examples/contrib/assignment.csproj
+++ b/examples/contrib/assignment.csproj
@@ -19,6 +19,6 @@
-
+
diff --git a/examples/contrib/broken_weights.csproj b/examples/contrib/broken_weights.csproj
index 010f3e1fc8..2bba64cb25 100644
--- a/examples/contrib/broken_weights.csproj
+++ b/examples/contrib/broken_weights.csproj
@@ -19,6 +19,6 @@
-
+
diff --git a/examples/contrib/bus_schedule.csproj b/examples/contrib/bus_schedule.csproj
index 3576b124f1..f792cfedcb 100644
--- a/examples/contrib/bus_schedule.csproj
+++ b/examples/contrib/bus_schedule.csproj
@@ -19,6 +19,6 @@
-
+
diff --git a/examples/contrib/circuit.csproj b/examples/contrib/circuit.csproj
index c71a7be757..18164820ca 100644
--- a/examples/contrib/circuit.csproj
+++ b/examples/contrib/circuit.csproj
@@ -19,6 +19,6 @@
-
+
diff --git a/examples/contrib/circuit2.csproj b/examples/contrib/circuit2.csproj
index 92f504f5ad..ff2a4ec5d5 100644
--- a/examples/contrib/circuit2.csproj
+++ b/examples/contrib/circuit2.csproj
@@ -19,6 +19,6 @@
-
+
diff --git a/examples/contrib/coins3.csproj b/examples/contrib/coins3.csproj
index 61d6101ee5..285679826f 100644
--- a/examples/contrib/coins3.csproj
+++ b/examples/contrib/coins3.csproj
@@ -19,6 +19,6 @@
-
+
diff --git a/examples/contrib/coins_grid.csproj b/examples/contrib/coins_grid.csproj
index 352f8aeb58..5fc6ec0d9d 100644
--- a/examples/contrib/coins_grid.csproj
+++ b/examples/contrib/coins_grid.csproj
@@ -19,6 +19,6 @@
-
+
diff --git a/examples/contrib/combinatorial_auction2.csproj b/examples/contrib/combinatorial_auction2.csproj
index 6eede0f47e..6a84d889cc 100644
--- a/examples/contrib/combinatorial_auction2.csproj
+++ b/examples/contrib/combinatorial_auction2.csproj
@@ -19,6 +19,6 @@
-
+
diff --git a/examples/contrib/contiguity_regular.csproj b/examples/contrib/contiguity_regular.csproj
index 6adaf31712..36c3d2b537 100644
--- a/examples/contrib/contiguity_regular.csproj
+++ b/examples/contrib/contiguity_regular.csproj
@@ -19,6 +19,6 @@
-
+
diff --git a/examples/contrib/contiguity_transition.csproj b/examples/contrib/contiguity_transition.csproj
index 8048f94d03..bc1db5eed1 100644
--- a/examples/contrib/contiguity_transition.csproj
+++ b/examples/contrib/contiguity_transition.csproj
@@ -19,6 +19,6 @@
-
+
diff --git a/examples/contrib/costas_array.csproj b/examples/contrib/costas_array.csproj
index 250d8d7dc4..f6add6be54 100644
--- a/examples/contrib/costas_array.csproj
+++ b/examples/contrib/costas_array.csproj
@@ -19,6 +19,6 @@
-
+
diff --git a/examples/contrib/covering_opl.csproj b/examples/contrib/covering_opl.csproj
index ee7700e282..1e781051a9 100644
--- a/examples/contrib/covering_opl.csproj
+++ b/examples/contrib/covering_opl.csproj
@@ -19,6 +19,6 @@
-
+
diff --git a/examples/contrib/crew.csproj b/examples/contrib/crew.csproj
index 1cc0355eeb..b2deb42683 100644
--- a/examples/contrib/crew.csproj
+++ b/examples/contrib/crew.csproj
@@ -19,6 +19,6 @@
-
+
diff --git a/examples/contrib/crossword.csproj b/examples/contrib/crossword.csproj
index 35226157ee..94d856f12b 100644
--- a/examples/contrib/crossword.csproj
+++ b/examples/contrib/crossword.csproj
@@ -19,6 +19,6 @@
-
+
diff --git a/examples/contrib/crypta.csproj b/examples/contrib/crypta.csproj
index 48e079bb57..89677c12fc 100644
--- a/examples/contrib/crypta.csproj
+++ b/examples/contrib/crypta.csproj
@@ -19,6 +19,6 @@
-
+
diff --git a/examples/contrib/crypto.csproj b/examples/contrib/crypto.csproj
index 85ce6ce058..b10552af7c 100644
--- a/examples/contrib/crypto.csproj
+++ b/examples/contrib/crypto.csproj
@@ -19,6 +19,6 @@
-
+
diff --git a/examples/contrib/csdiet.csproj b/examples/contrib/csdiet.csproj
index 5fd98fb274..c51e18b6bc 100644
--- a/examples/contrib/csdiet.csproj
+++ b/examples/contrib/csdiet.csproj
@@ -19,6 +19,6 @@
-
+
diff --git a/examples/contrib/curious_set_of_integers.csproj b/examples/contrib/curious_set_of_integers.csproj
index dc3bb30808..a30a36ca7e 100644
--- a/examples/contrib/curious_set_of_integers.csproj
+++ b/examples/contrib/curious_set_of_integers.csproj
@@ -19,6 +19,6 @@
-
+
diff --git a/examples/contrib/debruijn.csproj b/examples/contrib/debruijn.csproj
index fe34005a7d..9cdb4c4122 100644
--- a/examples/contrib/debruijn.csproj
+++ b/examples/contrib/debruijn.csproj
@@ -19,6 +19,6 @@
-
+
diff --git a/examples/contrib/discrete_tomography.csproj b/examples/contrib/discrete_tomography.csproj
index 1516a287f6..61500b54f1 100644
--- a/examples/contrib/discrete_tomography.csproj
+++ b/examples/contrib/discrete_tomography.csproj
@@ -19,6 +19,6 @@
-
+
diff --git a/examples/contrib/divisible_by_9_through_1.csproj b/examples/contrib/divisible_by_9_through_1.csproj
index 3597d9a1f4..5fb3ece15e 100644
--- a/examples/contrib/divisible_by_9_through_1.csproj
+++ b/examples/contrib/divisible_by_9_through_1.csproj
@@ -19,6 +19,6 @@
-
+
diff --git a/examples/contrib/dudeney.csproj b/examples/contrib/dudeney.csproj
index 76e13f1d1d..00d8968b7b 100644
--- a/examples/contrib/dudeney.csproj
+++ b/examples/contrib/dudeney.csproj
@@ -19,6 +19,6 @@
-
+
diff --git a/examples/contrib/einav_puzzle2.csproj b/examples/contrib/einav_puzzle2.csproj
index 5f02b17c23..b69e401f08 100644
--- a/examples/contrib/einav_puzzle2.csproj
+++ b/examples/contrib/einav_puzzle2.csproj
@@ -19,6 +19,6 @@
-
+
diff --git a/examples/contrib/eq10.csproj b/examples/contrib/eq10.csproj
index 7a73c6c74e..0a165bd799 100644
--- a/examples/contrib/eq10.csproj
+++ b/examples/contrib/eq10.csproj
@@ -19,6 +19,6 @@
-
+
diff --git a/examples/contrib/eq20.csproj b/examples/contrib/eq20.csproj
index 4327f524cc..0898016881 100644
--- a/examples/contrib/eq20.csproj
+++ b/examples/contrib/eq20.csproj
@@ -19,6 +19,6 @@
-
+
diff --git a/examples/contrib/fill_a_pix.csproj b/examples/contrib/fill_a_pix.csproj
index d7cb56fd78..b614ce4564 100644
--- a/examples/contrib/fill_a_pix.csproj
+++ b/examples/contrib/fill_a_pix.csproj
@@ -19,6 +19,6 @@
-
+
diff --git a/examples/contrib/fsProgram.fsproj b/examples/contrib/fsProgram.fsproj
index a9a98e5acb..5faf7e76c6 100644
--- a/examples/contrib/fsProgram.fsproj
+++ b/examples/contrib/fsProgram.fsproj
@@ -19,6 +19,6 @@
-
+
diff --git a/examples/contrib/fsdiet.fsproj b/examples/contrib/fsdiet.fsproj
index 5b6840ad74..5c99fd3698 100644
--- a/examples/contrib/fsdiet.fsproj
+++ b/examples/contrib/fsdiet.fsproj
@@ -19,6 +19,6 @@
-
+
diff --git a/examples/contrib/fsequality-inequality.fsproj b/examples/contrib/fsequality-inequality.fsproj
index 4f405a4650..6467d98fdc 100644
--- a/examples/contrib/fsequality-inequality.fsproj
+++ b/examples/contrib/fsequality-inequality.fsproj
@@ -19,6 +19,6 @@
-
+
diff --git a/examples/contrib/fsequality.fsproj b/examples/contrib/fsequality.fsproj
index e32693248e..def72492ad 100644
--- a/examples/contrib/fsequality.fsproj
+++ b/examples/contrib/fsequality.fsproj
@@ -19,6 +19,6 @@
-
+
diff --git a/examples/contrib/fsinteger-linear-program.fsproj b/examples/contrib/fsinteger-linear-program.fsproj
index fddc281388..8372a56e8e 100644
--- a/examples/contrib/fsinteger-linear-program.fsproj
+++ b/examples/contrib/fsinteger-linear-program.fsproj
@@ -19,6 +19,6 @@
-
+
diff --git a/examples/contrib/fsintegerprogramming.fsproj b/examples/contrib/fsintegerprogramming.fsproj
index 510fd50daa..65d4a96adf 100644
--- a/examples/contrib/fsintegerprogramming.fsproj
+++ b/examples/contrib/fsintegerprogramming.fsproj
@@ -19,6 +19,6 @@
-
+
diff --git a/examples/contrib/fsknapsack.fsproj b/examples/contrib/fsknapsack.fsproj
index 2bd1ef0f2f..a92b55968b 100644
--- a/examples/contrib/fsknapsack.fsproj
+++ b/examples/contrib/fsknapsack.fsproj
@@ -19,6 +19,6 @@
-
+
diff --git a/examples/contrib/fslinearprogramming.fsproj b/examples/contrib/fslinearprogramming.fsproj
index 42fce610cf..655a5cdc75 100644
--- a/examples/contrib/fslinearprogramming.fsproj
+++ b/examples/contrib/fslinearprogramming.fsproj
@@ -19,6 +19,6 @@
-
+
diff --git a/examples/contrib/fsnetwork-max-flow-lpSolve.fsproj b/examples/contrib/fsnetwork-max-flow-lpSolve.fsproj
index 00760e0fbf..23a1180700 100644
--- a/examples/contrib/fsnetwork-max-flow-lpSolve.fsproj
+++ b/examples/contrib/fsnetwork-max-flow-lpSolve.fsproj
@@ -19,6 +19,6 @@
-
+
diff --git a/examples/contrib/fsnetwork-max-flow.fsproj b/examples/contrib/fsnetwork-max-flow.fsproj
index 95e8a34f9a..d25fb4cdfa 100644
--- a/examples/contrib/fsnetwork-max-flow.fsproj
+++ b/examples/contrib/fsnetwork-max-flow.fsproj
@@ -19,6 +19,6 @@
-
+
diff --git a/examples/contrib/fsnetwork-min-cost-flow.fsproj b/examples/contrib/fsnetwork-min-cost-flow.fsproj
index 4bcbe4151e..e5892dd489 100644
--- a/examples/contrib/fsnetwork-min-cost-flow.fsproj
+++ b/examples/contrib/fsnetwork-min-cost-flow.fsproj
@@ -19,6 +19,6 @@
-
+
diff --git a/examples/contrib/fsrabbit-pheasant.fsproj b/examples/contrib/fsrabbit-pheasant.fsproj
index fcd72a63f7..1a0fcd9608 100644
--- a/examples/contrib/fsrabbit-pheasant.fsproj
+++ b/examples/contrib/fsrabbit-pheasant.fsproj
@@ -19,6 +19,6 @@
-
+
diff --git a/examples/contrib/fsvolsay.fsproj b/examples/contrib/fsvolsay.fsproj
index ea53a31e3b..9fa08d65f5 100644
--- a/examples/contrib/fsvolsay.fsproj
+++ b/examples/contrib/fsvolsay.fsproj
@@ -19,6 +19,6 @@
-
+
diff --git a/examples/contrib/fsvolsay3-lpSolve.fsproj b/examples/contrib/fsvolsay3-lpSolve.fsproj
index 3610ca6c51..f23ffc1e22 100644
--- a/examples/contrib/fsvolsay3-lpSolve.fsproj
+++ b/examples/contrib/fsvolsay3-lpSolve.fsproj
@@ -19,6 +19,6 @@
-
+
diff --git a/examples/contrib/fsvolsay3.fsproj b/examples/contrib/fsvolsay3.fsproj
index abbc3610ed..c0668b82fe 100644
--- a/examples/contrib/fsvolsay3.fsproj
+++ b/examples/contrib/fsvolsay3.fsproj
@@ -19,6 +19,6 @@
-
+
diff --git a/examples/contrib/furniture_moving.csproj b/examples/contrib/furniture_moving.csproj
index 58cd8da118..5aea2055ba 100644
--- a/examples/contrib/furniture_moving.csproj
+++ b/examples/contrib/furniture_moving.csproj
@@ -19,6 +19,6 @@
-
+
diff --git a/examples/contrib/furniture_moving_intervals.csproj b/examples/contrib/furniture_moving_intervals.csproj
index fa5b579719..c939735196 100644
--- a/examples/contrib/furniture_moving_intervals.csproj
+++ b/examples/contrib/furniture_moving_intervals.csproj
@@ -19,6 +19,6 @@
-
+
diff --git a/examples/contrib/futoshiki.csproj b/examples/contrib/futoshiki.csproj
index bfdda3db39..fbf8300aea 100644
--- a/examples/contrib/futoshiki.csproj
+++ b/examples/contrib/futoshiki.csproj
@@ -19,6 +19,6 @@
-
+
diff --git a/examples/contrib/golomb_ruler.csproj b/examples/contrib/golomb_ruler.csproj
index a86b2a4ef4..19a9330f3f 100644
--- a/examples/contrib/golomb_ruler.csproj
+++ b/examples/contrib/golomb_ruler.csproj
@@ -19,6 +19,6 @@
-
+
diff --git a/examples/contrib/grocery.csproj b/examples/contrib/grocery.csproj
index dde3294994..c6fa50fd30 100644
--- a/examples/contrib/grocery.csproj
+++ b/examples/contrib/grocery.csproj
@@ -19,6 +19,6 @@
-
+
diff --git a/examples/contrib/hidato_table.csproj b/examples/contrib/hidato_table.csproj
index a56c364a9e..d8fd45e2a1 100644
--- a/examples/contrib/hidato_table.csproj
+++ b/examples/contrib/hidato_table.csproj
@@ -19,6 +19,6 @@
-
+
diff --git a/examples/contrib/just_forgotten.csproj b/examples/contrib/just_forgotten.csproj
index ed0c2579e2..e55142f4eb 100644
--- a/examples/contrib/just_forgotten.csproj
+++ b/examples/contrib/just_forgotten.csproj
@@ -19,6 +19,6 @@
-
+
diff --git a/examples/contrib/kakuro.csproj b/examples/contrib/kakuro.csproj
index 69633d148b..5251e6b3fc 100644
--- a/examples/contrib/kakuro.csproj
+++ b/examples/contrib/kakuro.csproj
@@ -19,6 +19,6 @@
-
+
diff --git a/examples/contrib/kenken2.csproj b/examples/contrib/kenken2.csproj
index 83944865f1..c5599581f9 100644
--- a/examples/contrib/kenken2.csproj
+++ b/examples/contrib/kenken2.csproj
@@ -19,6 +19,6 @@
-
+
diff --git a/examples/contrib/killer_sudoku.csproj b/examples/contrib/killer_sudoku.csproj
index ad421489c6..4242bd553b 100644
--- a/examples/contrib/killer_sudoku.csproj
+++ b/examples/contrib/killer_sudoku.csproj
@@ -19,6 +19,6 @@
-
+
diff --git a/examples/contrib/labeled_dice.csproj b/examples/contrib/labeled_dice.csproj
index be097400c2..ab0d7e888c 100644
--- a/examples/contrib/labeled_dice.csproj
+++ b/examples/contrib/labeled_dice.csproj
@@ -19,6 +19,6 @@
-
+
diff --git a/examples/contrib/langford.csproj b/examples/contrib/langford.csproj
index 99d3e74f2d..699c6dfbba 100644
--- a/examples/contrib/langford.csproj
+++ b/examples/contrib/langford.csproj
@@ -19,6 +19,6 @@
-
+
diff --git a/examples/contrib/least_diff.csproj b/examples/contrib/least_diff.csproj
index 3b761d4a1d..0f11894e96 100644
--- a/examples/contrib/least_diff.csproj
+++ b/examples/contrib/least_diff.csproj
@@ -19,6 +19,6 @@
-
+
diff --git a/examples/contrib/lectures.csproj b/examples/contrib/lectures.csproj
index 2f918057c3..3a99f85f53 100644
--- a/examples/contrib/lectures.csproj
+++ b/examples/contrib/lectures.csproj
@@ -19,6 +19,6 @@
-
+
diff --git a/examples/contrib/magic_sequence.csproj b/examples/contrib/magic_sequence.csproj
index f3301f897d..4d7b1bf4e0 100644
--- a/examples/contrib/magic_sequence.csproj
+++ b/examples/contrib/magic_sequence.csproj
@@ -19,6 +19,6 @@
-
+
diff --git a/examples/contrib/magic_square.csproj b/examples/contrib/magic_square.csproj
index e4dac7cda7..f8fb6ed1cf 100644
--- a/examples/contrib/magic_square.csproj
+++ b/examples/contrib/magic_square.csproj
@@ -19,6 +19,6 @@
-
+
diff --git a/examples/contrib/magic_square_and_cards.csproj b/examples/contrib/magic_square_and_cards.csproj
index 7f6f26ef01..96fc674a86 100644
--- a/examples/contrib/magic_square_and_cards.csproj
+++ b/examples/contrib/magic_square_and_cards.csproj
@@ -19,6 +19,6 @@
-
+
diff --git a/examples/contrib/map.csproj b/examples/contrib/map.csproj
index 19a09de9ce..0934398323 100644
--- a/examples/contrib/map.csproj
+++ b/examples/contrib/map.csproj
@@ -19,6 +19,6 @@
-
+
diff --git a/examples/contrib/map2.csproj b/examples/contrib/map2.csproj
index e1df5e2649..012cef96d8 100644
--- a/examples/contrib/map2.csproj
+++ b/examples/contrib/map2.csproj
@@ -19,6 +19,6 @@
-
+
diff --git a/examples/contrib/marathon2.csproj b/examples/contrib/marathon2.csproj
index 009ac9aa4c..8452cfa95a 100644
--- a/examples/contrib/marathon2.csproj
+++ b/examples/contrib/marathon2.csproj
@@ -19,6 +19,6 @@
-
+
diff --git a/examples/contrib/max_flow_taha.csproj b/examples/contrib/max_flow_taha.csproj
index 9a6b9248c8..a6b7a1d5f6 100644
--- a/examples/contrib/max_flow_taha.csproj
+++ b/examples/contrib/max_flow_taha.csproj
@@ -19,6 +19,6 @@
-
+
diff --git a/examples/contrib/max_flow_winston1.csproj b/examples/contrib/max_flow_winston1.csproj
index 4706a61634..65ecfcec2b 100644
--- a/examples/contrib/max_flow_winston1.csproj
+++ b/examples/contrib/max_flow_winston1.csproj
@@ -19,6 +19,6 @@
-
+
diff --git a/examples/contrib/minesweeper.csproj b/examples/contrib/minesweeper.csproj
index 4e0ba987b7..bbba5e96e6 100644
--- a/examples/contrib/minesweeper.csproj
+++ b/examples/contrib/minesweeper.csproj
@@ -19,6 +19,6 @@
-
+
diff --git a/examples/contrib/mr_smith.csproj b/examples/contrib/mr_smith.csproj
index d08bc1bc6c..0aa533873f 100644
--- a/examples/contrib/mr_smith.csproj
+++ b/examples/contrib/mr_smith.csproj
@@ -19,6 +19,6 @@
-
+
diff --git a/examples/contrib/nontransitive_dice.csproj b/examples/contrib/nontransitive_dice.csproj
index b2a807af70..1df0edcea2 100644
--- a/examples/contrib/nontransitive_dice.csproj
+++ b/examples/contrib/nontransitive_dice.csproj
@@ -19,6 +19,6 @@
-
+
diff --git a/examples/contrib/nqueens.csproj b/examples/contrib/nqueens.csproj
index bc784f8b97..254e17b1d5 100644
--- a/examples/contrib/nqueens.csproj
+++ b/examples/contrib/nqueens.csproj
@@ -19,6 +19,6 @@
-
+
diff --git a/examples/contrib/nurse_rostering_regular.csproj b/examples/contrib/nurse_rostering_regular.csproj
index a926194614..d4d0dfe94b 100644
--- a/examples/contrib/nurse_rostering_regular.csproj
+++ b/examples/contrib/nurse_rostering_regular.csproj
@@ -19,6 +19,6 @@
-
+
diff --git a/examples/contrib/nurse_rostering_transition.csproj b/examples/contrib/nurse_rostering_transition.csproj
index 46c18854cb..4fd9bf97cf 100644
--- a/examples/contrib/nurse_rostering_transition.csproj
+++ b/examples/contrib/nurse_rostering_transition.csproj
@@ -19,6 +19,6 @@
-
+
diff --git a/examples/contrib/olympic.csproj b/examples/contrib/olympic.csproj
index 8844175683..65bdd06eec 100644
--- a/examples/contrib/olympic.csproj
+++ b/examples/contrib/olympic.csproj
@@ -19,6 +19,6 @@
-
+
diff --git a/examples/contrib/organize_day.csproj b/examples/contrib/organize_day.csproj
index 869ef2a4e4..0d49dbde96 100644
--- a/examples/contrib/organize_day.csproj
+++ b/examples/contrib/organize_day.csproj
@@ -19,6 +19,6 @@
-
+
diff --git a/examples/contrib/organize_day_intervals.csproj b/examples/contrib/organize_day_intervals.csproj
index 5d6067f891..54967dc03d 100644
--- a/examples/contrib/organize_day_intervals.csproj
+++ b/examples/contrib/organize_day_intervals.csproj
@@ -19,6 +19,6 @@
-
+
diff --git a/examples/contrib/p_median.csproj b/examples/contrib/p_median.csproj
index 35a61f3dcf..f44ea38d91 100644
--- a/examples/contrib/p_median.csproj
+++ b/examples/contrib/p_median.csproj
@@ -19,6 +19,6 @@
-
+
diff --git a/examples/contrib/pandigital_numbers.csproj b/examples/contrib/pandigital_numbers.csproj
index fa0094b3cd..665d50ea91 100644
--- a/examples/contrib/pandigital_numbers.csproj
+++ b/examples/contrib/pandigital_numbers.csproj
@@ -19,6 +19,6 @@
-
+
diff --git a/examples/contrib/partition.csproj b/examples/contrib/partition.csproj
index 0e773cc6ae..b7406aef96 100644
--- a/examples/contrib/partition.csproj
+++ b/examples/contrib/partition.csproj
@@ -19,6 +19,6 @@
-
+
diff --git a/examples/contrib/perfect_square_sequence.csproj b/examples/contrib/perfect_square_sequence.csproj
index f10c94eeef..33ba0afe78 100644
--- a/examples/contrib/perfect_square_sequence.csproj
+++ b/examples/contrib/perfect_square_sequence.csproj
@@ -19,6 +19,6 @@
-
+
diff --git a/examples/contrib/photo_problem.csproj b/examples/contrib/photo_problem.csproj
index 61e9352dcd..5485a851c8 100644
--- a/examples/contrib/photo_problem.csproj
+++ b/examples/contrib/photo_problem.csproj
@@ -19,6 +19,6 @@
-
+
diff --git a/examples/contrib/place_number_puzzle.csproj b/examples/contrib/place_number_puzzle.csproj
index 4fbf809cf1..040abc96b3 100644
--- a/examples/contrib/place_number_puzzle.csproj
+++ b/examples/contrib/place_number_puzzle.csproj
@@ -19,6 +19,6 @@
-
+
diff --git a/examples/contrib/post_office_problem2.csproj b/examples/contrib/post_office_problem2.csproj
index b03afa88d5..6ee5e7ff34 100644
--- a/examples/contrib/post_office_problem2.csproj
+++ b/examples/contrib/post_office_problem2.csproj
@@ -19,6 +19,6 @@
-
+
diff --git a/examples/contrib/quasigroup_completion.csproj b/examples/contrib/quasigroup_completion.csproj
index 1765d354da..47a97e63d4 100644
--- a/examples/contrib/quasigroup_completion.csproj
+++ b/examples/contrib/quasigroup_completion.csproj
@@ -19,6 +19,6 @@
-
+
diff --git a/examples/contrib/regex.csproj b/examples/contrib/regex.csproj
index ea1d279cb8..7186355598 100644
--- a/examples/contrib/regex.csproj
+++ b/examples/contrib/regex.csproj
@@ -19,6 +19,6 @@
-
+
diff --git a/examples/contrib/rogo2.csproj b/examples/contrib/rogo2.csproj
index 087358f3aa..485a92d239 100644
--- a/examples/contrib/rogo2.csproj
+++ b/examples/contrib/rogo2.csproj
@@ -19,6 +19,6 @@
-
+
diff --git a/examples/contrib/scheduling_speakers.csproj b/examples/contrib/scheduling_speakers.csproj
index aeec8adac0..28cd395bb4 100644
--- a/examples/contrib/scheduling_speakers.csproj
+++ b/examples/contrib/scheduling_speakers.csproj
@@ -19,6 +19,6 @@
-
+
diff --git a/examples/contrib/secret_santa.csproj b/examples/contrib/secret_santa.csproj
index 01730b3ad8..71bb49533d 100644
--- a/examples/contrib/secret_santa.csproj
+++ b/examples/contrib/secret_santa.csproj
@@ -19,6 +19,6 @@
-
+
diff --git a/examples/contrib/secret_santa2.csproj b/examples/contrib/secret_santa2.csproj
index 6fc18ff405..56dfd61b70 100644
--- a/examples/contrib/secret_santa2.csproj
+++ b/examples/contrib/secret_santa2.csproj
@@ -19,6 +19,6 @@
-
+
diff --git a/examples/contrib/send_more_money.csproj b/examples/contrib/send_more_money.csproj
index 333eecd271..3da4610279 100644
--- a/examples/contrib/send_more_money.csproj
+++ b/examples/contrib/send_more_money.csproj
@@ -19,6 +19,6 @@
-
+
diff --git a/examples/contrib/send_more_money2.csproj b/examples/contrib/send_more_money2.csproj
index 8205eb16b8..78021382d1 100644
--- a/examples/contrib/send_more_money2.csproj
+++ b/examples/contrib/send_more_money2.csproj
@@ -19,6 +19,6 @@
-
+
diff --git a/examples/contrib/send_most_money.csproj b/examples/contrib/send_most_money.csproj
index 3dd6254e26..284a0dd7a4 100644
--- a/examples/contrib/send_most_money.csproj
+++ b/examples/contrib/send_most_money.csproj
@@ -19,6 +19,6 @@
-
+
diff --git a/examples/contrib/seseman.csproj b/examples/contrib/seseman.csproj
index 9be350dc9d..397d12fd9c 100644
--- a/examples/contrib/seseman.csproj
+++ b/examples/contrib/seseman.csproj
@@ -19,6 +19,6 @@
-
+
diff --git a/examples/contrib/set_covering.csproj b/examples/contrib/set_covering.csproj
index c76fbacd07..073be7c98f 100644
--- a/examples/contrib/set_covering.csproj
+++ b/examples/contrib/set_covering.csproj
@@ -19,6 +19,6 @@
-
+
diff --git a/examples/contrib/set_covering2.csproj b/examples/contrib/set_covering2.csproj
index d1add2c6f7..0d260b2be2 100644
--- a/examples/contrib/set_covering2.csproj
+++ b/examples/contrib/set_covering2.csproj
@@ -19,6 +19,6 @@
-
+
diff --git a/examples/contrib/set_covering3.csproj b/examples/contrib/set_covering3.csproj
index 5d5b6f24f4..d2b76fe66f 100644
--- a/examples/contrib/set_covering3.csproj
+++ b/examples/contrib/set_covering3.csproj
@@ -19,6 +19,6 @@
-
+
diff --git a/examples/contrib/set_covering4.csproj b/examples/contrib/set_covering4.csproj
index ded463f702..617b613eab 100644
--- a/examples/contrib/set_covering4.csproj
+++ b/examples/contrib/set_covering4.csproj
@@ -19,6 +19,6 @@
-
+
diff --git a/examples/contrib/set_covering_deployment.csproj b/examples/contrib/set_covering_deployment.csproj
index f23c17fc14..ef5853e5b5 100644
--- a/examples/contrib/set_covering_deployment.csproj
+++ b/examples/contrib/set_covering_deployment.csproj
@@ -19,6 +19,6 @@
-
+
diff --git a/examples/contrib/set_covering_skiena.csproj b/examples/contrib/set_covering_skiena.csproj
index 7c7d8ea9a8..4ef9806c85 100644
--- a/examples/contrib/set_covering_skiena.csproj
+++ b/examples/contrib/set_covering_skiena.csproj
@@ -19,6 +19,6 @@
-
+
diff --git a/examples/contrib/set_partition.csproj b/examples/contrib/set_partition.csproj
index 543f08442d..0578b54f33 100644
--- a/examples/contrib/set_partition.csproj
+++ b/examples/contrib/set_partition.csproj
@@ -19,6 +19,6 @@
-
+
diff --git a/examples/contrib/sicherman_dice.csproj b/examples/contrib/sicherman_dice.csproj
index 7509a54817..1865d4ecb1 100644
--- a/examples/contrib/sicherman_dice.csproj
+++ b/examples/contrib/sicherman_dice.csproj
@@ -19,6 +19,6 @@
-
+
diff --git a/examples/contrib/ski_assignment.csproj b/examples/contrib/ski_assignment.csproj
index f30eb9016e..a039a942f8 100644
--- a/examples/contrib/ski_assignment.csproj
+++ b/examples/contrib/ski_assignment.csproj
@@ -19,6 +19,6 @@
-
+
diff --git a/examples/contrib/stable_marriage.csproj b/examples/contrib/stable_marriage.csproj
index fc0643f083..d920b525f5 100644
--- a/examples/contrib/stable_marriage.csproj
+++ b/examples/contrib/stable_marriage.csproj
@@ -19,6 +19,6 @@
-
+
diff --git a/examples/contrib/strimko2.csproj b/examples/contrib/strimko2.csproj
index 09fe4621cc..b322d3487e 100644
--- a/examples/contrib/strimko2.csproj
+++ b/examples/contrib/strimko2.csproj
@@ -19,6 +19,6 @@
-
+
diff --git a/examples/contrib/subset_sum.csproj b/examples/contrib/subset_sum.csproj
index 442cecf7ec..96895304ee 100644
--- a/examples/contrib/subset_sum.csproj
+++ b/examples/contrib/subset_sum.csproj
@@ -19,6 +19,6 @@
-
+
diff --git a/examples/contrib/sudoku.csproj b/examples/contrib/sudoku.csproj
index fc4fe94f57..8b011d7899 100644
--- a/examples/contrib/sudoku.csproj
+++ b/examples/contrib/sudoku.csproj
@@ -19,6 +19,6 @@
-
+
diff --git a/examples/contrib/survo_puzzle.csproj b/examples/contrib/survo_puzzle.csproj
index 553da8a2d0..c959a8daaf 100644
--- a/examples/contrib/survo_puzzle.csproj
+++ b/examples/contrib/survo_puzzle.csproj
@@ -19,6 +19,6 @@
-
+
diff --git a/examples/contrib/to_num.csproj b/examples/contrib/to_num.csproj
index 9481b67dfc..ec428ecacd 100644
--- a/examples/contrib/to_num.csproj
+++ b/examples/contrib/to_num.csproj
@@ -19,6 +19,6 @@
-
+
diff --git a/examples/contrib/traffic_lights.csproj b/examples/contrib/traffic_lights.csproj
index 4a152731d9..12c3dc5916 100644
--- a/examples/contrib/traffic_lights.csproj
+++ b/examples/contrib/traffic_lights.csproj
@@ -19,6 +19,6 @@
-
+
diff --git a/examples/contrib/volsay.csproj b/examples/contrib/volsay.csproj
index 0697a81fec..66738ac7b3 100644
--- a/examples/contrib/volsay.csproj
+++ b/examples/contrib/volsay.csproj
@@ -19,6 +19,6 @@
-
+
diff --git a/examples/contrib/volsay2.csproj b/examples/contrib/volsay2.csproj
index f843cc0bae..587e73ecfe 100644
--- a/examples/contrib/volsay2.csproj
+++ b/examples/contrib/volsay2.csproj
@@ -19,6 +19,6 @@
-
+
diff --git a/examples/contrib/volsay3.csproj b/examples/contrib/volsay3.csproj
index afda1d645f..3b1db3988c 100644
--- a/examples/contrib/volsay3.csproj
+++ b/examples/contrib/volsay3.csproj
@@ -19,6 +19,6 @@
-
+
diff --git a/examples/contrib/wedding_optimal_chart.csproj b/examples/contrib/wedding_optimal_chart.csproj
index 9f8aaa595d..c981c1274c 100644
--- a/examples/contrib/wedding_optimal_chart.csproj
+++ b/examples/contrib/wedding_optimal_chart.csproj
@@ -19,6 +19,6 @@
-
+
diff --git a/examples/contrib/who_killed_agatha.csproj b/examples/contrib/who_killed_agatha.csproj
index 2b458df6c2..ec41598538 100644
--- a/examples/contrib/who_killed_agatha.csproj
+++ b/examples/contrib/who_killed_agatha.csproj
@@ -19,6 +19,6 @@
-
+
diff --git a/examples/contrib/word_square.csproj b/examples/contrib/word_square.csproj
index 9be20ceb57..b9da401f48 100644
--- a/examples/contrib/word_square.csproj
+++ b/examples/contrib/word_square.csproj
@@ -19,6 +19,6 @@
-
+
diff --git a/examples/contrib/xkcd.csproj b/examples/contrib/xkcd.csproj
index 734e3e9a64..5843c952f3 100644
--- a/examples/contrib/xkcd.csproj
+++ b/examples/contrib/xkcd.csproj
@@ -19,6 +19,6 @@
-
+
diff --git a/examples/contrib/young_tableaux.csproj b/examples/contrib/young_tableaux.csproj
index 17085bd14d..92b95bdb81 100644
--- a/examples/contrib/young_tableaux.csproj
+++ b/examples/contrib/young_tableaux.csproj
@@ -19,6 +19,6 @@
-
+
diff --git a/examples/contrib/zebra.csproj b/examples/contrib/zebra.csproj
index 7ab4596b01..f0375a2bda 100644
--- a/examples/contrib/zebra.csproj
+++ b/examples/contrib/zebra.csproj
@@ -19,6 +19,6 @@
-
+
diff --git a/examples/dotnet/BalanceGroupSat.csproj b/examples/dotnet/BalanceGroupSat.csproj
index f59af3642d..6257256be5 100644
--- a/examples/dotnet/BalanceGroupSat.csproj
+++ b/examples/dotnet/BalanceGroupSat.csproj
@@ -19,6 +19,6 @@
-
+
diff --git a/examples/dotnet/GateSchedulingSat.csproj b/examples/dotnet/GateSchedulingSat.csproj
index 6ce0a5125f..a97f031965 100644
--- a/examples/dotnet/GateSchedulingSat.csproj
+++ b/examples/dotnet/GateSchedulingSat.csproj
@@ -19,6 +19,6 @@
-
+
diff --git a/examples/dotnet/JobshopFt06Sat.csproj b/examples/dotnet/JobshopFt06Sat.csproj
index fb68ab6f57..95e2473ac6 100644
--- a/examples/dotnet/JobshopFt06Sat.csproj
+++ b/examples/dotnet/JobshopFt06Sat.csproj
@@ -19,6 +19,6 @@
-
+
diff --git a/examples/dotnet/JobshopSat.csproj b/examples/dotnet/JobshopSat.csproj
index 3bdc471d55..71b16c7196 100644
--- a/examples/dotnet/JobshopSat.csproj
+++ b/examples/dotnet/JobshopSat.csproj
@@ -19,6 +19,6 @@
-
+
diff --git a/examples/dotnet/NetworkRoutingSat.csproj b/examples/dotnet/NetworkRoutingSat.csproj
index ea03f8f071..58b1834c58 100644
--- a/examples/dotnet/NetworkRoutingSat.csproj
+++ b/examples/dotnet/NetworkRoutingSat.csproj
@@ -19,6 +19,6 @@
-
+
diff --git a/examples/dotnet/NursesSat.csproj b/examples/dotnet/NursesSat.csproj
index 7c00b01ac8..12126c722c 100644
--- a/examples/dotnet/NursesSat.csproj
+++ b/examples/dotnet/NursesSat.csproj
@@ -19,6 +19,6 @@
-
+
diff --git a/examples/dotnet/ShiftSchedulingSat.csproj b/examples/dotnet/ShiftSchedulingSat.csproj
index 7bccac5325..74cf3a778b 100644
--- a/examples/dotnet/ShiftSchedulingSat.csproj
+++ b/examples/dotnet/ShiftSchedulingSat.csproj
@@ -19,6 +19,6 @@
-
+
diff --git a/examples/dotnet/SpeakerSchedulingSat.csproj b/examples/dotnet/SpeakerSchedulingSat.csproj
index 18e973515d..08be7e2685 100644
--- a/examples/dotnet/SpeakerSchedulingSat.csproj
+++ b/examples/dotnet/SpeakerSchedulingSat.csproj
@@ -19,6 +19,6 @@
-
+
diff --git a/examples/dotnet/TaskSchedulingSat.csproj b/examples/dotnet/TaskSchedulingSat.csproj
index f88147b522..87799b6bc0 100644
--- a/examples/dotnet/TaskSchedulingSat.csproj
+++ b/examples/dotnet/TaskSchedulingSat.csproj
@@ -19,6 +19,6 @@
-
+
diff --git a/examples/dotnet/cscvrptw.csproj b/examples/dotnet/cscvrptw.csproj
index e0087fc48b..060eeb062e 100644
--- a/examples/dotnet/cscvrptw.csproj
+++ b/examples/dotnet/cscvrptw.csproj
@@ -19,6 +19,6 @@
-
+
diff --git a/examples/dotnet/csflow.csproj b/examples/dotnet/csflow.csproj
index 5a7faf41cb..778848fe30 100644
--- a/examples/dotnet/csflow.csproj
+++ b/examples/dotnet/csflow.csproj
@@ -19,6 +19,6 @@
-
+
diff --git a/examples/dotnet/csintegerprogramming.csproj b/examples/dotnet/csintegerprogramming.csproj
index 382f237994..1d6c7c26d8 100644
--- a/examples/dotnet/csintegerprogramming.csproj
+++ b/examples/dotnet/csintegerprogramming.csproj
@@ -19,6 +19,6 @@
-
+
diff --git a/examples/dotnet/csknapsack.csproj b/examples/dotnet/csknapsack.csproj
index 4d7a173991..55f90001e1 100644
--- a/examples/dotnet/csknapsack.csproj
+++ b/examples/dotnet/csknapsack.csproj
@@ -19,6 +19,6 @@
-
+
diff --git a/examples/dotnet/cslinearprogramming.csproj b/examples/dotnet/cslinearprogramming.csproj
index cefda9392a..6a37b7322a 100644
--- a/examples/dotnet/cslinearprogramming.csproj
+++ b/examples/dotnet/cslinearprogramming.csproj
@@ -19,6 +19,6 @@
-
+
diff --git a/examples/dotnet/csls_api.csproj b/examples/dotnet/csls_api.csproj
index f9b59a6db3..9b2f068170 100644
--- a/examples/dotnet/csls_api.csproj
+++ b/examples/dotnet/csls_api.csproj
@@ -19,6 +19,6 @@
-
+
diff --git a/examples/dotnet/csrabbitspheasants.csproj b/examples/dotnet/csrabbitspheasants.csproj
index 2e95c661cc..4344b7218c 100644
--- a/examples/dotnet/csrabbitspheasants.csproj
+++ b/examples/dotnet/csrabbitspheasants.csproj
@@ -19,6 +19,6 @@
-
+
diff --git a/examples/dotnet/cstsp.csproj b/examples/dotnet/cstsp.csproj
index e859c733b0..5883791e8c 100644
--- a/examples/dotnet/cstsp.csproj
+++ b/examples/dotnet/cstsp.csproj
@@ -19,6 +19,6 @@
-
+
diff --git a/examples/tests/ConstraintSolverTests.csproj b/examples/tests/ConstraintSolverTests.csproj
index 099b7fd21e..40e81adffb 100644
--- a/examples/tests/ConstraintSolverTests.csproj
+++ b/examples/tests/ConstraintSolverTests.csproj
@@ -22,6 +22,6 @@
-
+
diff --git a/examples/tests/LinearSolverTests.csproj b/examples/tests/LinearSolverTests.csproj
index 996f6112a6..210860eae6 100644
--- a/examples/tests/LinearSolverTests.csproj
+++ b/examples/tests/LinearSolverTests.csproj
@@ -22,6 +22,6 @@
-
+
diff --git a/examples/tests/RoutingSolverTests.csproj b/examples/tests/RoutingSolverTests.csproj
index 758635d270..64e8869deb 100644
--- a/examples/tests/RoutingSolverTests.csproj
+++ b/examples/tests/RoutingSolverTests.csproj
@@ -22,6 +22,6 @@
-
+
diff --git a/examples/tests/SatSolverTests.csproj b/examples/tests/SatSolverTests.csproj
index 75d18912cc..7c132a9906 100644
--- a/examples/tests/SatSolverTests.csproj
+++ b/examples/tests/SatSolverTests.csproj
@@ -22,6 +22,6 @@
-
+
diff --git a/examples/tests/issue18.csproj b/examples/tests/issue18.csproj
index ff6bdf224f..76a549774a 100644
--- a/examples/tests/issue18.csproj
+++ b/examples/tests/issue18.csproj
@@ -22,6 +22,6 @@
-
+
diff --git a/examples/tests/issue22.csproj b/examples/tests/issue22.csproj
index 0263b822e6..2726a2ad8e 100644
--- a/examples/tests/issue22.csproj
+++ b/examples/tests/issue22.csproj
@@ -22,6 +22,6 @@
-
+
diff --git a/examples/tests/issue33.csproj b/examples/tests/issue33.csproj
index 392f64dae1..f42747b30f 100644
--- a/examples/tests/issue33.csproj
+++ b/examples/tests/issue33.csproj
@@ -22,6 +22,6 @@
-
+
diff --git a/ortools/CMakeLists.txt b/ortools/CMakeLists.txt
deleted file mode 100644
index 1ef6ed3df8..0000000000
--- a/ortools/CMakeLists.txt
+++ /dev/null
@@ -1,9 +0,0 @@
-ENABLE_LANGUAGE(CXX)
-ENABLE_LANGUAGE(Python)
-
-PROJECT(or_tools_algorithms)
-
-FILE(GLOB ${PROJECT_NAME}_SOURCES "*.cc")
-
-ADD_LIBRARY(${PROJECT_NAME} OBJECT ${${PROJECT_NAME}_SOURCES})
-SET_TARGET_PROPERTIES(${PROJECT_NAME} PROPERTIES POSITION_INDEPENDENT_CODE ON)
\ No newline at end of file
diff --git a/ortools/algorithms/knapsack_solver.cc b/ortools/algorithms/knapsack_solver.cc
index bcf6b74493..5d0ba6c898 100644
--- a/ortools/algorithms/knapsack_solver.cc
+++ b/ortools/algorithms/knapsack_solver.cc
@@ -1157,16 +1157,16 @@ KnapsackSolver::KnapsackSolver(SolverType solver_type,
break;
#endif // USE_SCIP
#if defined(USE_XPRESS)
- case KNAPSACK_MULTIDIMENSION_XPRESS_MIP_SOLVER:
- solver_ = absl::make_unique(
- MPSolver::XPRESS_MIXED_INTEGER_PROGRAMMING, solver_name);
- break;
+ case KNAPSACK_MULTIDIMENSION_XPRESS_MIP_SOLVER:
+ solver_ = absl::make_unique(
+ MPSolver::XPRESS_MIXED_INTEGER_PROGRAMMING, solver_name);
+ break;
#endif
#if defined(USE_CPLEX)
- case KNAPSACK_MULTIDIMENSION_CPLEX_MIP_SOLVER:
- solver_ = absl::make_unique(
- MPSolver::CPLEX_MIXED_INTEGER_PROGRAMMING, solver_name);
- break;
+ case KNAPSACK_MULTIDIMENSION_CPLEX_MIP_SOLVER:
+ solver_ = absl::make_unique(
+ MPSolver::CPLEX_MIXED_INTEGER_PROGRAMMING, solver_name);
+ break;
#endif
default:
LOG(FATAL) << "Unknown knapsack solver type.";
diff --git a/ortools/algorithms/knapsack_solver.h b/ortools/algorithms/knapsack_solver.h
index 52fda5fe0b..ea2a8223ee 100644
--- a/ortools/algorithms/knapsack_solver.h
+++ b/ortools/algorithms/knapsack_solver.h
@@ -173,11 +173,23 @@ class KnapsackSolver {
*/
KNAPSACK_MULTIDIMENSION_SCIP_MIP_SOLVER = 6,
#endif // USE_SCIP
+
#if defined(USE_XPRESS)
- KNAPSACK_MULTIDIMENSION_XPRESS_MIP_SOLVER = 7,
+ /** XPRESS based solver
+ *
+ * This solver can deal with both large number of items and several
+ * dimensions. This solver is based on Integer Programming solver XPRESS.
+ */
+ KNAPSACK_MULTIDIMENSION_XPRESS_MIP_SOLVER = 7,
#endif
+
#if defined(USE_CPLEX)
- KNAPSACK_MULTIDIMENSION_CPLEX_MIP_SOLVER = 8,
+ /** CPLEX based solver
+ *
+ * This solver can deal with both large number of items and several
+ * dimensions. This solver is based on Integer Programming solver CPLEX.
+ */
+ KNAPSACK_MULTIDIMENSION_CPLEX_MIP_SOLVER = 8,
#endif
};
diff --git a/ortools/algorithms/samples/Knapsack.csproj b/ortools/algorithms/samples/Knapsack.csproj
index 85190332cd..6fa079f3eb 100644
--- a/ortools/algorithms/samples/Knapsack.csproj
+++ b/ortools/algorithms/samples/Knapsack.csproj
@@ -19,6 +19,6 @@
-
+
diff --git a/ortools/algorithms/samples/knapsack.cc b/ortools/algorithms/samples/knapsack.cc
index 544f6ffb00..d3306a9be6 100644
--- a/ortools/algorithms/samples/knapsack.cc
+++ b/ortools/algorithms/samples/knapsack.cc
@@ -71,7 +71,7 @@ void RunKnapsackExample() {
packed_weights_ss << packed_weights.back();
int64 total_weights =
- std::accumulate(packed_weights.begin(), packed_weights.end(), 0LL);
+ std::accumulate(packed_weights.begin(), packed_weights.end(), int64{0});
LOG(INFO) << "Total value: " << computed_value;
LOG(INFO) << "Packed items: {" << packed_items_ss.str() << "}";
diff --git a/ortools/algorithms/samples/simple_knapsack_program.cc b/ortools/algorithms/samples/simple_knapsack_program.cc
index 56f2382bb9..8b91f15fe0 100644
--- a/ortools/algorithms/samples/simple_knapsack_program.cc
+++ b/ortools/algorithms/samples/simple_knapsack_program.cc
@@ -62,7 +62,7 @@ void SimpleKnapsackProgram() {
packed_weights_ss << packed_weights.back();
int64 total_weights =
- std::accumulate(packed_weights.begin(), packed_weights.end(), 0LL);
+ std::accumulate(packed_weights.begin(), packed_weights.end(), int64{0});
LOG(INFO) << "Total value: " << computed_value;
LOG(INFO) << "Packed items: {" << packed_items_ss.str() << "}";
diff --git a/ortools/constraint_solver/constraint_solver.h b/ortools/constraint_solver/constraint_solver.h
index b8f80c06f0..f863ff3efa 100644
--- a/ortools/constraint_solver/constraint_solver.h
+++ b/ortools/constraint_solver/constraint_solver.h
@@ -4232,8 +4232,9 @@ class SearchLimit : public SearchMonitor {
/// number of failures in the search tree
class RegularLimit : public SearchLimit {
public:
- RegularLimit(Solver* const s, int64 time, int64 branches, int64 failures,
- int64 solutions, bool smart_time_check, bool cumulative);
+ RegularLimit(Solver* const s, absl::Duration time, int64 branches,
+ int64 failures, int64 solutions, bool smart_time_check,
+ bool cumulative);
~RegularLimit() override;
void Copy(const SearchLimit* const limit) override;
SearchLimit* MakeClone() const override;
diff --git a/ortools/constraint_solver/constraint_solveri.h b/ortools/constraint_solver/constraint_solveri.h
index 534dad38b2..9b7fdd3ea8 100644
--- a/ortools/constraint_solver/constraint_solveri.h
+++ b/ortools/constraint_solver/constraint_solveri.h
@@ -2991,6 +2991,453 @@ inline int64 PosIntDivDown(int64 e, int64 v) {
}
std::vector ToInt64Vector(const std::vector& input);
+
+#if !defined(SWIG)
+// A PathState represents a set of paths and changed made on it.
+//
+// More accurately, let us define P_{num_nodes, starts, ends}-graphs the set of
+// directed graphs with nodes [0, num_nodes) whose connected components are
+// paths from starts[i] to ends[i] (for the same i) and loops.
+// Let us fix num_nodes, starts and ends so we call these P-graphs.
+//
+// Let us define some notions on graphs with the same set of nodes:
+// tails(D) is the set of nodes that are the tail of some arc of D.
+// P0 inter P1 is the graph of all arcs both in P0 and P1.
+// P0 union P1 is the graph of all arcs either in P0 or P1.
+// P1 - P0 is the graph with arcs in P1 and not in P0.
+// P0 |> D is the graph with arcs of P0 whose tail is not in tails(D).
+// P0 + D is (P0 |> D) union D.
+//
+// Now suppose P0 and P1 are P-graphs.
+// P0 + (P1 - P0) is exactly P1.
+// Moreover, note that P0 |> D is not a union of paths from some starts[i] to
+// ends[i] and loops like P0, because the operation removes arcs from P0.
+// P0 |> D is a union of generic paths, loops, and isolated nodes.
+// Let us call the generic paths and isolated nodes "chains".
+// Then the paths of P0 + D are chains linked by arcs of D.
+// Those chains are particularly interesting when examining a P-graph change.
+//
+// A PathState represents a P-graph for a fixed {num_nodes, starts, ends}.
+// The value of a PathState can be changed incrementally from P0 to P1
+// by passing the arcs of P1 - P0 to ChangeNext() and marking the end of the
+// change with a call to CutChains().
+// If P0 + D is not a P-graph, the behaviour is undefined.
+// TODO(user): check whether we want to have a DCHECK that P0 + D
+// is a P-graph or if CutChains() should return false.
+//
+// After CutChains(), tails(D) can be traversed using an iterator,
+// and the chains of P0 |> D can be browsed by chain-based iterators.
+// An iterator allows to browse the set of paths that have changed.
+// Then Commit() or Revert() can be called: Commit() changes the PathState to
+// represent P1 = P0 + D, all further changes are made from P1; Revert() changes
+// the PathState to forget D completely and return the state to P0.
+//
+// After a Commit(), Revert() or at initial state, the same iterators are
+// available and represent the change by an empty D: the set of changed paths
+// and the set of changed nodes is empty. Still, the chain-based iterator allows
+// to browse paths: each path has exactly one chain.
+class PathState {
+ public:
+ // A ChainRange allows to iterate on all chains of a path.
+ // ChainRange is a range, its iterator Chain*, its value type Chain.
+ class ChainRange;
+ // A Chain allows to iterate on all nodes of a chain, and access some data:
+ // first node, last node, number of nodes in the chain.
+ // Chain is a range, its iterator ChainNodeIterator, its value type int.
+ // Chains are returned by PathChainIterator's operator*().
+ class Chain;
+ // A NodeRange allows to iterate on all nodes of a path.
+ // NodeRange is a range, its iterator PathNodeIterator, its value type int.
+ class NodeRange;
+
+ // Path constructor: path_start and path_end must be disjoint,
+ // their values in [0, num_nodes).
+ PathState(int num_nodes, std::vector path_start,
+ std::vector path_end);
+
+ // Instance-constant accessors.
+
+ // Returns the number of nodes in the underlying graph.
+ int NumNodes() const { return num_nodes_; }
+ // Returns the number of paths (empty paths included).
+ int NumPaths() const { return num_paths_; }
+ // Returns the start of a path.
+ int Start(int path) const { return path_start_end_[path].start; }
+ // Returns the end of a path.
+ int End(int path) const { return path_start_end_[path].end; }
+
+ // State-dependent accessors.
+
+ // Returns the committed path of a given node, -1 if it is a loop.
+ int Path(int node) const {
+ return committed_nodes_[committed_index_[node]].path;
+ }
+ // Returns the set of arcs that have been added,
+ // i.e. that were changed and were not in the committed state.
+ const std::vector>& ChangedArcs() const {
+ return changed_arcs_;
+ }
+ // Returns the set of paths that actually changed,
+ // i.e. that have an arc in ChangedArcs().
+ const std::vector& ChangedPaths() const { return changed_paths_; }
+ // Returns the current range of chains of path.
+ ChainRange Chains(int path) const;
+ // Returns the current range of nodes of path.
+ NodeRange Nodes(int path) const;
+
+ // State modifiers.
+
+ // Adds arc (node, new_next) to the changed state, more formally,
+ // changes the state from (P0, D) to (P0, D + (node, new_next)).
+ void ChangeNext(int node, int new_next) {
+ changed_arcs_.emplace_back(node, new_next);
+ }
+ // Marks the end of ChangeNext() sequence, more formally,
+ // changes the state from (P0, D) to (P0 |> D, D).
+ void CutChains();
+ // Makes the current temporary state permanent, more formally,
+ // changes the state from (P0 |> D, D) to (P0 + D, \emptyset),
+ void Commit();
+ // Erase incremental changes made by ChangeNext() and CutChains(),
+ // more formally, changes the state from (P0 |> D, D) to (P0, \emptyset).
+ void Revert();
+
+ private:
+ // Most structs below are named pairs of ints, for typing purposes.
+
+ // Start and end are stored together to optimize (likely) simultaneous access.
+ struct PathStartEnd {
+ PathStartEnd(int start, int end) : start(start), end(end) {}
+ int start;
+ int end;
+ };
+ // Paths are ranges of chains, which are ranges of committed nodes, see below.
+ struct PathBounds {
+ int begin_index;
+ int end_index;
+ };
+ struct ChainBounds {
+ ChainBounds() = default;
+ ChainBounds(int begin_index, int end_index)
+ : begin_index(begin_index), end_index(end_index) {}
+ int begin_index;
+ int end_index;
+ };
+ struct CommittedNode {
+ CommittedNode(int node, int path) : node(node), path(path) {}
+ int node;
+ // Path of node in the committed state, -1 for loop nodes.
+ // TODO(user): check if path would be better stored
+ // with committed_index_, or in its own vector, or just recomputed.
+ int path;
+ };
+ // Used in temporary structures, see below.
+ struct TailHeadIndices {
+ int tail_index;
+ int head_index;
+ };
+ struct IndexArc {
+ int index;
+ int arc;
+ bool operator<(const IndexArc& other) const { return index < other.index; }
+ };
+
+ // Copies nodes in chains of path at the end of nodes,
+ // and sets those nodes' path member to value path.
+ void CopyNewPathAtEndOfNodes(int path);
+ // Commits paths in O(#{changed paths' nodes}) time,
+ // increasing this object's space usage by O(|changed path nodes|).
+ void IncrementalCommit();
+ // Commits paths in O(num_nodes + num_paths) time,
+ // reducing this object's space usage to O(num_nodes + num_paths).
+ void FullCommit();
+
+ // Instance-constant data.
+ const int num_nodes_;
+ const int num_paths_;
+ std::vector path_start_end_;
+
+ // Representation of the committed and changed paths.
+ // A path is a range of chains, which is a range of nodes.
+ // Ranges are represented internally by indices in vectors:
+ // ChainBounds are indices in committed_nodes_. PathBounds are indices in
+ // chains_. When committed (after construction, Revert() or Commit()):
+ // - path ranges are [path, path+1): they have one chain.
+ // - chain ranges don't overlap, chains_ has an empty sentinel at the end.
+ // - committed_nodes_ contains all nodes and old duplicates may appear,
+ // the current version of a node is at the index given by
+ // committed_index_[node]. A Commit() can add nodes at the end of
+ // committed_nodes_ in a space/time tradeoff, but if committed_nodes_' size
+ // is above num_nodes_threshold_, Commit() must reclaim useless duplicates'
+ // space by rewriting the path/chain/nodes structure.
+ // When changed (after CutChains()), new chains are computed,
+ // and the structure is updated accordingly:
+ // - path ranges that were changed have nonoverlapping values [begin, end)
+ // where begin is >= num_paths_ + 1, i.e. new chains are stored after
+ // committed state.
+ // - additional chain ranges are stored after the committed chains
+ // to represent the new chains resulting from the changes.
+ // Those chains do not overlap with each other or with unchanged chains.
+ // An empty sentinel chain is added at the end of additional chains.
+ // - committed_nodes_ are not modified, and still represent the committed
+ // paths.
+ // committed_index_ is not modified either.
+ std::vector committed_nodes_;
+ std::vector committed_index_;
+ const int num_nodes_threshold_;
+ std::vector chains_;
+ std::vector paths_;
+
+ // Incremental information: indices of nodes whose successor have changed,
+ // path that have changed nodes.
+ std::vector> changed_arcs_;
+ std::vector changed_paths_;
+ std::vector path_has_changed_;
+
+ // Temporary structures, since they will be reused heavily,
+ // those are members in order to be allocated once and for all.
+ std::vector tail_head_indices_;
+ std::vector arcs_by_tail_index_;
+ std::vector arcs_by_head_index_;
+ std::vector next_arc_;
+};
+
+// A Chain is a range of committed nodes.
+class PathState::Chain {
+ public:
+ class Iterator {
+ public:
+ Iterator& operator++() {
+ ++current_node_;
+ return *this;
+ }
+ int operator*() const { return current_node_->node; }
+ bool operator!=(Iterator other) const {
+ return current_node_ != other.current_node_;
+ }
+
+ private:
+ // Only a Chain can construct its iterator.
+ friend class PathState::Chain;
+ explicit Iterator(const CommittedNode* node) : current_node_(node) {}
+ const CommittedNode* current_node_;
+ };
+
+ // Chains hold CommittedNode* values, a Chain may be invalidated
+ // if the underlying vector is modified.
+ Chain(const CommittedNode* begin_node, const CommittedNode* end_node)
+ : begin_(begin_node), end_(end_node) {}
+
+ int NumNodes() const { return end_ - begin_; }
+ int First() const { return begin_->node; }
+ int Last() const { return (end_ - 1)->node; }
+ Iterator begin() const { return Iterator(begin_); }
+ Iterator end() const { return Iterator(end_); }
+
+ private:
+ const CommittedNode* const begin_;
+ const CommittedNode* const end_;
+};
+
+// A ChainRange is a range of Chains, committed or not.
+class PathState::ChainRange {
+ public:
+ class Iterator {
+ public:
+ Iterator& operator++() {
+ ++current_chain_;
+ return *this;
+ }
+ Chain operator*() const {
+ return {first_node_ + current_chain_->begin_index,
+ first_node_ + current_chain_->end_index};
+ }
+ bool operator!=(Iterator other) const {
+ return current_chain_ != other.current_chain_;
+ }
+
+ private:
+ // Only a ChainRange can construct its Iterator.
+ friend class ChainRange;
+ Iterator(const ChainBounds* chain, const CommittedNode* const first_node)
+ : current_chain_(chain), first_node_(first_node) {}
+ const ChainBounds* current_chain_;
+ const CommittedNode* const first_node_;
+ };
+
+ // ChainRanges hold ChainBounds* and CommittedNode*,
+ // a ChainRange may be invalidated if on of the underlying vector is modified.
+ ChainRange(const ChainBounds* const begin_chain,
+ const ChainBounds* const end_chain,
+ const CommittedNode* const first_node)
+ : begin_(begin_chain), end_(end_chain), first_node_(first_node) {}
+
+ Iterator begin() const { return {begin_, first_node_}; }
+ Iterator end() const { return {end_, first_node_}; }
+
+ private:
+ const ChainBounds* const begin_;
+ const ChainBounds* const end_;
+ const CommittedNode* const first_node_;
+};
+
+// A NodeRange allows to iterate on all nodes of a path,
+// by a two-level iteration on ChainBounds* and CommittedNode* of a PathState.
+class PathState::NodeRange {
+ public:
+ class Iterator {
+ public:
+ Iterator& operator++() {
+ ++current_node_;
+ if (current_node_ == end_node_) {
+ ++current_chain_;
+ // Note: dereferencing bounds is valid because there is a sentinel
+ // value at the end of PathState::chains_ to that intent.
+ const ChainBounds bounds = *current_chain_;
+ current_node_ = first_node_ + bounds.begin_index;
+ end_node_ = first_node_ + bounds.end_index;
+ }
+ return *this;
+ }
+ int operator*() const { return current_node_->node; }
+ bool operator!=(Iterator other) const {
+ return current_chain_ != other.current_chain_;
+ }
+
+ private:
+ // Only a NodeRange can construct its Iterator.
+ friend class NodeRange;
+ Iterator(const ChainBounds* current_chain,
+ const CommittedNode* const first_node)
+ : current_node_(first_node + current_chain->begin_index),
+ end_node_(first_node + current_chain->end_index),
+ current_chain_(current_chain),
+ first_node_(first_node) {}
+ const CommittedNode* current_node_;
+ const CommittedNode* end_node_;
+ const ChainBounds* current_chain_;
+ const CommittedNode* const first_node_;
+ };
+
+ // NodeRanges hold ChainBounds* and CommittedNode*,
+ // a NodeRange may be invalidated if on of the underlying vector is modified.
+ NodeRange(const ChainBounds* begin_chain, const ChainBounds* end_chain,
+ const CommittedNode* first_node)
+ : begin_chain_(begin_chain),
+ end_chain_(end_chain),
+ first_node_(first_node) {}
+ Iterator begin() const { return {begin_chain_, first_node_}; }
+ // Note: there is a sentinel value at the end of PathState::chains_,
+ // so dereferencing chain_range_.end()->begin_ is always valid.
+ Iterator end() const { return {end_chain_, first_node_}; }
+
+ private:
+ const ChainBounds* begin_chain_;
+ const ChainBounds* end_chain_;
+ const CommittedNode* const first_node_;
+};
+
+// This checker enforces unary dimension requirements.
+// A unary dimension requires that there is some valuation of
+// node_capacity and demand such that for all paths,
+// if arc A -> B is on a path of path_class p,
+// then node_capacity[A] + demand[p][A] = node_capacity[B].
+// Moreover, all node_capacities of a path must be inside interval
+// path_capacity[path].
+// Note that Intervals have two meanings:
+// - for demand and node_capacity, those are values allowed for each associated
+// decision variable.
+// - for path_capacity, those are set of values that node_capacities of the path
+// must respect.
+// If the path capacity of a path is [kint64min, kint64max],
+// then the unary dimension requirements are not enforced on this path.
+class UnaryDimensionChecker {
+ public:
+ struct Interval {
+ int64 min;
+ int64 max;
+ };
+
+ UnaryDimensionChecker(const PathState* path_state,
+ std::vector path_capacity,
+ std::vector path_class,
+ std::vector> demand,
+ std::vector node_capacity);
+
+ // Given the change made in PathState, checks that the unary dimension
+ // constraint is still feasible.
+ bool Check() const;
+
+ // Commits to the changes made in PathState,
+ // must be called before PathState::Commit().
+ void Commit();
+
+ private:
+ // Range min/max query on partial_demand_sums_.
+ // The first_node and last_node MUST form a subpath in the committed state.
+ // Nodes first_node and last_node are passed by their index in precomputed
+ // data, they must be committed in some path, and it has to be the same path.
+ // See partial_demand_sums_.
+ Interval GetMinMaxPartialDemandSum(int first_node_index,
+ int last_node_index) const;
+
+ // Queries whether all nodes in the committed subpath [first_node, last_node]
+ // have fixed demands and trivial node_capacity [kint64min, kint64max].
+ // first_node and last_node MUST form a subpath in the committed state.
+ // Nodes are passed by their index in precomputed data.
+ bool SubpathOnlyHasTrivialNodes(int first_node_index,
+ int last_node_index) const;
+
+ const PathState* const path_state_;
+ const std::vector path_capacity_;
+ const std::vector path_class_;
+ const std::vector> demand_;
+ const std::vector node_capacity_;
+
+ // Precomputed data.
+ // Maps nodes to their pre-computed data, except for isolated nodes,
+ // which do not have precomputed data.
+ // Only valid for nodes that are in some path in the committed state.
+ std::vector index_;
+ // Implementation of a range min/max query, n = #nodes.
+ // partial_demand_sums_rmq_[0][index_[node]] contains the sum of demands
+ // from the start of the node's path to the node.
+ // If node is the start of path, the sum is demand_[path_class_[path]][node],
+ // moreover partial_demand_sums_rmq_[0][index_[node]-1] is {0, 0}.
+ // partial_demand_sums_rmq_[layer][index] contains an interval
+ // [min_value, max_value] such that min_value is
+ // min(partial_demand_sums_rmq_[0][index+i].min | i in [0, 2^layer)),
+ // similarly max_value is the maximum of .max on the same range.
+ std::vector> partial_demand_sums_rmq_;
+ const int maximum_rmq_exponent_; // floor(log_2(#nodes)).
+ // previous_nontrivial_index_[index_[node]] has the index of the previous
+ // node on its committed path that has nonfixed demand or nontrivial node
+ // capacity. This allows for O(1) queries that all nodes on a subpath
+ // are nonfixed and nontrivial.
+ std::vector previous_nontrivial_index_;
+};
+
+// Make a filter that takes ownership of a PathState and synchronizes it with
+// solver events. The solver represents a graph with array of variables 'nexts'.
+// Solver events are embodied by Assignment* deltas, that are translated to node
+// changes during Relax(), committed during Synchronize(), and reverted on
+// Revert().
+LocalSearchFilter* MakePathStateFilter(Solver* solver,
+ std::unique_ptr path_state,
+ const std::vector& nexts);
+
+// Make a filter that translates solver events to the input checker's interface.
+// Since UnaryDimensionChecker has a PathState, the filter returned by this
+// must be synchronized to the corresponding PathStateFilter:
+// - Relax() must be called after the PathStateFilter's.
+// - Accept() must be called after.
+// - Synchronize() must be called before.
+// - Revert() must be called before.
+LocalSearchFilter* MakeUnaryDimensionFilter(
+ Solver* solver, std::unique_ptr checker);
+
+#endif // !defined(SWIG)
+
} // namespace operations_research
#endif // OR_TOOLS_CONSTRAINT_SOLVER_CONSTRAINT_SOLVERI_H_
diff --git a/ortools/constraint_solver/constraints.cc b/ortools/constraint_solver/constraints.cc
index eef404d5a6..92660bf501 100644
--- a/ortools/constraint_solver/constraints.cc
+++ b/ortools/constraint_solver/constraints.cc
@@ -147,7 +147,7 @@ class MapDomain : public Constraint {
void InitialPropagate() override {
for (int i = 0; i < actives_.size(); ++i) {
- actives_[i]->SetRange(0LL, 1LL);
+ actives_[i]->SetRange(int64{0}, int64{1});
if (!var_->Contains(i)) {
actives_[i]->SetValue(0);
} else if (actives_[i]->Max() == 0LL) {
@@ -187,7 +187,7 @@ class MapDomain : public Constraint {
}
for (int64 j = std::max(vmax + int64{1}, int64{0});
j <= std::min(oldmax, size - int64{1}); ++j) {
- actives_[j]->SetValue(0LL);
+ actives_[j]->SetValue(int64{0});
}
}
diff --git a/ortools/constraint_solver/expr_array.cc b/ortools/constraint_solver/expr_array.cc
index 26c3b411e7..3f15cba6b7 100644
--- a/ortools/constraint_solver/expr_array.cc
+++ b/ortools/constraint_solver/expr_array.cc
@@ -1588,7 +1588,7 @@ void SumBooleanGreaterOrEqualToOne::InitialPropagate() {
if (bits_.IsCardinalityZero()) {
solver()->Fail();
} else if (bits_.IsCardinalityOne()) {
- vars_[bits_.GetFirstBit(0)]->SetValue(1LL);
+ vars_[bits_.GetFirstBit(0)]->SetValue(int64{1});
inactive_.Switch(solver());
}
}
@@ -1602,7 +1602,7 @@ void SumBooleanGreaterOrEqualToOne::Update(int index) {
if (bits_.IsCardinalityZero()) {
solver()->Fail();
} else if (bits_.IsCardinalityOne()) {
- vars_[bits_.GetFirstBit(0)]->SetValue(1LL);
+ vars_[bits_.GetFirstBit(0)]->SetValue(int64{1});
inactive_.Switch(solver());
}
}
@@ -2213,9 +2213,9 @@ class PositiveBooleanScalProd : public BaseIntExpr {
solver()->Fail();
}
if (new_min > 0LL) {
- var->SetMin(1LL);
+ var->SetMin(int64{1});
} else if (new_max < coefficient) {
- var->SetMax(0LL);
+ var->SetMax(int64{0});
}
}
}
@@ -3230,7 +3230,7 @@ IntExpr* MakeSumFct(Solver* solver, const std::vector& pre_vars) {
IntExpr* Solver::MakeSum(const std::vector& vars) {
const int size = vars.size();
if (size == 0) {
- return MakeIntConst(0LL);
+ return MakeIntConst(int64{0});
} else if (size == 1) {
return vars[0];
} else if (size == 2) {
diff --git a/ortools/constraint_solver/expr_cst.cc b/ortools/constraint_solver/expr_cst.cc
index 76e13c1627..2cf034580b 100644
--- a/ortools/constraint_solver/expr_cst.cc
+++ b/ortools/constraint_solver/expr_cst.cc
@@ -676,10 +676,10 @@ class IsGreaterEqualCstCt : public CastConstraint {
IntVar* Solver::MakeIsGreaterOrEqualCstVar(IntExpr* const var, int64 value) {
if (var->Min() >= value) {
- return MakeIntConst(1LL);
+ return MakeIntConst(int64{1});
}
if (var->Max() < value) {
- return MakeIntConst(0LL);
+ return MakeIntConst(int64{0});
}
if (var->IsVar()) {
return var->Var()->IsGreaterOrEqual(value);
@@ -775,10 +775,10 @@ class IsLessEqualCstCt : public CastConstraint {
IntVar* Solver::MakeIsLessOrEqualCstVar(IntExpr* const var, int64 value) {
if (var->Max() <= value) {
- return MakeIntConst(1LL);
+ return MakeIntConst(int64{1});
}
if (var->Min() > value) {
- return MakeIntConst(0LL);
+ return MakeIntConst(int64{0});
}
if (var->IsVar()) {
return var->Var()->IsLessOrEqual(value);
diff --git a/ortools/constraint_solver/expressions.cc b/ortools/constraint_solver/expressions.cc
index f7c7589712..43abb57c26 100644
--- a/ortools/constraint_solver/expressions.cc
+++ b/ortools/constraint_solver/expressions.cc
@@ -1384,10 +1384,10 @@ class DomainIntVar : public IntVar {
return s->MakeIsGreaterOrEqualCstVar(this, constant);
}
if (!Contains(constant)) {
- return s->MakeIntConst(0LL);
+ return s->MakeIntConst(int64{0});
}
if (Bound() && min_.Value() == constant) {
- return s->MakeIntConst(1LL);
+ return s->MakeIntConst(int64{1});
}
IntExpr* const cache = s->Cache()->FindExprConstantExpression(
this, constant, ModelCache::EXPR_CONSTANT_IS_EQUAL);
@@ -1437,10 +1437,10 @@ class DomainIntVar : public IntVar {
return s->MakeIsLessOrEqualCstVar(this, constant - 1);
}
if (!Contains(constant)) {
- return s->MakeIntConst(1LL);
+ return s->MakeIntConst(int64{1});
}
if (Bound() && min_.Value() == constant) {
- return s->MakeIntConst(0LL);
+ return s->MakeIntConst(int64{0});
}
IntExpr* const cache = s->Cache()->FindExprConstantExpression(
this, constant, ModelCache::EXPR_CONSTANT_IS_NOT_EQUAL);
@@ -1457,10 +1457,10 @@ class DomainIntVar : public IntVar {
IntVar* IsGreaterOrEqual(int64 constant) override {
Solver* const s = solver();
if (max_.Value() < constant) {
- return s->MakeIntConst(0LL);
+ return s->MakeIntConst(int64{0});
}
if (min_.Value() >= constant) {
- return s->MakeIntConst(1LL);
+ return s->MakeIntConst(int64{1});
}
IntExpr* const cache = s->Cache()->FindExprConstantExpression(
this, constant, ModelCache::EXPR_CONSTANT_IS_GREATER_OR_EQUAL);
@@ -7135,7 +7135,7 @@ IntExpr* Solver::MakeSemiContinuousExpr(IntExpr* const expr, int64 fixed_charge,
int64 step) {
if (step == 0) {
if (fixed_charge == 0) {
- return MakeIntConst(0LL);
+ return MakeIntConst(int64{0});
} else {
return RegisterIntExpr(
RevAlloc(new SemiContinuousStepZeroExpr(this, expr, fixed_charge)));
diff --git a/ortools/constraint_solver/java/constraint_solver.i b/ortools/constraint_solver/java/constraint_solver.i
index 63949097d5..a6680d9935 100644
--- a/ortools/constraint_solver/java/constraint_solver.i
+++ b/ortools/constraint_solver/java/constraint_solver.i
@@ -13,7 +13,6 @@
// TODO(user): Refactor this file to adhere to the SWIG style guide.
-
%include "enumsimple.swg"
%include "exception.i"
@@ -1311,17 +1310,17 @@ import java.util.function.Supplier;
// IntVarLocalSearchHandler
%unignore IntVarLocalSearchHandler;
-%ignore IntVarLocalSearchHandler::AddToAssignment;
+%rename (addToAssignment) IntVarLocalSearchHandler::AddToAssignment;
%rename (onAddVars) IntVarLocalSearchHandler::OnAddVars;
%rename (onRevertChanges) IntVarLocalSearchHandler::OnRevertChanges;
-%rename (valueFromAssignment) IntVarLocalSearchHandler::ValueFromAssignment;
+%rename (valueFromAssignent) IntVarLocalSearchHandler::ValueFromAssignent;
// SequenceVarLocalSearchHandler
%unignore SequenceVarLocalSearchHandler;
-%ignore SequenceVarLocalSearchHandler::AddToAssignment;
-%ignore SequenceVarLocalSearchHandler::ValueFromAssignment;
+%rename (addToAssignment) SequenceVarLocalSearchHandler::AddToAssignment;
%rename (onAddVars) SequenceVarLocalSearchHandler::OnAddVars;
%rename (onRevertChanges) SequenceVarLocalSearchHandler::OnRevertChanges;
+%rename (valueFromAssignent) SequenceVarLocalSearchHandler::ValueFromAssignent;
// LocalSearchOperator
%feature("director") LocalSearchOperator;
diff --git a/ortools/constraint_solver/local_search.cc b/ortools/constraint_solver/local_search.cc
index 626d58b5f8..2a6a3e2c1e 100644
--- a/ortools/constraint_solver/local_search.cc
+++ b/ortools/constraint_solver/local_search.cc
@@ -2415,6 +2415,510 @@ LocalSearchFilter* Solver::MakeRejectFilter() {
return RevAlloc(new RejectFilter());
}
+PathState::PathState(int num_nodes, std::vector path_start,
+ std::vector path_end)
+ : num_nodes_(num_nodes),
+ num_paths_(path_start.size()),
+ num_nodes_threshold_(std::max(16, 4 * num_nodes_)) // Arbitrary value.
+{
+ DCHECK_EQ(path_start.size(), num_paths_);
+ DCHECK_EQ(path_end.size(), num_paths_);
+ for (int p = 0; p < num_paths_; ++p) {
+ path_start_end_.push_back({path_start[p], path_end[p]});
+ }
+ // Initial state is all unperformed: paths go from start to end directly.
+ committed_index_.assign(num_nodes_, -1);
+ committed_nodes_.assign(2 * num_paths_, {-1, -1});
+ chains_.assign(num_paths_ + 1, {-1, -1}); // Reserve 1 more for sentinel.
+ paths_.assign(num_paths_, {-1, -1});
+ for (int path = 0; path < num_paths_; ++path) {
+ const int index = 2 * path;
+ const PathStartEnd start_end = path_start_end_[path];
+ committed_index_[start_end.start] = index;
+ committed_index_[start_end.end] = index + 1;
+
+ committed_nodes_[index] = {start_end.start, path};
+ committed_nodes_[index + 1] = {start_end.end, path};
+
+ chains_[path] = {index, index + 2};
+ paths_[path] = {path, path + 1};
+ }
+ chains_[num_paths_] = {0, 0}; // Sentinel.
+ // Nodes that are not starts or ends are loops.
+ for (int node = 0; node < num_nodes_; ++node) {
+ if (committed_index_[node] != -1) continue; // node is start or end.
+ committed_index_[node] = committed_nodes_.size();
+ committed_nodes_.push_back({node, -1});
+ }
+ path_has_changed_.assign(num_paths_, false);
+}
+
+PathState::ChainRange PathState::Chains(int path) const {
+ const PathBounds bounds = paths_[path];
+ return PathState::ChainRange(chains_.data() + bounds.begin_index,
+ chains_.data() + bounds.end_index,
+ committed_nodes_.data());
+}
+
+PathState::NodeRange PathState::Nodes(int path) const {
+ const PathBounds bounds = paths_[path];
+ return PathState::NodeRange(chains_.data() + bounds.begin_index,
+ chains_.data() + bounds.end_index,
+ committed_nodes_.data());
+}
+
+void PathState::CutChains() {
+ // Filter out unchanged arcs from changed_arcs_,
+ // translate changed arcs to changed arc indices.
+ // Fill changed_paths_ while we hold node_path.
+ DCHECK_EQ(chains_.size(), num_paths_ + 1); // One per path + sentinel.
+ DCHECK(changed_paths_.empty());
+ tail_head_indices_.clear();
+ int num_changed_arcs = 0;
+ for (const auto [node, next] : changed_arcs_) {
+ const int node_index = committed_index_[node];
+ const int next_index = committed_index_[next];
+ const int node_path = committed_nodes_[node_index].path;
+ if (next != node &&
+ (next_index != node_index + 1 || node_path == -1)) { // New arc.
+ tail_head_indices_.push_back({node_index, next_index});
+ changed_arcs_[num_changed_arcs++] = {node, next};
+ if (node_path != -1 && !path_has_changed_[node_path]) {
+ path_has_changed_[node_path] = true;
+ changed_paths_.push_back(node_path);
+ }
+ } else if (node == next && node_path != -1) { // New loop.
+ changed_arcs_[num_changed_arcs++] = {node, node};
+ }
+ }
+ changed_arcs_.resize(num_changed_arcs);
+
+ // TRICKY: For each changed path, we want to generate a sequence of chains
+ // that represents the path in the changed state.
+ // First, notice that if we add a fake end->start arc for each changed path,
+ // then all chains will be from the head of an arc to the tail of an arc.
+ // A way to generate the changed chains and paths would be, for each path,
+ // to start from a fake arc's head (the path start), go down the path until
+ // the tail of an arc, and go to the next arc until we return on the fake arc,
+ // enqueuing the [head, tail] chains as we go.
+ // In turn, to do that, we need to know which arc to go to.
+ // If we sort all heads and tails by index in two separate arrays,
+ // the head_index and tail_index at the same rank are such that
+ // [head_index, tail_index] is a chain. Moreover, the arc that must be visited
+ // after head_index's arc is tail_index's arc.
+
+ // Add a fake end->start arc for each path.
+ for (const int path : changed_paths_) {
+ const PathStartEnd start_end = path_start_end_[path];
+ tail_head_indices_.push_back(
+ {committed_index_[start_end.end], committed_index_[start_end.start]});
+ }
+
+ // Generate pairs (tail_index, arc) and (head_index, arc) for all arcs,
+ // sort those pairs by index.
+ const int num_arc_indices = tail_head_indices_.size();
+ arcs_by_tail_index_.resize(num_arc_indices);
+ arcs_by_head_index_.resize(num_arc_indices);
+ for (int i = 0; i < num_arc_indices; ++i) {
+ arcs_by_tail_index_[i] = {tail_head_indices_[i].tail_index, i};
+ arcs_by_head_index_[i] = {tail_head_indices_[i].head_index, i};
+ }
+ std::sort(arcs_by_tail_index_.begin(), arcs_by_tail_index_.end());
+ std::sort(arcs_by_head_index_.begin(), arcs_by_head_index_.end());
+ // Generate the map from arc to next arc in path.
+ next_arc_.resize(num_arc_indices);
+ for (int i = 0; i < num_arc_indices; ++i) {
+ next_arc_[arcs_by_head_index_[i].arc] = arcs_by_tail_index_[i].arc;
+ }
+
+ // Generate chains: for every changed path, start from its fake arc,
+ // jump to next_arc_ until going back to fake arc,
+ // enqueuing chains as we go.
+ const int first_fake_arc = num_arc_indices - changed_paths_.size();
+ for (int fake_arc = first_fake_arc; fake_arc < num_arc_indices; ++fake_arc) {
+ const int new_path_begin = chains_.size();
+ int32 arc = fake_arc;
+ do {
+ const int chain_begin = tail_head_indices_[arc].head_index;
+ arc = next_arc_[arc];
+ const int chain_end = tail_head_indices_[arc].tail_index + 1;
+ chains_.emplace_back(chain_begin, chain_end);
+ } while (arc != fake_arc);
+ const int path = changed_paths_[fake_arc - first_fake_arc];
+ const int new_path_end = chains_.size();
+ paths_[path] = {new_path_begin, new_path_end};
+ }
+ chains_.emplace_back(0, 0); // Sentinel.
+}
+
+void PathState::Commit() {
+ if (committed_nodes_.size() < num_nodes_threshold_) {
+ IncrementalCommit();
+ } else {
+ FullCommit();
+ }
+}
+
+void PathState::Revert() {
+ chains_.resize(num_paths_ + 1); // One per path + sentinel.
+ for (const int path : changed_paths_) {
+ paths_[path] = {path, path + 1};
+ path_has_changed_[path] = false;
+ }
+ changed_paths_.clear();
+ changed_arcs_.clear();
+}
+
+void PathState::CopyNewPathAtEndOfNodes(int path) {
+ // Copy path's nodes, chain by chain.
+ const int new_path_begin_index = committed_nodes_.size();
+ const PathBounds path_bounds = paths_[path];
+ for (int i = path_bounds.begin_index; i < path_bounds.end_index; ++i) {
+ const ChainBounds chain_bounds = chains_[i];
+ committed_nodes_.insert(committed_nodes_.end(),
+ committed_nodes_.data() + chain_bounds.begin_index,
+ committed_nodes_.data() + chain_bounds.end_index);
+ }
+ const int new_path_end_index = committed_nodes_.size();
+ // Set new nodes' path member to path.
+ for (int i = new_path_begin_index; i < new_path_end_index; ++i) {
+ committed_nodes_[i].path = path;
+ }
+}
+
+// TODO(user): Instead of copying paths at the end systematically,
+// reuse some of the memory when possible.
+void PathState::IncrementalCommit() {
+ const int new_nodes_begin = committed_nodes_.size();
+ for (const int path : ChangedPaths()) {
+ const int chain_begin = committed_nodes_.size();
+ CopyNewPathAtEndOfNodes(path);
+ const int chain_end = committed_nodes_.size();
+ chains_[path] = {chain_begin, chain_end};
+ }
+ // Re-index all copied nodes.
+ const int new_nodes_end = committed_nodes_.size();
+ for (int i = new_nodes_begin; i < new_nodes_end; ++i) {
+ committed_index_[committed_nodes_[i].node] = i;
+ }
+ // New loops stay in place: only change their path to -1,
+ // committed_index_ does not change.
+ for (const auto [node, next] : ChangedArcs()) {
+ if (node != next) continue;
+ const int index = committed_index_[node];
+ committed_nodes_[index].path = -1;
+ }
+ // Committed part of the state is set up, erase incremental changes.
+ Revert();
+}
+
+void PathState::FullCommit() {
+ // Copy all paths at the end of committed_nodes_,
+ // then remove all old committed_nodes_.
+ const int old_num_nodes = committed_nodes_.size();
+ for (int path = 0; path < num_paths_; ++path) {
+ const int new_path_begin = committed_nodes_.size() - old_num_nodes;
+ CopyNewPathAtEndOfNodes(path);
+ const int new_path_end = committed_nodes_.size() - old_num_nodes;
+ chains_[path] = {new_path_begin, new_path_end};
+ }
+ committed_nodes_.erase(committed_nodes_.begin(),
+ committed_nodes_.begin() + old_num_nodes);
+
+ // Reindex path nodes, then loop nodes.
+ constexpr int kUnindexed = -1;
+ committed_index_.assign(num_nodes_, kUnindexed);
+ int index = 0;
+ for (const CommittedNode committed_node : committed_nodes_) {
+ committed_index_[committed_node.node] = index++;
+ }
+ for (int node = 0; node < num_nodes_; ++node) {
+ if (committed_index_[node] != kUnindexed) continue;
+ committed_index_[node] = index++;
+ committed_nodes_.push_back({node, -1});
+ }
+ // Committed part of the state is set up, erase incremental changes.
+ Revert();
+}
+
+namespace {
+
+class PathStateFilter : public LocalSearchFilter {
+ public:
+ std::string DebugString() const override { return "PathStateFilter"; }
+ PathStateFilter(std::unique_ptr path_state,
+ const std::vector& nexts);
+ void Relax(const Assignment* delta, const Assignment* deltadelta) override;
+ bool Accept(const Assignment* delta, const Assignment* deltadelta,
+ int64 objective_min, int64 objective_max) override {
+ return true;
+ }
+ void Synchronize(const Assignment* delta,
+ const Assignment* deltadelta) override;
+ void Revert() override;
+
+ private:
+ const std::unique_ptr path_state_;
+ // Map IntVar* index to node, offset by the min index in nexts.
+ std::vector variable_index_to_node_;
+ int index_offset_;
+};
+
+PathStateFilter::PathStateFilter(std::unique_ptr path_state,
+ const std::vector& nexts)
+ : path_state_(std::move(path_state)) {
+ index_offset_ = std::numeric_limits::max();
+ for (const IntVar* next : nexts) {
+ index_offset_ = std::min(index_offset_, next->index());
+ }
+ variable_index_to_node_.resize(nexts.size(), -1);
+ for (int node = 0; node < nexts.size(); ++node) {
+ const int index = nexts[node]->index() - index_offset_;
+ if (variable_index_to_node_.size() <= index) {
+ variable_index_to_node_.resize(index + 1, -1);
+ }
+ variable_index_to_node_[index] = node;
+ }
+}
+
+void PathStateFilter::Relax(const Assignment* delta,
+ const Assignment* deltadelta) {
+ for (const IntVarElement& var_value : delta->IntVarContainer().elements()) {
+ const int index = var_value.Var()->index() - index_offset_;
+ if (index < 0 || variable_index_to_node_.size() <= index) continue;
+ const int node = variable_index_to_node_[index];
+ if (node == -1) continue;
+ path_state_->ChangeNext(node, var_value.Value());
+ }
+ path_state_->CutChains();
+}
+
+// The solver does not guarantee that a given Synchronize() corresponds to
+// the previous Relax() (or that there has been a call to Relax()),
+// so we replay the full change call sequence.
+void PathStateFilter::Synchronize(const Assignment* delta,
+ const Assignment* deltadelta) {
+ path_state_->Revert();
+ Relax(delta, deltadelta);
+ path_state_->Commit();
+}
+
+void PathStateFilter::Revert() { path_state_->Revert(); }
+
+} // namespace
+
+LocalSearchFilter* MakePathStateFilter(Solver* solver,
+ std::unique_ptr path_state,
+ const std::vector& nexts) {
+ PathStateFilter* filter = new PathStateFilter(std::move(path_state), nexts);
+ return solver->RevAlloc(filter);
+}
+
+UnaryDimensionChecker::UnaryDimensionChecker(
+ const PathState* path_state, std::vector path_capacity,
+ std::vector path_class, std::vector> demand,
+ std::vector node_capacity)
+ : path_state_(path_state),
+ path_capacity_(std::move(path_capacity)),
+ path_class_(std::move(path_class)),
+ demand_(std::move(demand)),
+ node_capacity_(std::move(node_capacity)),
+ index_(path_state_->NumNodes(), 0),
+ maximum_rmq_exponent_(
+ MostSignificantBitPosition32(path_state_->NumNodes())) {
+ DCHECK_EQ(path_state_->NumPaths(), path_capacity_.size());
+ DCHECK_EQ(path_state_->NumPaths(), path_class_.size());
+ const int num_nodes = path_state_->NumNodes();
+ const int num_paths = path_state_->NumPaths();
+ partial_demand_sums_rmq_.resize(maximum_rmq_exponent_ + 1);
+ for (auto& sums : partial_demand_sums_rmq_) {
+ sums.resize(num_nodes + num_paths);
+ }
+ previous_nontrivial_index_.reserve(num_nodes + num_paths);
+ Commit();
+}
+
+bool UnaryDimensionChecker::Check() const {
+ for (const int path : path_state_->ChangedPaths()) {
+ const Interval path_capacity = path_capacity_[path];
+ if (path_capacity.min == kint64min && path_capacity.max == kint64max) {
+ continue;
+ }
+ const int path_class = path_class_[path];
+ // Loop invariant: capacity_used is nonempty and within path_capacity.
+ Interval capacity_used = node_capacity_[path_state_->Start(path)];
+ capacity_used = {std::max(capacity_used.min, path_capacity.min),
+ std::min(capacity_used.max, path_capacity.max)};
+ if (capacity_used.min > capacity_used.max) return false;
+
+ for (const auto& chain : path_state_->Chains(path)) {
+ const int first_node = chain.First();
+ const int last_node = chain.Last();
+
+ const int first_index = index_[first_node];
+ const int last_index = index_[last_node];
+
+ const int chain_path = path_state_->Path(first_node);
+ const int chain_path_class =
+ chain_path == -1 ? -1 : path_class_[chain_path];
+ // Call the RMQ if the chain size is large enough;
+ // the optimal value was found with the associated benchmark in tests,
+ // in particular BM_UnaryDimensionChecker.
+ constexpr int kMinRangeSizeForRMQ = 4;
+ if (last_index - first_index > kMinRangeSizeForRMQ &&
+ path_class == chain_path_class &&
+ SubpathOnlyHasTrivialNodes(first_index, last_index)) {
+ // Compute feasible values of capacity_used that will not violate
+ // path_capacity. This is done by considering the worst cases
+ // using a range min/max query.
+ const Interval min_max =
+ GetMinMaxPartialDemandSum(first_index, last_index);
+ const Interval prev_sum = partial_demand_sums_rmq_[0][first_index - 1];
+ const Interval min_max_delta = {CapSub(min_max.min, prev_sum.min),
+ CapSub(min_max.max, prev_sum.max)};
+ capacity_used = {
+ std::max(capacity_used.min,
+ CapSub(path_capacity.min, min_max_delta.min)),
+ std::min(capacity_used.max,
+ CapSub(path_capacity.max, min_max_delta.max))};
+ if (capacity_used.min > capacity_used.max) return false;
+ // Move to last node's state, which is valid since we did not return.
+ const Interval last_sum = partial_demand_sums_rmq_[0][last_index];
+ capacity_used = {
+ CapSub(CapAdd(capacity_used.min, last_sum.min), prev_sum.min),
+ CapSub(CapAdd(capacity_used.max, last_sum.max), prev_sum.max)};
+ } else {
+ for (const int node : chain) {
+ const Interval node_capacity = node_capacity_[node];
+ capacity_used = {std::max(capacity_used.min, node_capacity.min),
+ std::min(capacity_used.max, node_capacity.max)};
+ if (capacity_used.min > capacity_used.max) return false;
+ const Interval demand = demand_[path_class][node];
+ capacity_used = {CapAdd(capacity_used.min, demand.min),
+ CapAdd(capacity_used.max, demand.max)};
+ capacity_used = {std::max(capacity_used.min, path_capacity.min),
+ std::min(capacity_used.max, path_capacity.max)};
+ }
+ }
+ }
+ if (std::max(capacity_used.min, path_capacity.min) >
+ std::min(capacity_used.max, path_capacity.max)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+// TODO(user): use amortized algorithm instead of rebuilding from scratch.
+void UnaryDimensionChecker::Commit() {
+ previous_nontrivial_index_.clear();
+ const int num_paths = path_state_->NumPaths();
+ int index = 0;
+ for (int path = 0; path < num_paths; ++path) {
+ const int path_class = path_class_[path];
+ Interval demand_sum = {0, 0};
+ int previous_nontrivial_index = -1;
+ // Value of partial_demand_sums_rmq_ at node_index-1 must be the sum
+ // of all demands of nodes before node.
+ partial_demand_sums_rmq_[0][index] = demand_sum;
+ previous_nontrivial_index_.push_back(-1);
+ ++index;
+
+ for (const int node : path_state_->Nodes(path)) {
+ index_[node] = index;
+ const Interval demand = demand_[path_class][node];
+ demand_sum = {CapAdd(demand_sum.min, demand.min),
+ CapAdd(demand_sum.max, demand.max)};
+ partial_demand_sums_rmq_[0][index] = demand_sum;
+
+ const Interval node_capacity = node_capacity_[node];
+ if (demand.min != demand.max || node_capacity.min != kint64min ||
+ node_capacity.max != kint64max) {
+ previous_nontrivial_index = index;
+ }
+ previous_nontrivial_index_.push_back(previous_nontrivial_index);
+ ++index;
+ }
+ }
+ // Update RMQ structure:
+ // sums[l+1][i] = min/max(sums[l][i], sums[l][i+2^l]) if i+2^l < num_indices,
+ // sums[l+1][i] otherwise.
+ const int num_indices = index_.size();
+ for (int layer = 1, window_size = 1; layer <= maximum_rmq_exponent_;
+ ++layer, window_size *= 2) {
+ for (int i = 0; i < num_indices - window_size; ++i) {
+ const Interval i1 = partial_demand_sums_rmq_[layer - 1][i];
+ const Interval i2 = partial_demand_sums_rmq_[layer - 1][i + window_size];
+ partial_demand_sums_rmq_[layer][i] = {std::min(i1.min, i2.min),
+ std::max(i1.max, i2.max)};
+ }
+ std::copy(
+ partial_demand_sums_rmq_[layer - 1].begin() + num_indices - window_size,
+ partial_demand_sums_rmq_[layer - 1].begin() + num_indices,
+ partial_demand_sums_rmq_[layer].begin() + num_indices - window_size);
+ }
+}
+
+// TODO(user): since this is called only when
+// last_node_index - first_node_index is large enough,
+// the lower layers of partial_demand_sums_rmq_ are never used.
+// Moreover, paths are rarely long enough to warrant the use of
+// higher layers: allocate those only when needed.
+// For instance, if this is only called when the range size is > 4
+// and paths are <= 32 nodes long, then we only need layers 0, 2, 3, and 4.
+// To compare, on a 512 < #nodes <= 1024 problem, this uses layers in [0, 10].
+UnaryDimensionChecker::Interval
+UnaryDimensionChecker::GetMinMaxPartialDemandSum(int first_node_index,
+ int last_node_index) const {
+ DCHECK_LT(first_node_index, last_node_index);
+ // Find largest window_size = 2^layer such that
+ // first_node_index < last_node_index - window_size + 1.
+ const int layer =
+ MostSignificantBitPosition32(last_node_index - first_node_index);
+ const int window_size = 1 << layer;
+ // Classical range min/max query in O(1).
+ const Interval i1 = partial_demand_sums_rmq_[layer][first_node_index];
+ const Interval i2 =
+ partial_demand_sums_rmq_[layer][last_node_index - window_size + 1];
+ return {std::min(i1.min, i2.min), std::max(i1.max, i2.max)};
+}
+
+bool UnaryDimensionChecker::SubpathOnlyHasTrivialNodes(
+ int first_node_index, int last_node_index) const {
+ DCHECK_LE(first_node_index, last_node_index);
+ return first_node_index > previous_nontrivial_index_[last_node_index];
+}
+
+namespace {
+
+class UnaryDimensionFilter : public LocalSearchFilter {
+ public:
+ std::string DebugString() const override { return "UnaryDimensionFilter"; }
+ explicit UnaryDimensionFilter(std::unique_ptr checker)
+ : checker_(std::move(checker)) {}
+
+ bool Accept(const Assignment* delta, const Assignment* deltadelta,
+ int64 objective_min, int64 objective_max) override {
+ return checker_->Check();
+ }
+
+ void Synchronize(const Assignment* assignment,
+ const Assignment* delta) override {
+ checker_->Commit();
+ }
+
+ private:
+ std::unique_ptr checker_;
+};
+
+} // namespace
+
+LocalSearchFilter* MakeUnaryDimensionFilter(
+ Solver* solver, std::unique_ptr checker) {
+ UnaryDimensionFilter* filter = new UnaryDimensionFilter(std::move(checker));
+ return solver->RevAlloc(filter);
+}
+
namespace {
// ----- Variable domain filter -----
// Rejects assignments to values outside the domain of variables
diff --git a/ortools/constraint_solver/routing.cc b/ortools/constraint_solver/routing.cc
index 6a71e578a3..5c9ab58a0b 100644
--- a/ortools/constraint_solver/routing.cc
+++ b/ortools/constraint_solver/routing.cc
@@ -194,47 +194,37 @@ class SetCumulsFromLocalDimensionCosts : public DecisionBuilder {
const RoutingDimension* const dimension = optimizer->dimension();
RoutingModel* const model = dimension->model();
const auto next = [model](int64 i) { return model->NextVar(i)->Value(); };
+ const auto compute_cumul_values =
+ [this, &next](LocalDimensionCumulOptimizer* optimizer, int vehicle,
+ std::vector* cumul_values) {
+ return optimize_and_pack_ ? optimizer->ComputePackedRouteCumuls(
+ vehicle, next, cumul_values)
+ : optimizer->ComputeRouteCumuls(
+ vehicle, next, cumul_values);
+ };
for (int vehicle = 0; vehicle < model->vehicles(); ++vehicle) {
// TODO(user): Investigate if we should skip unused vehicles.
DCHECK(DimensionFixedTransitsEqualTransitEvaluatorForVehicle(*dimension,
vehicle));
std::vector cumul_values;
- bool cumuls_optimized =
- optimize_and_pack_
- ? optimizer->ComputePackedRouteCumuls(vehicle, next,
- &cumul_values)
- : optimizer->ComputeRouteCumuls(vehicle, next, &cumul_values);
- if (!cumuls_optimized) {
+ const DimensionSchedulingStatus status =
+ compute_cumul_values(optimizer.get(), vehicle, &cumul_values);
+ if (status == DimensionSchedulingStatus::INFEASIBLE) {
should_fail = true;
break;
}
- // Check if relaxed solution is feasible (for disjoint time windows).
- // TODO(user): Move this logic to LocalDimensionCumulOptimizer.
- bool is_feasible = true;
- int64 node = model->Start(vehicle);
- for (int64 cumul_value : cumul_values) {
- if (cumul_value !=
- dimension->GetFirstPossibleGreaterOrEqualValueForNode(
- node, cumul_value)) {
- is_feasible = false;
- break;
- }
- if (!model->IsEnd(node)) node = model->NextVar(node)->Value();
- }
// If relaxation is not feasible, try the MILP optimizer.
- if (!is_feasible) {
+ if (status == DimensionSchedulingStatus::RELAXED_OPTIMAL_ONLY) {
cumul_values.clear();
DCHECK(local_mp_optimizers_[i] != nullptr);
- cumuls_optimized =
- optimize_and_pack_
- ? local_mp_optimizers_[i]->ComputePackedRouteCumuls(
- vehicle, next, &cumul_values)
- : local_mp_optimizers_[i]->ComputeRouteCumuls(vehicle, next,
- &cumul_values);
- if (!cumuls_optimized) {
+ if (compute_cumul_values(local_mp_optimizers_[i].get(), vehicle,
+ &cumul_values) ==
+ DimensionSchedulingStatus::INFEASIBLE) {
should_fail = true;
break;
}
+ } else {
+ DCHECK(status == DimensionSchedulingStatus::OPTIMAL);
}
std::vector cumuls;
int current = model->Start(vehicle);
@@ -754,6 +744,7 @@ RoutingModel::RoutingModel(const RoutingIndexManager& index_manager,
index_to_pickup_index_pairs_.resize(size);
index_to_delivery_index_pairs_.resize(size);
index_to_visit_type_.resize(index_manager.num_indices(), kUnassigned);
+ index_to_type_policy_.resize(index_manager.num_indices());
index_to_vehicle_.resize(index_manager.num_indices(), kUnassigned);
for (int v = 0; v < index_manager.num_vehicles(); ++v) {
@@ -1948,8 +1939,13 @@ void RoutingModel::CloseModelWithParameters(
active_[i]->SetValue(1);
}
const int type = GetVisitType(i);
- if (type != kUnassigned &&
- gtl::ContainsKey(trivially_infeasible_visit_types_, type)) {
+ if (type == kUnassigned) {
+ continue;
+ }
+ const absl::flat_hash_set* const infeasible_policies =
+ gtl::FindOrNull(trivially_infeasible_visit_types_to_policies_, type);
+ if (infeasible_policies != nullptr &&
+ gtl::ContainsKey(*infeasible_policies, index_to_type_policy_[i])) {
active_[i]->SetValue(0);
}
}
@@ -3823,9 +3819,11 @@ bool RoutingModel::ArcIsMoreConstrainedThanArc(int64 from, int64 to1,
return to1 < to2;
}
-void RoutingModel::SetVisitType(int64 index, int type) {
+void RoutingModel::SetVisitType(int64 index, int type, VisitTypePolicy policy) {
CHECK_LT(index, index_to_visit_type_.size());
+ DCHECK_EQ(index_to_visit_type_.size(), index_to_type_policy_.size());
index_to_visit_type_[index] = type;
+ index_to_type_policy_[index] = policy;
num_visit_types_ = std::max(num_visit_types_, type + 1);
}
@@ -3834,12 +3832,19 @@ int RoutingModel::GetVisitType(int64 index) const {
return index_to_visit_type_[index];
}
+RoutingModel::VisitTypePolicy RoutingModel::GetVisitTypePolicy(
+ int64 index) const {
+ CHECK_LT(index, index_to_type_policy_.size());
+ return index_to_type_policy_[index];
+}
+
void RoutingModel::CloseVisitTypes() {
hard_incompatible_types_per_type_index_.resize(num_visit_types_);
temporal_incompatible_types_per_type_index_.resize(num_visit_types_);
same_vehicle_required_type_alternatives_per_type_index_.resize(
num_visit_types_);
- temporal_required_type_alternatives_per_type_index_.resize(num_visit_types_);
+ required_type_alternatives_when_adding_type_index_.resize(num_visit_types_);
+ required_type_alternatives_when_removing_type_index_.resize(num_visit_types_);
}
void RoutingModel::AddHardTypeIncompatibility(int type1, int type2) {
@@ -3883,7 +3888,13 @@ void RoutingModel::AddSameVehicleRequiredTypeAlternatives(
if (required_type_alternatives.empty()) {
// The dependent_type requires an infeasible (empty) set of types.
- trivially_infeasible_visit_types_.insert(dependent_type);
+ // Nodes of this type and all policies except
+ // ADDED_TYPE_REMOVED_FROM_VEHICLE are trivially infeasible.
+ absl::flat_hash_set& infeasible_policies =
+ trivially_infeasible_visit_types_to_policies_[dependent_type];
+ infeasible_policies.insert(TYPE_ADDED_TO_VEHICLE);
+ infeasible_policies.insert(TYPE_ON_VEHICLE_UP_TO_VISIT);
+ infeasible_policies.insert(TYPE_SIMULTANEOUSLY_ADDED_AND_REMOVED);
return;
}
@@ -3892,22 +3903,49 @@ void RoutingModel::AddSameVehicleRequiredTypeAlternatives(
.push_back(std::move(required_type_alternatives));
}
-void RoutingModel::AddTemporalRequiredTypeAlternatives(
+void RoutingModel::AddRequiredTypeAlternativesWhenAddingType(
int dependent_type, absl::flat_hash_set required_type_alternatives) {
DCHECK_LT(dependent_type,
- temporal_required_type_alternatives_per_type_index_.size());
+ required_type_alternatives_when_adding_type_index_.size());
if (required_type_alternatives.empty()) {
// The dependent_type requires an infeasible (empty) set of types.
- trivially_infeasible_visit_types_.insert(dependent_type);
+ // Nodes of this type and policy TYPE_ADDED_TO_VEHICLE or
+ // TYPE_SIMULTANEOUSLY_ADDED_AND_REMOVED are trivially infeasible.
+ absl::flat_hash_set& infeasible_policies =
+ trivially_infeasible_visit_types_to_policies_[dependent_type];
+ infeasible_policies.insert(TYPE_ADDED_TO_VEHICLE);
+ infeasible_policies.insert(TYPE_SIMULTANEOUSLY_ADDED_AND_REMOVED);
return;
}
has_temporal_type_requirements_ = true;
- temporal_required_type_alternatives_per_type_index_[dependent_type].push_back(
+ required_type_alternatives_when_adding_type_index_[dependent_type].push_back(
std::move(required_type_alternatives));
}
+void RoutingModel::AddRequiredTypeAlternativesWhenRemovingType(
+ int dependent_type, absl::flat_hash_set required_type_alternatives) {
+ DCHECK_LT(dependent_type,
+ required_type_alternatives_when_removing_type_index_.size());
+
+ if (required_type_alternatives.empty()) {
+ // The dependent_type requires an infeasible (empty) set of types.
+ // Nodes of this type and all policies except TYPE_ADDED_TO_VEHICLE are
+ // trivially infeasible.
+ absl::flat_hash_set& infeasible_policies =
+ trivially_infeasible_visit_types_to_policies_[dependent_type];
+ infeasible_policies.insert(ADDED_TYPE_REMOVED_FROM_VEHICLE);
+ infeasible_policies.insert(TYPE_ON_VEHICLE_UP_TO_VISIT);
+ infeasible_policies.insert(TYPE_SIMULTANEOUSLY_ADDED_AND_REMOVED);
+ return;
+ }
+
+ has_temporal_type_requirements_ = true;
+ required_type_alternatives_when_removing_type_index_[dependent_type]
+ .push_back(std::move(required_type_alternatives));
+}
+
const std::vector>&
RoutingModel::GetSameVehicleRequiredTypeAlternativesOfType(int type) const {
DCHECK_GE(type, 0);
@@ -3917,10 +3955,17 @@ RoutingModel::GetSameVehicleRequiredTypeAlternativesOfType(int type) const {
}
const std::vector>&
-RoutingModel::GetTemporalRequiredTypeAlternativesOfType(int type) const {
+RoutingModel::GetRequiredTypeAlternativesWhenAddingType(int type) const {
DCHECK_GE(type, 0);
- DCHECK_LT(type, temporal_required_type_alternatives_per_type_index_.size());
- return temporal_required_type_alternatives_per_type_index_[type];
+ DCHECK_LT(type, required_type_alternatives_when_adding_type_index_.size());
+ return required_type_alternatives_when_adding_type_index_[type];
+}
+
+const std::vector>&
+RoutingModel::GetRequiredTypeAlternativesWhenRemovingType(int type) const {
+ DCHECK_GE(type, 0);
+ DCHECK_LT(type, required_type_alternatives_when_removing_type_index_.size());
+ return required_type_alternatives_when_removing_type_index_[type];
}
int64 RoutingModel::UnperformedPenalty(int64 var_index) const {
@@ -5782,74 +5827,6 @@ void RoutingDimension::InitializeTransits(
InitializeTransitVariables(slack_max);
}
-void AppendTasksFromPath(const std::vector& path,
- const std::vector& min_travels,
- const std::vector& max_travels,
- const std::vector& pre_travels,
- const std::vector& post_travels,
- const RoutingDimension& dimension,
- DisjunctivePropagator::Tasks* tasks) {
- const int num_nodes = path.size();
- DCHECK_EQ(pre_travels.size(), num_nodes - 1);
- DCHECK_EQ(post_travels.size(), num_nodes - 1);
- for (int i = 0; i < num_nodes; ++i) {
- const int64 cumul_min = dimension.CumulVar(path[i])->Min();
- const int64 cumul_max = dimension.CumulVar(path[i])->Max();
- // Add task associated to visit i.
- // Visits start at Cumul(path[i]) - before_visit
- // and end at Cumul(path[i]) + after_visit
- {
- const int64 before_visit = (i == 0) ? 0 : post_travels[i - 1];
- const int64 after_visit = (i == num_nodes - 1) ? 0 : pre_travels[i];
-
- tasks->start_min.push_back(CapSub(cumul_min, before_visit));
- tasks->start_max.push_back(CapSub(cumul_max, before_visit));
- tasks->duration_min.push_back(CapAdd(before_visit, after_visit));
- tasks->duration_max.push_back(CapAdd(before_visit, after_visit));
- tasks->end_min.push_back(CapAdd(cumul_min, after_visit));
- tasks->end_max.push_back(CapAdd(cumul_max, after_visit));
- tasks->is_preemptible.push_back(false);
- }
- if (i == num_nodes - 1) break;
-
- // Tasks from travels.
- // A travel task starts at Cumul(path[i]) + pre_travel,
- // last for FixedTransitVar(path[i]) - pre_travel - post_travel,
- // and must end at the latest at Cumul(path[i+1]) - post_travel.
- {
- const int64 pre_travel = pre_travels[i];
- const int64 post_travel = post_travels[i];
- tasks->start_min.push_back(CapAdd(cumul_min, pre_travel));
- tasks->start_max.push_back(CapAdd(cumul_max, pre_travel));
- tasks->duration_min.push_back(std::max(
- 0, CapSub(min_travels[i], CapAdd(pre_travel, post_travel))));
- tasks->duration_max.push_back(
- max_travels[i] == kint64max
- ? kint64max
- : std::max(0, CapSub(max_travels[i],
- CapAdd(pre_travel, post_travel))));
- tasks->end_min.push_back(
- CapSub(dimension.CumulVar(path[i + 1])->Min(), post_travel));
- tasks->end_max.push_back(
- CapSub(dimension.CumulVar(path[i + 1])->Max(), post_travel));
- tasks->is_preemptible.push_back(true);
- }
- }
-}
-
-void AppendTasksFromIntervals(const std::vector& intervals,
- DisjunctivePropagator::Tasks* tasks) {
- for (IntervalVar* interval : intervals) {
- if (!interval->MustBePerformed()) continue;
- tasks->start_min.push_back(interval->StartMin());
- tasks->start_max.push_back(interval->StartMax());
- tasks->duration_min.push_back(interval->DurationMin());
- tasks->duration_max.push_back(interval->DurationMax());
- tasks->end_min.push_back(interval->EndMin());
- tasks->end_max.push_back(interval->EndMax());
- tasks->is_preemptible.push_back(false);
- }
-}
void FillPathEvaluation(const std::vector& path,
const RoutingModel::TransitCallback2& evaluator,
@@ -5862,33 +5839,7 @@ void FillPathEvaluation(const std::vector& path,
}
TypeRegulationsChecker::TypeRegulationsChecker(const RoutingModel& model)
- : model_(model),
- pickup_delivery_status_of_node_(model.Size()),
- counts_of_type_(model.GetNumberOfVisitTypes()) {
- if (!model.HasTypeRegulations()) {
- return;
- }
- for (int node_index = 0; node_index < model.Size(); node_index++) {
- const std::vector>& pickup_index_pairs =
- model.GetPickupIndexPairs(node_index);
- const std::vector>& delivery_index_pairs =
- model.GetDeliveryIndexPairs(node_index);
- if (!pickup_index_pairs.empty()) {
- // Pickup node. We verify that it's not a delivery and that it appears in
- // a single pickup index pair.
- CHECK(delivery_index_pairs.empty());
- CHECK_EQ(pickup_index_pairs.size(), 1);
- pickup_delivery_status_of_node_[node_index] = PICKUP;
- } else if (!delivery_index_pairs.empty()) {
- // Delivery node. Check that it appears in a single delivery index pair.
- CHECK_EQ(delivery_index_pairs.size(), 1);
- pickup_delivery_status_of_node_[node_index] = DELIVERY;
- } else {
- // Neither pickup nor delivery.
- pickup_delivery_status_of_node_[node_index] = NONE;
- }
- }
-}
+ : model_(model), occurrences_of_type_(model.GetNumberOfVisitTypes()) {}
bool TypeRegulationsChecker::CheckVehicle(
int vehicle, const std::function& next_accessor) {
@@ -5896,52 +5847,80 @@ bool TypeRegulationsChecker::CheckVehicle(
return true;
}
- InitializeCheck();
+ InitializeCheck(vehicle, next_accessor);
- // Accumulates the count of types before the current node.
- // {0, 0, 0} does not compile on or-tools.
- counts_of_type_.assign(model_.GetNumberOfVisitTypes(),
- TypeRegulationsChecker::NodeCount());
-
- for (int64 current = model_.Start(vehicle); !model_.IsEnd(current);
- current = next_accessor(current)) {
- const int type = model_.GetVisitType(current);
+ for (int pos = 0; pos < current_route_visits_.size(); pos++) {
+ const int64 current_visit = current_route_visits_[pos];
+ const int type = model_.GetVisitType(current_visit);
if (type < 0) {
continue;
}
- DCHECK_LT(type, counts_of_type_.size());
- const PickupDeliveryStatus pickup_delivery_status =
- pickup_delivery_status_of_node_[current];
- NodeCount& counts = counts_of_type_[type];
- if (pickup_delivery_status == DELIVERY) {
- // The node is a delivery.
- if (counts.pickup <= counts.delivery) {
- // All pickups for this type have already been delivered, so we're
- // missing a pickup for this delivery.
- return false;
- }
- counts.delivery++;
+ const VisitTypePolicy policy = model_.GetVisitTypePolicy(current_visit);
+
+ DCHECK_LT(type, occurrences_of_type_.size());
+ int& num_type_added = occurrences_of_type_[type].num_type_added_to_vehicle;
+ int& num_type_removed =
+ occurrences_of_type_[type].num_type_removed_from_vehicle;
+ DCHECK_LE(num_type_removed, num_type_added);
+ if (policy == RoutingModel::ADDED_TYPE_REMOVED_FROM_VEHICLE &&
+ num_type_removed == num_type_added) {
+ // The type is not actually being removed as all added types have already
+ // been removed.
continue;
}
- // The node is either a pickup or a "fixed" (non-pickup/delivery) node.
- if (!CheckTypeRegulations(type)) {
+
+ if (!CheckTypeRegulations(type, policy, pos)) {
return false;
}
- // Update count of type based on whether it is a pickup or not.
- int& count = pickup_delivery_status == NONE ? counts.non_pickup_delivery
- : counts.pickup;
- count++;
+ // Update count of type based on the visit policy.
+ if (policy == VisitTypePolicy::TYPE_ADDED_TO_VEHICLE ||
+ policy == VisitTypePolicy::TYPE_SIMULTANEOUSLY_ADDED_AND_REMOVED) {
+ num_type_added++;
+ }
+ if (policy == VisitTypePolicy::TYPE_SIMULTANEOUSLY_ADDED_AND_REMOVED ||
+ policy == VisitTypePolicy::ADDED_TYPE_REMOVED_FROM_VEHICLE) {
+ num_type_removed++;
+ }
}
return FinalizeCheck();
}
-int TypeRegulationsChecker::GetNonDeliveryCount(int type) const {
- const NodeCount& counts = counts_of_type_[type];
- return counts.non_pickup_delivery + counts.pickup;
+void TypeRegulationsChecker::InitializeCheck(
+ int vehicle, const std::function& next_accessor) {
+ // Accumulates the count of types before the current node.
+ // {0, 0, -1} does not compile on or-tools.
+ std::fill(occurrences_of_type_.begin(), occurrences_of_type_.end(),
+ TypeRegulationsChecker::TypePolicyOccurrence());
+
+ // TODO(user): Optimize the filter to avoid scanning the route an extra
+ // time when there are no TYPE_ON_VEHICLE_UP_TO_VISIT policies on the route,
+ // by passing a boolean to CheckVehicle() passed to InitializeCheck().
+ current_route_visits_.clear();
+ for (int64 current = model_.Start(vehicle); !model_.IsEnd(current);
+ current = next_accessor(current)) {
+ const int type = model_.GetVisitType(current);
+ if (type >= 0 && model_.GetVisitTypePolicy(current) ==
+ VisitTypePolicy::TYPE_ON_VEHICLE_UP_TO_VISIT) {
+ occurrences_of_type_[type].position_of_last_type_on_vehicle_up_to_visit =
+ current_route_visits_.size();
+ }
+ current_route_visits_.push_back(current);
+ }
+
+ OnInitializeCheck();
}
-int TypeRegulationsChecker::GetNonDeliveredCount(int type) const {
- return GetNonDeliveryCount(type) - counts_of_type_[type].delivery;
+bool TypeRegulationsChecker::TypeOccursOnRoute(int type) const {
+ const TypePolicyOccurrence& occurrences = occurrences_of_type_[type];
+ return occurrences.num_type_added_to_vehicle > 0 ||
+ occurrences.position_of_last_type_on_vehicle_up_to_visit >= 0;
+}
+
+bool TypeRegulationsChecker::TypeCurrentlyOnRoute(int type, int pos) const {
+ const TypePolicyOccurrence& occurrences = occurrences_of_type_[type];
+ return occurrences.num_type_removed_from_vehicle <
+ occurrences.num_type_added_to_vehicle ||
+ occurrences.position_of_last_type_on_vehicle_up_to_visit >= pos;
}
TypeIncompatibilityChecker::TypeIncompatibilityChecker(
@@ -5960,17 +5939,24 @@ bool TypeIncompatibilityChecker::HasRegulationsToCheck() const {
// TODO(user): Improve algorithm by only checking a given type if necessary?
// - For temporal incompatibilities, only check if NonDeliveredType(count) == 1.
// - For hard incompatibilities, only if NonDeliveryType(type) == 1.
-bool TypeIncompatibilityChecker::CheckTypeRegulations(int type) {
+bool TypeIncompatibilityChecker::CheckTypeRegulations(int type,
+ VisitTypePolicy policy,
+ int pos) {
+ if (policy == VisitTypePolicy::ADDED_TYPE_REMOVED_FROM_VEHICLE) {
+ // NOTE: We don't need to check incompatibilities when the type is being
+ // removed from the route.
+ return true;
+ }
for (int incompatible_type :
model_.GetTemporalTypeIncompatibilitiesOfType(type)) {
- if (GetNonDeliveredCount(incompatible_type) > 0) {
+ if (TypeCurrentlyOnRoute(incompatible_type, pos)) {
return false;
}
}
if (check_hard_incompatibilities_) {
for (int incompatible_type :
model_.GetHardTypeIncompatibilitiesOfType(type)) {
- if (GetNonDeliveryCount(incompatible_type) > 0) {
+ if (TypeOccursOnRoute(incompatible_type)) {
return false;
}
}
@@ -5983,12 +5969,14 @@ bool TypeRequirementChecker::HasRegulationsToCheck() const {
model_.HasSameVehicleTypeRequirements();
}
-bool TypeRequirementChecker::CheckTypeRegulations(int type) {
+bool TypeRequirementChecker::CheckRequiredTypesCurrentlyOnRoute(
+ const std::vector>& required_type_alternatives,
+ int pos) {
for (const absl::flat_hash_set& requirement_alternatives :
- model_.GetTemporalRequiredTypeAlternativesOfType(type)) {
+ required_type_alternatives) {
bool has_one_of_alternatives = false;
- for (const int type_alternative : requirement_alternatives) {
- if (GetNonDeliveredCount(type_alternative) > 0) {
+ for (int type_alternative : requirement_alternatives) {
+ if (TypeCurrentlyOnRoute(type_alternative, pos)) {
has_one_of_alternatives = true;
break;
}
@@ -5997,7 +5985,27 @@ bool TypeRequirementChecker::CheckTypeRegulations(int type) {
return false;
}
}
- if (!model_.GetSameVehicleRequiredTypeAlternativesOfType(type).empty()) {
+ return true;
+}
+
+bool TypeRequirementChecker::CheckTypeRegulations(int type,
+ VisitTypePolicy policy,
+ int pos) {
+ if (policy == RoutingModel::TYPE_ADDED_TO_VEHICLE ||
+ policy == RoutingModel::TYPE_SIMULTANEOUSLY_ADDED_AND_REMOVED) {
+ if (!CheckRequiredTypesCurrentlyOnRoute(
+ model_.GetRequiredTypeAlternativesWhenAddingType(type), pos)) {
+ return false;
+ }
+ }
+ if (policy != RoutingModel::TYPE_ADDED_TO_VEHICLE) {
+ if (!CheckRequiredTypesCurrentlyOnRoute(
+ model_.GetRequiredTypeAlternativesWhenRemovingType(type), pos)) {
+ return false;
+ }
+ }
+ if (policy != RoutingModel::ADDED_TYPE_REMOVED_FROM_VEHICLE &&
+ !model_.GetSameVehicleRequiredTypeAlternativesOfType(type).empty()) {
types_with_same_vehicle_requirements_on_route_.insert(type);
}
return true;
@@ -6009,7 +6017,7 @@ bool TypeRequirementChecker::FinalizeCheck() const {
model_.GetSameVehicleRequiredTypeAlternativesOfType(type)) {
bool has_one_of_alternatives = false;
for (const int type_alternative : requirement_alternatives) {
- if (GetNonDeliveryCount(type_alternative) > 0) {
+ if (TypeOccursOnRoute(type_alternative)) {
has_one_of_alternatives = true;
break;
}
@@ -6076,144 +6084,6 @@ void TypeRegulationsConstraint::InitialPropagate() {
}
}
-bool DisjunctivePropagator::DistanceDuration(Tasks* tasks) {
- if (tasks->distance_duration.empty()) return true;
- if (tasks->num_chain_tasks == 0) return true;
- const int route_start = 0;
- const int route_end = tasks->num_chain_tasks - 1;
- const int num_tasks = tasks->start_min.size();
- for (int i = 0; i < tasks->distance_duration.size(); ++i) {
- const int64 max_distance = tasks->distance_duration[i].first;
- const int64 minimum_break_duration = tasks->distance_duration[i].second;
-
- // Special case: no breaks.
- if (tasks->num_chain_tasks == num_tasks) {
- tasks->end_min[route_start] =
- std::max(tasks->end_min[route_start],
- CapSub(tasks->start_min[route_end], max_distance));
- tasks->start_max[route_end] =
- std::min(tasks->start_max[route_end],
- CapAdd(tasks->end_max[route_start], max_distance));
- continue;
- }
-
- // This is a sweeping algorithm that looks whether the union of intervals
- // defined by breaks and route start/end is (-infty, +infty).
- // Those intervals are:
- // - route start: (-infty, start_max + distance]
- // - route end: [end_min, +infty)
- // - breaks: [start_min, end_max + distance) if their duration_max
- // is >= min_duration, empty set otherwise.
- // If sweeping finds that a time point can be covered by only one interval,
- // it will force the corresponding break or route start/end to cover this
- // point, which can force a break to be above minimum_break_duration.
-
- // We suppose break tasks are ordered, so the algorithm supposes that
- // start_min(task_n) <= start_min(task_{n+1}) and
- // end_max(task_n) <= end_max(task_{n+1}).
- for (int task = tasks->num_chain_tasks + 1; task < num_tasks; ++task) {
- tasks->start_min[task] =
- std::max(tasks->start_min[task], tasks->start_min[task - 1]);
- }
- for (int task = num_tasks - 2; task >= tasks->num_chain_tasks; --task) {
- tasks->end_max[task] =
- std::min(tasks->end_max[task], tasks->end_max[task + 1]);
- }
- // Initial state: start at -inf with route_start in task_set.
- // Sweep over profile, looking for time points where the number of
- // covering breaks is <= 1. If it is 0, fail, otherwise force the
- // unique break to cover it.
- // Route start and end get a special treatment, not sure generalizing
- // would be better.
- int64 xor_active_tasks = route_start;
- int num_active_tasks = 1;
- int64 previous_time = kint64min;
- const int64 route_start_time =
- CapAdd(tasks->end_max[route_start], max_distance);
- const int64 route_end_time = tasks->start_min[route_end];
- int index_break_by_smin = tasks->num_chain_tasks;
- int index_break_by_emax = tasks->num_chain_tasks;
- while (index_break_by_emax < num_tasks) {
- // Find next time point among start/end of covering intervals.
- int64 current_time =
- CapAdd(tasks->end_max[index_break_by_emax], max_distance);
- if (index_break_by_smin < num_tasks) {
- current_time =
- std::min(current_time, tasks->start_min[index_break_by_smin]);
- }
- if (previous_time < route_start_time && route_start_time < current_time) {
- current_time = route_start_time;
- }
- if (previous_time < route_end_time && route_end_time < current_time) {
- current_time = route_end_time;
- }
- // If num_active_tasks was 1, the unique active task must cover from
- // previous_time to current_time.
- if (num_active_tasks == 1) {
- // xor_active_tasks is the unique task that can cover [previous_time,
- // current_time).
- if (xor_active_tasks != route_end) {
- tasks->end_min[xor_active_tasks] =
- std::max(tasks->end_min[xor_active_tasks],
- CapSub(current_time, max_distance));
- if (xor_active_tasks != route_start) {
- tasks->duration_min[xor_active_tasks] = std::max(
- tasks->duration_min[xor_active_tasks],
- std::max(
- minimum_break_duration,
- CapSub(CapSub(current_time, max_distance), previous_time)));
- }
- }
- }
- // Process covering intervals that start or end at current_time.
- while (index_break_by_smin < num_tasks &&
- current_time == tasks->start_min[index_break_by_smin]) {
- if (tasks->duration_max[index_break_by_smin] >=
- minimum_break_duration) {
- xor_active_tasks ^= index_break_by_smin;
- ++num_active_tasks;
- }
- ++index_break_by_smin;
- }
- while (index_break_by_emax < num_tasks &&
- current_time ==
- CapAdd(tasks->end_max[index_break_by_emax], max_distance)) {
- if (tasks->duration_max[index_break_by_emax] >=
- minimum_break_duration) {
- xor_active_tasks ^= index_break_by_emax;
- --num_active_tasks;
- }
- ++index_break_by_emax;
- }
- if (current_time == route_start_time) {
- xor_active_tasks ^= route_start;
- --num_active_tasks;
- }
- if (current_time == route_end_time) {
- xor_active_tasks ^= route_end;
- ++num_active_tasks;
- }
- // If num_active_tasks becomes 1, the unique active task must cover from
- // current_time.
- if (num_active_tasks <= 0) return false;
- if (num_active_tasks == 1) {
- if (xor_active_tasks != route_start) {
- // xor_active_tasks is the unique task that can cover from
- // current_time to the next time point.
- tasks->start_max[xor_active_tasks] =
- std::min(tasks->start_max[xor_active_tasks], current_time);
- if (xor_active_tasks != route_end) {
- tasks->duration_min[xor_active_tasks] = std::max(
- tasks->duration_min[xor_active_tasks], minimum_break_duration);
- }
- }
- }
- previous_time = current_time;
- }
- }
- return true;
-}
-
void RoutingDimension::CloseModel(bool use_light_propagation) {
Solver* const solver = model_->solver();
const auto capacity_lambda = [this](int64 vehicle) {
diff --git a/ortools/constraint_solver/routing.h b/ortools/constraint_solver/routing.h
index bfa8e9c71a..b0a7020547 100644
--- a/ortools/constraint_solver/routing.h
+++ b/ortools/constraint_solver/routing.h
@@ -147,7 +147,7 @@
/// for (int64 node = routing.Start(route_number);
/// !routing.IsEnd(node);
/// node = solution->Value(routing.NextVar(node))) {
-/// LOG(INFO) << routing.IndexToNode(node);
+/// LOG(INFO) << manager.IndexToNode(node);
/// }
///
///
@@ -713,21 +713,35 @@ class RoutingModel {
/// Set the node visit types and incompatibilities/requirements between the
/// types (see below).
///
- /// NOTE: The visit type of a node must be positive, and all nodes belonging
- /// to the same pickup/delivery pair must have the same type (or no type at
- /// all).
- ///
/// NOTE: Before adding any incompatibilities and/or requirements on types:
/// 1) All corresponding node types must have been set.
/// 2) CloseVisitTypes() must be called so all containers are resized
/// accordingly.
///
- /// NOTE: These incompatibilities and requirements are only handled when each
- /// node index appears in at most one pickup/delivery pair, i.e. when the same
- /// node isn't a pickup and/or delivery in multiple pickup/delivery pairs.
+ /// The following enum is used to describe how a node with a given type 'T'
+ /// impacts the number of types 'T' on the route when visited, and thus
+ /// determines how temporal incompatibilities and requirements take effect.
+ enum VisitTypePolicy {
+ /// When visited, the number of types 'T' on the vehicle increases by one.
+ TYPE_ADDED_TO_VEHICLE,
+ /// When visited, one instance of type 'T' previously added to the route
+ /// (TYPE_ADDED_TO_VEHICLE), if any, is removed from the vehicle.
+ /// If the type was not previously added to the route or all added instances
+ /// have already been removed, this visit has no effect on the types.
+ ADDED_TYPE_REMOVED_FROM_VEHICLE,
+ /// With the following policy, the visit enforces that type 'T' is
+ /// considered on the route from its start until this node is visited.
+ TYPE_ON_VEHICLE_UP_TO_VISIT,
+ /// The visit doesn't have an impact on the number of types 'T' on the
+ /// route, as it's (virtually) added and removed directly.
+ /// This policy can be used for visits which are part of an incompatibility
+ /// or requirement set without affecting the type count on the route.
+ TYPE_SIMULTANEOUSLY_ADDED_AND_REMOVED
+ };
// TODO(user): Support multiple visit types per node?
- void SetVisitType(int64 index, int type);
+ void SetVisitType(int64 index, int type, VisitTypePolicy type_policy);
int GetVisitType(int64 index) const;
+ VisitTypePolicy GetVisitTypePolicy(int64 index) const;
/// This function should be called once all node visit types have been set and
/// prior to adding any incompatibilities/requirements.
// TODO(user): Reconsider the logic and potentially remove the need to
@@ -765,18 +779,30 @@ class RoutingModel {
/// route.
void AddSameVehicleRequiredTypeAlternatives(
int dependent_type, absl::flat_hash_set required_type_alternatives);
- /// If type_D temporally depends on type_R, any non-delivery node_D of type_D
- /// requires at least one non-delivered node of type_R on its vehicle at the
- /// time node_D is visited.
- void AddTemporalRequiredTypeAlternatives(
+ /// If type_D depends on type_R when adding type_D, any node_D of type_D and
+ /// VisitTypePolicy TYPE_ADDED_TO_VEHICLE or
+ /// TYPE_SIMULTANEOUSLY_ADDED_AND_REMOVED requires at least one type_R on its
+ /// vehicle at the time node_D is visited.
+ void AddRequiredTypeAlternativesWhenAddingType(
+ int dependent_type, absl::flat_hash_set required_type_alternatives);
+ /// The following requirements apply when visiting dependent nodes that remove
+ /// their type from the route, i.e. type_R must be on the vehicle when type_D
+ /// of VisitTypePolicy ADDED_TYPE_REMOVED_FROM_VEHICLE,
+ /// TYPE_ON_VEHICLE_UP_TO_VISIT or TYPE_SIMULTANEOUSLY_ADDED_AND_REMOVED is
+ /// visited.
+ void AddRequiredTypeAlternativesWhenRemovingType(
int dependent_type, absl::flat_hash_set required_type_alternatives);
// clang-format off
- /// Returns the sets of same-vehicle/temporal requirement alternatives for the
- /// given type.
+ /// Returns the set of same-vehicle requirement alternatives for the given
+ /// type.
const std::vector >&
GetSameVehicleRequiredTypeAlternativesOfType(int type) const;
+ /// Returns the set of requirement alternatives when adding the given type.
const std::vector >&
- GetTemporalRequiredTypeAlternativesOfType(int type) const;
+ GetRequiredTypeAlternativesWhenAddingType(int type) const;
+ /// Returns the set of requirement alternatives when removing the given type.
+ const std::vector >&
+ GetRequiredTypeAlternativesWhenRemovingType(int type) const;
// clang-format on
/// Returns true iff any same-route (resp. temporal) type requirements have
/// been added to the model.
@@ -1599,7 +1625,7 @@ class RoutingModel {
std::vector > same_vehicle_costs_;
/// Allowed vehicles
#ifndef SWIG
- std::vector> allowed_vehicles_;
+ std::vector> allowed_vehicles_;
#endif // SWIG
/// Pickup and delivery
IndexPairs pickup_delivery_pairs_;
@@ -1622,6 +1648,8 @@ class RoutingModel {
// Node visit types
// Variable index to visit type index.
std::vector index_to_visit_type_;
+ // Variable index to VisitTypePolicy.
+ std::vector index_to_type_policy_;
// clang-format off
std::vector >
hard_incompatible_types_per_type_index_;
@@ -1634,9 +1662,12 @@ class RoutingModel {
same_vehicle_required_type_alternatives_per_type_index_;
bool has_same_vehicle_type_requirements_;
std::vector > >
- temporal_required_type_alternatives_per_type_index_;
+ required_type_alternatives_when_adding_type_index_;
+ std::vector > >
+ required_type_alternatives_when_removing_type_index_;
bool has_temporal_type_requirements_;
- absl::flat_hash_set trivially_infeasible_visit_types_;
+ absl::flat_hash_map*type*/int, absl::flat_hash_set >
+ trivially_infeasible_visit_types_to_policies_;
// clang-format on
int num_visit_types_;
// Two indices are equivalent if they correspond to the same node (as given
@@ -1927,29 +1958,54 @@ class TypeRegulationsChecker {
const std::function& next_accessor);
protected:
- enum PickupDeliveryStatus { PICKUP, DELIVERY, NONE };
- struct NodeCount {
- int non_pickup_delivery = 0;
- int pickup = 0;
- int delivery = 0;
+#ifndef SWIG
+ using VisitTypePolicy = RoutingModel::VisitTypePolicy;
+#endif // SWIG
+
+ struct TypePolicyOccurrence {
+ /// Number of TYPE_ADDED_TO_VEHICLE and
+ /// TYPE_SIMULTANEOUSLY_ADDED_AND_REMOVED node type policies seen on the
+ /// route.
+ int num_type_added_to_vehicle = 0;
+ /// Number of ADDED_TYPE_REMOVED_FROM_VEHICLE (effectively removing a type
+ /// from the route) and TYPE_SIMULTANEOUSLY_ADDED_AND_REMOVED node type
+ /// policies seen on the route.
+ /// This number is always <= num_type_added_to_vehicle, as a type is only
+ /// actually removed if it was on the route before.
+ int num_type_removed_from_vehicle = 0;
+ /// Position of the last node of policy TYPE_ON_VEHICLE_UP_TO_VISIT visited
+ /// on the route.
+ /// If positive, the type is considered on the vehicle from the start of the
+ /// route until this position.
+ int position_of_last_type_on_vehicle_up_to_visit = -1;
};
- /// Returns the number of pickups and fixed nodes from
- /// counts_of_type_["type"].
- int GetNonDeliveryCount(int type) const;
- /// Same as above, but subtracting the number of deliveries of "type".
- int GetNonDeliveredCount(int type) const;
+ /// Returns true iff any occurrence of the given type was seen on the route,
+ /// i.e. iff the added count for this type is positive, or if a node of this
+ /// type and policy TYPE_ON_VEHICLE_UP_TO_VISIT is visited on the route (see
+ /// TypePolicyOccurrence.last_type_on_vehicle_up_to_visit).
+ bool TypeOccursOnRoute(int type) const;
+ /// Returns true iff there's at least one instance of the given type on the
+ /// route when scanning the route at the given position 'pos'.
+ /// This is the case iff we have at least one added but non-removed instance
+ /// of the type, or if
+ /// occurrences_of_type_[type].last_type_on_vehicle_up_to_visit is greater
+ /// than 'pos'.
+ bool TypeCurrentlyOnRoute(int type, int pos) const;
+ void InitializeCheck(int vehicle,
+ const std::function& next_accessor);
+ virtual void OnInitializeCheck() {}
virtual bool HasRegulationsToCheck() const = 0;
- virtual void InitializeCheck() {}
- virtual bool CheckTypeRegulations(int type) = 0;
+ virtual bool CheckTypeRegulations(int type, VisitTypePolicy policy,
+ int pos) = 0;
virtual bool FinalizeCheck() const { return true; }
const RoutingModel& model_;
private:
- std::vector pickup_delivery_status_of_node_;
- std::vector counts_of_type_;
+ std::vector occurrences_of_type_;
+ std::vector current_route_visits_;
};
/// Checker for type incompatibilities.
@@ -1961,7 +2017,7 @@ class TypeIncompatibilityChecker : public TypeRegulationsChecker {
private:
bool HasRegulationsToCheck() const override;
- bool CheckTypeRegulations(int type) override;
+ bool CheckTypeRegulations(int type, VisitTypePolicy policy, int pos) override;
/// NOTE(user): As temporal incompatibilities are always verified with
/// this checker, we only store 1 boolean indicating whether or not hard
/// incompatibilities are also verified.
@@ -1977,10 +2033,17 @@ class TypeRequirementChecker : public TypeRegulationsChecker {
private:
bool HasRegulationsToCheck() const override;
- void InitializeCheck() override {
+ void OnInitializeCheck() override {
types_with_same_vehicle_requirements_on_route_.clear();
}
- bool CheckTypeRegulations(int type) override;
+ // clang-format off
+ /// Verifies that for each set in required_type_alternatives, at least one of
+ /// the required types is on the route at position 'pos'.
+ bool CheckRequiredTypesCurrentlyOnRoute(
+ const std::vector >& required_type_alternatives,
+ int pos);
+ // clang-format on
+ bool CheckTypeRegulations(int type, VisitTypePolicy policy, int pos) override;
bool FinalizeCheck() const override;
absl::flat_hash_set types_with_same_vehicle_requirements_on_route_;
@@ -1993,18 +2056,39 @@ class TypeRequirementChecker : public TypeRegulationsChecker {
/// Two nodes with hard incompatible types cannot be served by the same vehicle
/// at all, while with a temporal incompatibility they can't be on the same
/// route at the same time.
+/// The VisitTypePolicy of a node determines how visiting it impacts the type
+/// count on the route.
///
-/// For example, for three temporally incompatible types T1 T2 and T3, two
-/// pickup/delivery pairs p1/d1 and p2/d2 of type T1 and T2 respectively, and a
-/// non-pickup/delivery node n of type T3, the configuration
-/// p1 --> d1 --> n --> p2 --> d2 is acceptable, whereas any configurations
-/// with p1 --> p2 --> d1 --> ..., or p1 --> n --> d1 --> ... is not feasible.
+/// For example, for
+/// - three temporally incompatible types T1 T2 and T3
+/// - 2 pairs of nodes a1/r1 and a2/r2 of type T1 and T2 respectively, with
+/// - a1 and a2 of VisitTypePolicy TYPE_ADDED_TO_VEHICLE
+/// - r1 and r2 of policy ADDED_TYPE_REMOVED_FROM_VEHICLE
+/// - 3 nodes A, UV and AR of type T3, respectively with type policies
+/// TYPE_ADDED_TO_VEHICLE, TYPE_ON_VEHICLE_UP_TO_VISIT and
+/// TYPE_SIMULTANEOUSLY_ADDED_AND_REMOVED
+/// the configurations
+/// UV --> a1 --> r1 --> a2 --> r2, a1 --> r1 --> a2 --> r2 --> A and
+/// a1 --> r1 --> AR --> a2 --> r2 are acceptable, whereas the configurations
+/// a1 --> a2 --> r1 --> ..., or A --> a1 --> r1 --> ..., or
+/// a1 --> r1 --> UV --> ... are not feasible.
///
/// It also verifies same-vehicle and temporal type requirements.
-/// In the above example, if T1 is a requirement for T2:
-/// - For a same-vehicle requirement, p1/d1 must be on the same vehicle as
-/// p2/d2.
-/// - For a temporal requirement, p2 must be visited between p1 and d1.
+/// A node of type T_d with a same-vehicle requirement for type T_r needs to be
+/// served by the same vehicle as a node of type T_r.
+/// Temporal requirements, on the other hand, can take effect either when the
+/// dependent type is being added to the route or when it's removed from it,
+/// which is determined by the dependent node's VisitTypePolicy.
+/// In the above example:
+/// - If T3 is required on the same vehicle as T1, A, AR or UV must be on the
+/// same vehicle as a1.
+/// - If T2 is required when adding T1, a2 must be visited *before* a1, and if
+/// r2 is also visited on the route, it must be *after* a1, i.e. T2 must be on
+/// the vehicle when a1 is visited:
+/// ... --> a2 --> ... --> a1 --> ... --> r2 --> ...
+/// - If T3 is required when removing T1, T3 needs to be on the vehicle when
+/// r1 is visited:
+/// ... --> A --> ... --> r1 --> ... OR ... --> r1 --> ... --> UV --> ...
class TypeRegulationsConstraint : public Constraint {
public:
explicit TypeRegulationsConstraint(const RoutingModel& model);
@@ -2957,6 +3041,10 @@ class GlobalCheapestInsertionFilteredHeuristic
AdjustablePriorityQueue* priority_queue,
std::vector* node_entries);
+ /// Computes the neighborhood of all nodes for every cost class, if needed and
+ /// not done already.
+ void ComputeNeighborhoods();
+
/// Marks neighbor_index as visited in
/// node_index_to_[pickup|delivery|single]_neighbors_by_cost_class_
/// [node_index][cost_class] according to whether the neighbor is a pickup,
diff --git a/ortools/constraint_solver/routing_breaks.cc b/ortools/constraint_solver/routing_breaks.cc
index 3e5c63c014..6ce24cb7bd 100644
--- a/ortools/constraint_solver/routing_breaks.cc
+++ b/ortools/constraint_solver/routing_breaks.cc
@@ -26,12 +26,26 @@ bool DisjunctivePropagator::Propagate(Tasks* tasks) {
DCHECK_EQ(tasks->start_min.size(), tasks->end_max.size());
DCHECK_EQ(tasks->start_min.size(), tasks->is_preemptible.size());
// Do forward deductions, then backward deductions.
- // Interleave Precedences() that is O(n).
- return Precedences(tasks) && EdgeFinding(tasks) && Precedences(tasks) &&
- DetectablePrecedencesWithChain(tasks) && ForbiddenIntervals(tasks) &&
- Precedences(tasks) && DistanceDuration(tasks) && Precedences(tasks) &&
- MirrorTasks(tasks) && EdgeFinding(tasks) && Precedences(tasks) &&
- DetectablePrecedencesWithChain(tasks) && MirrorTasks(tasks);
+ // All propagators are followed by Precedences(),
+ // except MirrorTasks() after which Precedences() would make no deductions,
+ // and DetectablePrecedencesWithChain() which is stronger than Precedences().
+ // Precedences() is a propagator that does obvious deductions quickly (O(n)),
+ // so interleaving Precedences() speeds up the propagation fixed point.
+ if (!Precedences(tasks) || !EdgeFinding(tasks) || !Precedences(tasks) ||
+ !DetectablePrecedencesWithChain(tasks)) {
+ return false;
+ }
+ if (!tasks->forbidden_intervals.empty()) {
+ if (!ForbiddenIntervals(tasks) || !Precedences(tasks)) return false;
+ }
+ if (!tasks->distance_duration.empty()) {
+ if (!DistanceDuration(tasks) || !Precedences(tasks)) return false;
+ }
+ if (!MirrorTasks(tasks) || !EdgeFinding(tasks) || !Precedences(tasks) ||
+ !DetectablePrecedencesWithChain(tasks) || !MirrorTasks(tasks)) {
+ return false;
+ }
+ return true;
}
bool DisjunctivePropagator::Precedences(Tasks* tasks) {
@@ -269,6 +283,218 @@ bool DisjunctivePropagator::ForbiddenIntervals(Tasks* tasks) {
return true;
}
+bool DisjunctivePropagator::DistanceDuration(Tasks* tasks) {
+ if (tasks->distance_duration.empty()) return true;
+ if (tasks->num_chain_tasks == 0) return true;
+ const int route_start = 0;
+ const int route_end = tasks->num_chain_tasks - 1;
+ const int num_tasks = tasks->start_min.size();
+ for (int i = 0; i < tasks->distance_duration.size(); ++i) {
+ const int64 max_distance = tasks->distance_duration[i].first;
+ const int64 minimum_break_duration = tasks->distance_duration[i].second;
+
+ // This is a sweeping algorithm that looks whether the union of intervals
+ // defined by breaks and route start/end is (-infty, +infty).
+ // Those intervals are:
+ // - route start: (-infty, start_max + distance]
+ // - route end: [end_min, +infty)
+ // - breaks: [start_min, end_max + distance) if their duration_max
+ // is >= min_duration, empty set otherwise.
+ // If sweeping finds that a time point can be covered by only one interval,
+ // it will force the corresponding break or route start/end to cover this
+ // point, which can force a break to be above minimum_break_duration.
+
+ // We suppose break tasks are ordered, so the algorithm supposes that
+ // start_min(task_n) <= start_min(task_{n+1}) and
+ // end_max(task_n) <= end_max(task_{n+1}).
+ for (int task = tasks->num_chain_tasks + 1; task < num_tasks; ++task) {
+ tasks->start_min[task] =
+ std::max(tasks->start_min[task], tasks->start_min[task - 1]);
+ }
+ for (int task = num_tasks - 2; task >= tasks->num_chain_tasks; --task) {
+ tasks->end_max[task] =
+ std::min(tasks->end_max[task], tasks->end_max[task + 1]);
+ }
+ // Skip breaks that cannot be performed after start.
+ int index_break_by_emax = tasks->num_chain_tasks;
+ while (index_break_by_emax < num_tasks &&
+ tasks->end_max[index_break_by_emax] <= tasks->end_max[route_start]) {
+ ++index_break_by_emax;
+ }
+ // Special case: no breaks after start.
+ if (index_break_by_emax == num_tasks) {
+ tasks->end_min[route_start] =
+ std::max(tasks->end_min[route_start],
+ CapSub(tasks->start_min[route_end], max_distance));
+ tasks->start_max[route_end] =
+ std::min(tasks->start_max[route_end],
+ CapAdd(tasks->end_max[route_start], max_distance));
+ continue;
+ }
+ // There will be a break after start, so route_start coverage is tested.
+ // Initial state: start at -inf with route_start in task_set.
+ // Sweep over profile, looking for time points where the number of
+ // covering breaks is <= 1. If it is 0, fail, otherwise force the
+ // unique break to cover it.
+ // Route start and end get a special treatment, not sure generalizing
+ // would be better.
+ int64 xor_active_tasks = route_start;
+ int num_active_tasks = 1;
+ int64 previous_time = kint64min;
+ const int64 route_start_time =
+ CapAdd(tasks->end_max[route_start], max_distance);
+ const int64 route_end_time = tasks->start_min[route_end];
+ int index_break_by_smin = tasks->num_chain_tasks;
+ while (index_break_by_emax < num_tasks) {
+ // Find next time point among start/end of covering intervals.
+ int64 current_time =
+ CapAdd(tasks->end_max[index_break_by_emax], max_distance);
+ if (index_break_by_smin < num_tasks) {
+ current_time =
+ std::min(current_time, tasks->start_min[index_break_by_smin]);
+ }
+ if (previous_time < route_start_time && route_start_time < current_time) {
+ current_time = route_start_time;
+ }
+ if (previous_time < route_end_time && route_end_time < current_time) {
+ current_time = route_end_time;
+ }
+ // If num_active_tasks was 1, the unique active task must cover from
+ // previous_time to current_time.
+ if (num_active_tasks == 1) {
+ // xor_active_tasks is the unique task that can cover [previous_time,
+ // current_time).
+ if (xor_active_tasks != route_end) {
+ tasks->end_min[xor_active_tasks] =
+ std::max(tasks->end_min[xor_active_tasks],
+ CapSub(current_time, max_distance));
+ if (xor_active_tasks != route_start) {
+ tasks->duration_min[xor_active_tasks] = std::max(
+ tasks->duration_min[xor_active_tasks],
+ std::max(
+ minimum_break_duration,
+ CapSub(CapSub(current_time, max_distance), previous_time)));
+ }
+ }
+ }
+ // Process covering intervals that start or end at current_time.
+ while (index_break_by_smin < num_tasks &&
+ current_time == tasks->start_min[index_break_by_smin]) {
+ if (tasks->duration_max[index_break_by_smin] >=
+ minimum_break_duration) {
+ xor_active_tasks ^= index_break_by_smin;
+ ++num_active_tasks;
+ }
+ ++index_break_by_smin;
+ }
+ while (index_break_by_emax < num_tasks &&
+ current_time ==
+ CapAdd(tasks->end_max[index_break_by_emax], max_distance)) {
+ if (tasks->duration_max[index_break_by_emax] >=
+ minimum_break_duration) {
+ xor_active_tasks ^= index_break_by_emax;
+ --num_active_tasks;
+ }
+ ++index_break_by_emax;
+ }
+ if (current_time == route_start_time) {
+ xor_active_tasks ^= route_start;
+ --num_active_tasks;
+ }
+ if (current_time == route_end_time) {
+ xor_active_tasks ^= route_end;
+ ++num_active_tasks;
+ }
+ // If num_active_tasks becomes 1, the unique active task must cover from
+ // current_time.
+ if (num_active_tasks <= 0) return false;
+ if (num_active_tasks == 1) {
+ if (xor_active_tasks != route_start) {
+ // xor_active_tasks is the unique task that can cover from
+ // current_time to the next time point.
+ tasks->start_max[xor_active_tasks] =
+ std::min(tasks->start_max[xor_active_tasks], current_time);
+ if (xor_active_tasks != route_end) {
+ tasks->duration_min[xor_active_tasks] = std::max(
+ tasks->duration_min[xor_active_tasks], minimum_break_duration);
+ }
+ }
+ }
+ previous_time = current_time;
+ }
+ }
+ return true;
+}
+
+void AppendTasksFromPath(const std::vector& path,
+ const std::vector& min_travels,
+ const std::vector& max_travels,
+ const std::vector& pre_travels,
+ const std::vector& post_travels,
+ const RoutingDimension& dimension,
+ DisjunctivePropagator::Tasks* tasks) {
+ const int num_nodes = path.size();
+ DCHECK_EQ(pre_travels.size(), num_nodes - 1);
+ DCHECK_EQ(post_travels.size(), num_nodes - 1);
+ for (int i = 0; i < num_nodes; ++i) {
+ const int64 cumul_min = dimension.CumulVar(path[i])->Min();
+ const int64 cumul_max = dimension.CumulVar(path[i])->Max();
+ // Add task associated to visit i.
+ // Visits start at Cumul(path[i]) - before_visit
+ // and end at Cumul(path[i]) + after_visit
+ {
+ const int64 before_visit = (i == 0) ? 0 : post_travels[i - 1];
+ const int64 after_visit = (i == num_nodes - 1) ? 0 : pre_travels[i];
+
+ tasks->start_min.push_back(CapSub(cumul_min, before_visit));
+ tasks->start_max.push_back(CapSub(cumul_max, before_visit));
+ tasks->duration_min.push_back(CapAdd(before_visit, after_visit));
+ tasks->duration_max.push_back(CapAdd(before_visit, after_visit));
+ tasks->end_min.push_back(CapAdd(cumul_min, after_visit));
+ tasks->end_max.push_back(CapAdd(cumul_max, after_visit));
+ tasks->is_preemptible.push_back(false);
+ }
+ if (i == num_nodes - 1) break;
+
+ // Tasks from travels.
+ // A travel task starts at Cumul(path[i]) + pre_travel,
+ // last for FixedTransitVar(path[i]) - pre_travel - post_travel,
+ // and must end at the latest at Cumul(path[i+1]) - post_travel.
+ {
+ const int64 pre_travel = pre_travels[i];
+ const int64 post_travel = post_travels[i];
+ tasks->start_min.push_back(CapAdd(cumul_min, pre_travel));
+ tasks->start_max.push_back(CapAdd(cumul_max, pre_travel));
+ tasks->duration_min.push_back(std::max(
+ 0, CapSub(min_travels[i], CapAdd(pre_travel, post_travel))));
+ tasks->duration_max.push_back(
+ max_travels[i] == kint64max
+ ? kint64max
+ : std::max(0, CapSub(max_travels[i],
+ CapAdd(pre_travel, post_travel))));
+ tasks->end_min.push_back(
+ CapSub(dimension.CumulVar(path[i + 1])->Min(), post_travel));
+ tasks->end_max.push_back(
+ CapSub(dimension.CumulVar(path[i + 1])->Max(), post_travel));
+ tasks->is_preemptible.push_back(true);
+ }
+ }
+}
+
+void AppendTasksFromIntervals(const std::vector& intervals,
+ DisjunctivePropagator::Tasks* tasks) {
+ for (IntervalVar* interval : intervals) {
+ if (!interval->MustBePerformed()) continue;
+ tasks->start_min.push_back(interval->StartMin());
+ tasks->start_max.push_back(interval->StartMax());
+ tasks->duration_min.push_back(interval->DurationMin());
+ tasks->duration_max.push_back(interval->DurationMax());
+ tasks->end_min.push_back(interval->EndMin());
+ tasks->end_max.push_back(interval->EndMax());
+ tasks->is_preemptible.push_back(false);
+ }
+}
+
GlobalVehicleBreaksConstraint::GlobalVehicleBreaksConstraint(
const RoutingDimension* dimension)
: Constraint(dimension->model()->solver()),
diff --git a/ortools/constraint_solver/routing_flags.cc b/ortools/constraint_solver/routing_flags.cc
index 3031e7eb68..a0e3ff03d3 100644
--- a/ortools/constraint_solver/routing_flags.cc
+++ b/ortools/constraint_solver/routing_flags.cc
@@ -286,6 +286,9 @@ void SetMiscellaneousParametersFromFlags(RoutingSearchParameters* parameters) {
parameters->set_relocate_expensive_chain_num_arcs_to_consider(
FLAGS_routing_relocate_expensive_chain_num_arcs_to_consider);
parameters->set_heuristic_expensive_chain_lns_num_arcs_to_consider(4);
+ parameters->set_continuous_scheduling_solver(RoutingSearchParameters::GLOP);
+ parameters->set_mixed_integer_scheduling_solver(
+ RoutingSearchParameters::CP_SAT);
}
RoutingSearchParameters BuildSearchParametersFromFlags() {
diff --git a/ortools/constraint_solver/routing_flow.cc b/ortools/constraint_solver/routing_flow.cc
index 60f79f6ea7..42bd707cb1 100644
--- a/ortools/constraint_solver/routing_flow.cc
+++ b/ortools/constraint_solver/routing_flow.cc
@@ -257,14 +257,17 @@ bool RoutingModel::SolveMatchingModel(
CapAdd(GetArcCostForVehicle(start, pickup, vehicle),
CapAdd(GetArcCostForVehicle(pickup, delivery, vehicle),
GetArcCostForVehicle(delivery, end, vehicle)));
- const std::unordered_map nexts = {
+ const absl::flat_hash_map nexts = {
{start, pickup}, {pickup, delivery}, {delivery, end}};
for (LocalDimensionCumulOptimizer& optimizer : optimizers) {
int64 cumul_cost_value = 0;
+ // TODO(user): if the result is RELAXED_OPTIMAL_ONLY, do a
+ // second pass with an MP solver.
if (optimizer.ComputeRouteCumulCostWithoutFixedTransits(
vehicle,
[&nexts](int64 node) { return nexts.find(node)->second; },
- &cumul_cost_value)) {
+ &cumul_cost_value) !=
+ DimensionSchedulingStatus::INFEASIBLE) {
cost = CapAdd(cost, cumul_cost_value);
} else {
add_arc = false;
@@ -277,14 +280,17 @@ bool RoutingModel::SolveMatchingModel(
add_arc = true;
cost = CapAdd(GetArcCostForVehicle(start, node, vehicle),
GetArcCostForVehicle(node, end, vehicle));
- const std::unordered_map nexts = {{start, node},
- {node, end}};
+ const absl::flat_hash_map nexts = {{start, node},
+ {node, end}};
for (LocalDimensionCumulOptimizer& optimizer : optimizers) {
int64 cumul_cost_value = 0;
+ // TODO(user): if the result is RELAXED_OPTIMAL_ONLY, do a
+ // second pass with an MP solver.
if (optimizer.ComputeRouteCumulCostWithoutFixedTransits(
vehicle,
[&nexts](int64 node) { return nexts.find(node)->second; },
- &cumul_cost_value)) {
+ &cumul_cost_value) !=
+ DimensionSchedulingStatus::INFEASIBLE) {
cost = CapAdd(cost, cumul_cost_value);
} else {
add_arc = false;
diff --git a/ortools/constraint_solver/routing_lp_scheduling.cc b/ortools/constraint_solver/routing_lp_scheduling.cc
index 709fc978ec..1c9c78951e 100644
--- a/ortools/constraint_solver/routing_lp_scheduling.cc
+++ b/ortools/constraint_solver/routing_lp_scheduling.cc
@@ -178,7 +178,7 @@ LocalDimensionCumulOptimizer::LocalDimensionCumulOptimizer(
}
}
-bool LocalDimensionCumulOptimizer::ComputeRouteCumulCost(
+DimensionSchedulingStatus LocalDimensionCumulOptimizer::ComputeRouteCumulCost(
int vehicle, const std::function& next_accessor,
int64* optimal_cost) {
return optimizer_core_.OptimizeSingleRoute(vehicle, next_accessor,
@@ -186,23 +186,23 @@ bool LocalDimensionCumulOptimizer::ComputeRouteCumulCost(
optimal_cost, nullptr);
}
-bool LocalDimensionCumulOptimizer::ComputeRouteCumulCostWithoutFixedTransits(
+DimensionSchedulingStatus
+LocalDimensionCumulOptimizer::ComputeRouteCumulCostWithoutFixedTransits(
int vehicle, const std::function& next_accessor,
int64* optimal_cost_without_transits) {
int64 cost = 0;
int64 transit_cost = 0;
- if (optimizer_core_.OptimizeSingleRoute(vehicle, next_accessor,
- solver_[vehicle].get(), nullptr,
- &cost, &transit_cost)) {
- if (optimal_cost_without_transits != nullptr) {
- *optimal_cost_without_transits = CapSub(cost, transit_cost);
- }
- return true;
+ const DimensionSchedulingStatus status = optimizer_core_.OptimizeSingleRoute(
+ vehicle, next_accessor, solver_[vehicle].get(), nullptr, &cost,
+ &transit_cost);
+ if (status != DimensionSchedulingStatus::INFEASIBLE &&
+ optimal_cost_without_transits != nullptr) {
+ *optimal_cost_without_transits = CapSub(cost, transit_cost);
}
- return false;
+ return status;
}
-bool LocalDimensionCumulOptimizer::ComputeRouteCumuls(
+DimensionSchedulingStatus LocalDimensionCumulOptimizer::ComputeRouteCumuls(
int vehicle, const std::function& next_accessor,
std::vector* optimal_cumuls) {
return optimizer_core_.OptimizeSingleRoute(vehicle, next_accessor,
@@ -210,7 +210,8 @@ bool LocalDimensionCumulOptimizer::ComputeRouteCumuls(
optimal_cumuls, nullptr, nullptr);
}
-bool LocalDimensionCumulOptimizer::ComputePackedRouteCumuls(
+DimensionSchedulingStatus
+LocalDimensionCumulOptimizer::ComputePackedRouteCumuls(
int vehicle, const std::function& next_accessor,
std::vector* packed_cumuls) {
return optimizer_core_.OptimizeAndPackSingleRoute(
@@ -433,7 +434,7 @@ bool CumulBoundsPropagator::PropagateCumulBounds(
return true;
}
-bool DimensionCumulOptimizerCore::OptimizeSingleRoute(
+DimensionSchedulingStatus DimensionCumulOptimizerCore::OptimizeSingleRoute(
int vehicle, const std::function& next_accessor,
RoutingLinearSolverWrapper* solver, std::vector* cumul_values,
int64* cost, int64* transit_cost, bool clear_lp) {
@@ -452,9 +453,13 @@ bool DimensionCumulOptimizerCore::OptimizeSingleRoute(
int64 cost_offset = 0;
if (!SetRouteCumulConstraints(vehicle, next_accessor, cumul_offset,
optimize_vehicle_costs, solver, transit_cost,
- &cost_offset) ||
- !solver->Solve(model->RemainingTime())) {
- return false;
+ &cost_offset)) {
+ return DimensionSchedulingStatus::INFEASIBLE;
+ }
+ const DimensionSchedulingStatus status =
+ solver->Solve(model->RemainingTime());
+ if (status == DimensionSchedulingStatus::INFEASIBLE) {
+ return status;
}
SetCumulValuesFromLP(current_route_cumul_variables_, cumul_offset, solver,
@@ -466,7 +471,7 @@ bool DimensionCumulOptimizerCore::OptimizeSingleRoute(
if (clear_lp) {
solver->Clear();
}
- return true;
+ return status;
}
bool DimensionCumulOptimizerCore::Optimize(
@@ -512,7 +517,8 @@ bool DimensionCumulOptimizerCore::Optimize(
SetGlobalConstraints(has_vehicles_being_optimized, solver);
- if (!solver->Solve(model->RemainingTime())) {
+ if (solver->Solve(model->RemainingTime()) ==
+ DimensionSchedulingStatus::INFEASIBLE) {
return false;
}
@@ -543,7 +549,8 @@ bool DimensionCumulOptimizerCore::OptimizeAndPack(
std::vector vehicles(dimension()->model()->vehicles());
std::iota(vehicles.begin(), vehicles.end(), 0);
- if (!PackRoutes(std::move(vehicles), solver)) {
+ if (PackRoutes(std::move(vehicles), solver) ==
+ DimensionSchedulingStatus::INFEASIBLE) {
return false;
}
@@ -554,27 +561,32 @@ bool DimensionCumulOptimizerCore::OptimizeAndPack(
return true;
}
-bool DimensionCumulOptimizerCore::OptimizeAndPackSingleRoute(
+DimensionSchedulingStatus
+DimensionCumulOptimizerCore::OptimizeAndPackSingleRoute(
int vehicle, const std::function& next_accessor,
RoutingLinearSolverWrapper* solver, std::vector* cumul_values) {
// Note: We pass a non-nullptr cost to the OptimizeSingleRoute() method so the
// costs are optimized by the LP.
int64 cost = 0;
- if (!OptimizeSingleRoute(vehicle, next_accessor, solver,
- /*cumul_values=*/nullptr, &cost,
- /*transit_cost=*/nullptr,
- /*clear_lp=*/false) ||
- !PackRoutes({vehicle}, solver)) {
- return false;
+ if (OptimizeSingleRoute(vehicle, next_accessor, solver,
+ /*cumul_values=*/nullptr, &cost,
+ /*transit_cost=*/nullptr,
+ /*clear_lp=*/false) ==
+ DimensionSchedulingStatus::INFEASIBLE) {
+ return DimensionSchedulingStatus::INFEASIBLE;
+ }
+ const DimensionSchedulingStatus status = PackRoutes({vehicle}, solver);
+ if (status == DimensionSchedulingStatus::INFEASIBLE) {
+ return DimensionSchedulingStatus::INFEASIBLE;
}
SetCumulValuesFromLP(current_route_cumul_variables_,
dimension_->GetLocalOptimizerOffsetForVehicle(vehicle),
solver, cumul_values);
solver->Clear();
- return true;
+ return status;
}
-bool DimensionCumulOptimizerCore::PackRoutes(
+DimensionSchedulingStatus DimensionCumulOptimizerCore::PackRoutes(
std::vector vehicles, RoutingLinearSolverWrapper* solver) {
const RoutingModel* model = dimension_->model();
@@ -594,8 +606,9 @@ bool DimensionCumulOptimizerCore::PackRoutes(
index_to_cumul_variable_[model->End(vehicle)], 1);
}
- if (!solver->Solve(model->RemainingTime())) {
- return false;
+ if (solver->Solve(model->RemainingTime()) ==
+ DimensionSchedulingStatus::INFEASIBLE) {
+ return DimensionSchedulingStatus::INFEASIBLE;
}
// Maximize the route start times without increasing the cost or the route end
@@ -612,11 +625,7 @@ bool DimensionCumulOptimizerCore::PackRoutes(
solver->SetObjectiveCoefficient(
index_to_cumul_variable_[model->Start(vehicle)], -1);
}
- if (!solver->Solve(model->RemainingTime())) {
- return false;
- }
-
- return true;
+ return solver->Solve(model->RemainingTime());
}
void DimensionCumulOptimizerCore::InitOptimizer(
diff --git a/ortools/constraint_solver/routing_lp_scheduling.h b/ortools/constraint_solver/routing_lp_scheduling.h
index e288f37c84..252c592267 100644
--- a/ortools/constraint_solver/routing_lp_scheduling.h
+++ b/ortools/constraint_solver/routing_lp_scheduling.h
@@ -14,6 +14,7 @@
#ifndef OR_TOOLS_CONSTRAINT_SOLVER_ROUTING_LP_SCHEDULING_H_
#define OR_TOOLS_CONSTRAINT_SOLVER_ROUTING_LP_SCHEDULING_H_
+#include "absl/container/flat_hash_map.h"
#include "absl/memory/memory.h"
#include "ortools/constraint_solver/routing.h"
#include "ortools/glop/lp_solver.h"
@@ -120,6 +121,16 @@ class CumulBoundsPropagator {
visited_pickup_delivery_indices_for_pair_;
};
+enum class DimensionSchedulingStatus {
+ // An optimal solution was found respecting all constraints.
+ OPTIMAL,
+ // An optimal solution was found, however constraints which were relaxed were
+ // violated.
+ RELAXED_OPTIMAL_ONLY,
+ // A solution could not be found.
+ INFEASIBLE
+};
+
class RoutingLinearSolverWrapper {
public:
virtual ~RoutingLinearSolverWrapper() {}
@@ -137,7 +148,7 @@ class RoutingLinearSolverWrapper {
virtual int NumVariables() const = 0;
virtual int CreateNewConstraint(int64 lower_bound, int64 upper_bound) = 0;
virtual void SetCoefficient(int ct, int index, double coefficient) = 0;
- virtual bool Solve(absl::Duration duration_limit) = 0;
+ virtual DimensionSchedulingStatus Solve(absl::Duration duration_limit) = 0;
virtual double GetObjectiveValue() const = 0;
virtual double GetValue(int index) const = 0;
};
@@ -151,6 +162,7 @@ class RoutingGlopWrapper : public RoutingLinearSolverWrapper {
void Clear() override {
linear_program_.Clear();
linear_program_.SetMaximizationProblem(false);
+ allowed_intervals_.clear();
}
int CreateNewPositiveVariable() override {
return linear_program_.CreateNewVariable().value();
@@ -174,7 +186,14 @@ class RoutingGlopWrapper : public RoutingLinearSolverWrapper {
return false;
}
void SetVariableDisjointBounds(int index, const std::vector& starts,
- const std::vector& ends) override {}
+ const std::vector& ends) override {
+ // TODO(user): Investigate if we can avoid rebuilding the interval list
+ // each time (we could keep a reference to the forbidden interval list in
+ // RoutingDimension but we would need to store cumul offsets and use them
+ // when checking intervals).
+ allowed_intervals_[index] =
+ absl::MakeUnique(starts, ends);
+ }
int64 GetVariableLowerBound(int index) const override {
return linear_program_.variable_lower_bounds()[glop::ColIndex(index)];
}
@@ -203,7 +222,7 @@ class RoutingGlopWrapper : public RoutingLinearSolverWrapper {
linear_program_.SetCoefficient(glop::RowIndex(ct), glop::ColIndex(index),
coefficient);
}
- bool Solve(absl::Duration duration_limit) override {
+ DimensionSchedulingStatus Solve(absl::Duration duration_limit) override {
lp_solver_.GetMutableParameters()->set_max_time_in_seconds(
absl::ToDoubleSeconds(duration_limit));
@@ -218,9 +237,21 @@ class RoutingGlopWrapper : public RoutingLinearSolverWrapper {
if (status != glop::ProblemStatus::OPTIMAL &&
status != glop::ProblemStatus::IMPRECISE) {
linear_program_.Clear();
- return false;
+ return DimensionSchedulingStatus::INFEASIBLE;
}
- return true;
+ for (const auto& allowed_interval : allowed_intervals_) {
+ const double value_double = GetValue(allowed_interval.first);
+ const int64 value = (value_double >= kint64max)
+ ? kint64max
+ : static_cast(std::round(value_double));
+ const SortedDisjointIntervalList* const interval_list =
+ allowed_interval.second.get();
+ const auto it = interval_list->FirstIntervalGreaterOrEqual(value);
+ if (it == interval_list->end() || value < it->start) {
+ return DimensionSchedulingStatus::RELAXED_OPTIMAL_ONLY;
+ }
+ }
+ return DimensionSchedulingStatus::OPTIMAL;
}
double GetObjectiveValue() const override {
return lp_solver_.GetObjectiveValue();
@@ -232,6 +263,8 @@ class RoutingGlopWrapper : public RoutingLinearSolverWrapper {
private:
glop::LinearProgram linear_program_;
glop::LPSolver lp_solver_;
+ absl::flat_hash_map>
+ allowed_intervals_;
};
class RoutingCPSatWrapper : public RoutingLinearSolverWrapper {
@@ -339,7 +372,7 @@ class RoutingCPSatWrapper : public RoutingLinearSolverWrapper {
CapAdd(constraint_offset_[ct_index],
CapProd(variable_offset_[index], coefficient));
}
- bool Solve(absl::Duration duration_limit) override {
+ DimensionSchedulingStatus Solve(absl::Duration duration_limit) override {
// Set constraint offsets
for (int ct_index = first_constraint_to_offset_;
ct_index < constraint_offset_.size(); ++ct_index) {
@@ -366,9 +399,9 @@ class RoutingCPSatWrapper : public RoutingLinearSolverWrapper {
hint_.add_vars(i);
hint_.add_values(response_.solution(i));
}
- return true;
+ return DimensionSchedulingStatus::OPTIMAL;
}
- return false;
+ return DimensionSchedulingStatus::INFEASIBLE;
}
double GetObjectiveValue() const override {
return response_.objective_value() + objective_offset_;
@@ -407,11 +440,10 @@ class DimensionCumulOptimizerCore {
// and "cost" parameters are null, we don't optimize the cost and stop at the
// first feasible solution in the linear solver (since in this case only
// feasibility is of interest).
- bool OptimizeSingleRoute(int vehicle,
- const std::function& next_accessor,
- RoutingLinearSolverWrapper* solver,
- std::vector* cumul_values, int64* cost,
- int64* transit_cost, bool clear_lp = true);
+ DimensionSchedulingStatus OptimizeSingleRoute(
+ int vehicle, const std::function& next_accessor,
+ RoutingLinearSolverWrapper* solver, std::vector* cumul_values,
+ int64* cost, int64* transit_cost, bool clear_lp = true);
bool Optimize(const std::function& next_accessor,
RoutingLinearSolverWrapper* solver,
@@ -422,7 +454,7 @@ class DimensionCumulOptimizerCore {
RoutingLinearSolverWrapper* solver,
std::vector* cumul_values);
- bool OptimizeAndPackSingleRoute(
+ DimensionSchedulingStatus OptimizeAndPackSingleRoute(
int vehicle, const std::function& next_accessor,
RoutingLinearSolverWrapper* solver, std::vector* cumul_values);
@@ -472,8 +504,8 @@ class DimensionCumulOptimizerCore {
// setting the coefficient of the route ends to 1, to first minimize the route
// ends' cumuls, and then maximizes the starts' cumuls without increasing the
// ends.
- bool PackRoutes(std::vector vehicles,
- RoutingLinearSolverWrapper* solver);
+ DimensionSchedulingStatus PackRoutes(std::vector vehicles,
+ RoutingLinearSolverWrapper* solver);
std::unique_ptr propagator_;
std::vector current_route_min_cumuls_;
@@ -502,13 +534,13 @@ class LocalDimensionCumulOptimizer {
// minimizing cumul soft lower and upper bound costs and vehicle span costs,
// and stores it in "optimal_cost" (if not null).
// Returns true iff the route respects all constraints.
- bool ComputeRouteCumulCost(int vehicle,
- const std::function& next_accessor,
- int64* optimal_cost);
+ DimensionSchedulingStatus ComputeRouteCumulCost(
+ int vehicle, const std::function& next_accessor,
+ int64* optimal_cost);
// Same as ComputeRouteCumulCost, but the cost computed does not contain
// the part of the vehicle span cost due to fixed transits.
- bool ComputeRouteCumulCostWithoutFixedTransits(
+ DimensionSchedulingStatus ComputeRouteCumulCostWithoutFixedTransits(
int vehicle, const std::function& next_accessor,
int64* optimal_cost_without_transits);
@@ -516,14 +548,14 @@ class LocalDimensionCumulOptimizer {
// vehicle, minimizing cumul soft lower and upper bound costs and vehicle span
// costs, stores them in "optimal_cumuls" (if not null), and returns true.
// Returns false if the route is not feasible.
- bool ComputeRouteCumuls(int vehicle,
- const std::function& next_accessor,
- std::vector* optimal_cumuls);
+ DimensionSchedulingStatus ComputeRouteCumuls(
+ int vehicle, const std::function& next_accessor,
+ std::vector* optimal_cumuls);
// Similar to ComputeRouteCumuls, but also tries to pack the cumul values on
// the route, such that the cost remains the same, the cumul of route end is
// minimized, and then the cumul of the start of the route is maximized.
- bool ComputePackedRouteCumuls(
+ DimensionSchedulingStatus ComputePackedRouteCumuls(
int vehicle, const std::function& next_accessor,
std::vector* packed_cumuls);
diff --git a/ortools/constraint_solver/routing_neighborhoods.cc b/ortools/constraint_solver/routing_neighborhoods.cc
index c207c72b6e..4b4296d7a2 100644
--- a/ortools/constraint_solver/routing_neighborhoods.cc
+++ b/ortools/constraint_solver/routing_neighborhoods.cc
@@ -938,10 +938,13 @@ bool FindMostExpensiveArcsOnRoute(
return false;
}
- // TODO(user): Investigate the impact of using a limited size priority
- // queue instead of vectors on performance.
- std::vector most_expensive_arc_costs(num_arcs, -1);
- most_expensive_arc_starts_and_ranks->assign(num_arcs, {-1, -1});
+ // NOTE: The negative ranks are so that for a given cost, lower ranks are
+ // given higher priority.
+ using ArcCostNegativeRankStart = std::tuple;
+ std::priority_queue,
+ std::greater>
+ arc_info_pq;
int64 before_node = start;
int rank = 0;
@@ -949,33 +952,29 @@ bool FindMostExpensiveArcsOnRoute(
const int64 after_node = next_accessor(before_node);
const int64 arc_cost =
arc_cost_for_route_start(before_node, after_node, start);
- if (most_expensive_arc_costs.back() < arc_cost) {
- // Insert this arc in most_expensive_* vectors.
- most_expensive_arc_costs.back() = arc_cost;
- most_expensive_arc_starts_and_ranks->back().first = before_node;
- most_expensive_arc_starts_and_ranks->back().second = rank;
- // Move the newly added element in the vectors to keep
- // most_expensive_arc_costs sorted decreasingly.
- int index_before_added_arc = num_arcs - 2;
- while (index_before_added_arc >= 0 &&
- most_expensive_arc_costs[index_before_added_arc] < arc_cost) {
- std::swap(most_expensive_arc_costs[index_before_added_arc + 1],
- most_expensive_arc_costs[index_before_added_arc]);
- std::swap(
- (*most_expensive_arc_starts_and_ranks)[index_before_added_arc + 1],
- (*most_expensive_arc_starts_and_ranks)[index_before_added_arc]);
- index_before_added_arc--;
- }
- }
+ arc_info_pq.emplace(arc_cost, -rank, before_node);
+
before_node = after_node;
rank++;
+
+ if (rank > num_arcs) {
+ arc_info_pq.pop();
+ }
}
- // If there are less than 'num_arcs' arcs on the path, resize the vector of
- // arc starts.
+
DCHECK_GE(rank, 2);
- if (rank < num_arcs) {
- most_expensive_arc_starts_and_ranks->resize(rank);
+ DCHECK_EQ(arc_info_pq.size(), std::min(rank, num_arcs));
+
+ most_expensive_arc_starts_and_ranks->resize(arc_info_pq.size());
+ int arc_index = arc_info_pq.size() - 1;
+ while (!arc_info_pq.empty()) {
+ const ArcCostNegativeRankStart& arc_info = arc_info_pq.top();
+ (*most_expensive_arc_starts_and_ranks)[arc_index] = {
+ std::get<2>(arc_info), -std::get<1>(arc_info)};
+ arc_index--;
+ arc_info_pq.pop();
}
+
*first_expensive_arc_indices = {0, 1};
return true;
}
diff --git a/ortools/constraint_solver/routing_parameters.cc b/ortools/constraint_solver/routing_parameters.cc
index d4a82b9b8a..48044c1e15 100644
--- a/ortools/constraint_solver/routing_parameters.cc
+++ b/ortools/constraint_solver/routing_parameters.cc
@@ -268,6 +268,22 @@ std::string FindErrorInRoutingSearchParameters(
if (std::isnan(offset) || std::isinf(offset)) {
return StrCat("Invalid value for log_cost_offset: ", offset);
}
+ const RoutingSearchParameters::SchedulingSolver continuous_scheduling_solver =
+ search_parameters.continuous_scheduling_solver();
+ if (continuous_scheduling_solver == RoutingSearchParameters::UNSET ||
+ continuous_scheduling_solver == RoutingSearchParameters::CP_SAT) {
+ return StrCat("Invalid value for continuous_scheduling_solver: ",
+ RoutingSearchParameters::SchedulingSolver_Name(
+ continuous_scheduling_solver));
+ }
+ const RoutingSearchParameters::SchedulingSolver
+ mixed_integer_scheduling_solver =
+ search_parameters.mixed_integer_scheduling_solver();
+ if (mixed_integer_scheduling_solver == RoutingSearchParameters::UNSET) {
+ return StrCat("Invalid value for mixed_integer_scheduling_solver: ",
+ RoutingSearchParameters::SchedulingSolver_Name(
+ mixed_integer_scheduling_solver));
+ }
return ""; // = Valid (No error).
}
diff --git a/ortools/constraint_solver/routing_sat.cc b/ortools/constraint_solver/routing_sat.cc
index 065f11f957..d83bd09edd 100644
--- a/ortools/constraint_solver/routing_sat.cc
+++ b/ortools/constraint_solver/routing_sat.cc
@@ -35,6 +35,9 @@ int AddVariable(CpModelProto* cp_model, int64 lb, int64 ub) {
return index;
}
+// Returns the unique depot node used in the CP-SAT models (as of 01/2020).
+int64 GetDepotFromModel(const RoutingModel& model) { return model.Start(0); }
+
// Structure to keep track of arcs created.
struct Arc {
int tail;
@@ -102,6 +105,114 @@ void AddDimensions(const RoutingModel& model, const ArcVarMap& arc_vars,
}
}
+std::vector CreateRanks(const RoutingModel& model,
+ const ArcVarMap& arc_vars,
+ CpModelProto* cp_model) {
+ const int depot = GetDepotFromModel(model);
+ const int size = model.Size() + model.vehicles();
+ const int rank_size = model.Size() - model.vehicles();
+ std::vector ranks(size, -1);
+ for (int i = 0; i < size; ++i) {
+ if (model.IsStart(i) || model.IsEnd(i)) continue;
+ ranks[i] = AddVariable(cp_model, 0, rank_size);
+ }
+ ranks[depot] = AddVariable(cp_model, 0, 0);
+ for (const auto arc_var : arc_vars) {
+ const int tail = arc_var.first.tail;
+ const int head = arc_var.first.head;
+ if (tail == head || head == depot) continue;
+ // arc[tail][head] -> ranks[head] == ranks[tail] + 1.
+ ConstraintProto* ct = cp_model->add_constraints();
+ ct->add_enforcement_literal(arc_var.second);
+ LinearConstraintProto* arg = ct->mutable_linear();
+ arg->add_domain(1);
+ arg->add_domain(1);
+ arg->add_vars(ranks[tail]);
+ arg->add_coeffs(-1);
+ arg->add_vars(ranks[head]);
+ arg->add_coeffs(1);
+ }
+ return ranks;
+}
+
+// Vehicle variables do not actually represent the index of the vehicle
+// performing a node, but we ensure that the values of two vehicle variables
+// are the same if and only if the corresponding nodes are served by the same
+// vehicle.
+std::vector CreateVehicleVars(const RoutingModel& model,
+ const ArcVarMap& arc_vars,
+ CpModelProto* cp_model) {
+ const int depot = GetDepotFromModel(model);
+ const int size = model.Size() + model.vehicles();
+ std::vector vehicles(size, -1);
+ for (int i = 0; i < size; ++i) {
+ if (model.IsStart(i) || model.IsEnd(i)) continue;
+ vehicles[i] = AddVariable(cp_model, 0, size - 1);
+ }
+ for (const auto arc_var : arc_vars) {
+ const int tail = arc_var.first.tail;
+ const int head = arc_var.first.head;
+ if (tail == head || head == depot) continue;
+ if (tail == depot) {
+ // arc[depot][head] -> vehicles[head] == head.
+ ConstraintProto* ct = cp_model->add_constraints();
+ ct->add_enforcement_literal(arc_var.second);
+ LinearConstraintProto* arg = ct->mutable_linear();
+ arg->add_domain(head);
+ arg->add_domain(head);
+ arg->add_vars(vehicles[head]);
+ arg->add_coeffs(1);
+ continue;
+ }
+ // arc[tail][head] -> vehicles[head] == vehicles[tail].
+ ConstraintProto* ct = cp_model->add_constraints();
+ ct->add_enforcement_literal(arc_var.second);
+ LinearConstraintProto* arg = ct->mutable_linear();
+ arg->add_domain(0);
+ arg->add_domain(0);
+ arg->add_vars(vehicles[tail]);
+ arg->add_coeffs(-1);
+ arg->add_vars(vehicles[head]);
+ arg->add_coeffs(1);
+ }
+ return vehicles;
+}
+
+void AddPickupDeliveryConstraints(const RoutingModel& model,
+ const ArcVarMap& arc_vars,
+ CpModelProto* cp_model) {
+ if (model.GetPickupAndDeliveryPairs().empty()) return;
+ const std::vector ranks = CreateRanks(model, arc_vars, cp_model);
+ const std::vector vehicles =
+ CreateVehicleVars(model, arc_vars, cp_model);
+ for (const auto& pairs : model.GetPickupAndDeliveryPairs()) {
+ const int64 pickup = pairs.first[0];
+ const int64 delivery = pairs.second[0];
+ {
+ // ranks[pickup] + 1 <= ranks[delivery].
+ ConstraintProto* ct = cp_model->add_constraints();
+ LinearConstraintProto* arg = ct->mutable_linear();
+ arg->add_domain(1);
+ arg->add_domain(kint64max);
+ arg->add_vars(ranks[delivery]);
+ arg->add_coeffs(1);
+ arg->add_vars(ranks[pickup]);
+ arg->add_coeffs(-1);
+ }
+ {
+ // vehicles[pickup] == vehicles[delivery]
+ ConstraintProto* ct = cp_model->add_constraints();
+ LinearConstraintProto* arg = ct->mutable_linear();
+ arg->add_domain(0);
+ arg->add_domain(0);
+ arg->add_vars(vehicles[delivery]);
+ arg->add_coeffs(1);
+ arg->add_vars(vehicles[pickup]);
+ arg->add_coeffs(-1);
+ }
+ }
+}
+
// Converts a RoutingModel to CpModelProto for models with multiple vehicles.
// All non-start/end nodes have the same index in both models. Start/end nodes
// map to a single depot index; its value is arbitrarly the index of the start
@@ -112,7 +223,7 @@ ArcVarMap PopulateMultiRouteModelFromRoutingModel(const RoutingModel& model,
CpModelProto* cp_model) {
ArcVarMap arc_vars;
const int num_nodes = model.Nexts().size();
- const int depot = model.Start(0);
+ const int depot = GetDepotFromModel(model);
// Create "arc" variables and set their cost.
for (int tail = 0; tail < num_nodes; ++tail) {
@@ -203,6 +314,8 @@ ArcVarMap PopulateMultiRouteModelFromRoutingModel(const RoutingModel& model,
}
}
+ AddPickupDeliveryConstraints(model, arc_vars, cp_model);
+
AddDimensions(model, arc_vars, cp_model);
// Create Routes constraint, ensuring circuits from and to the depot.
@@ -277,6 +390,7 @@ ArcVarMap PopulateSingleRouteModelFromRoutingModel(const RoutingModel& model,
gtl::InsertOrDie(&arc_vars, {tail, head}, index);
}
}
+ AddPickupDeliveryConstraints(model, arc_vars, cp_model);
AddDimensions(model, arc_vars, cp_model);
return arc_vars;
}
@@ -299,7 +413,7 @@ bool ConvertToSolution(const CpSolverResponse& response,
if (response.status() != CpSolverStatus::OPTIMAL &&
response.status() != CpSolverStatus::FEASIBLE)
return false;
- const int depot = model.Start(0);
+ const int depot = GetDepotFromModel(model);
int vehicle = 0;
for (const auto& arc_var : arc_vars) {
if (response.solution(arc_var.second) != 0) {
@@ -332,7 +446,7 @@ void AddSolutionAsHintToModel(const Assignment* solution,
if (solution == nullptr) return;
PartialVariableAssignment* const hint = cp_model->mutable_solution_hint();
hint->Clear();
- const int depot = model.Start(0);
+ const int depot = GetDepotFromModel(model);
const int num_nodes = model.Nexts().size();
for (int tail = 0; tail < num_nodes; ++tail) {
const int tail_index = model.IsStart(tail) ? depot : tail;
diff --git a/ortools/constraint_solver/routing_search.cc b/ortools/constraint_solver/routing_search.cc
index 734d27eb89..1bbb382e33 100644
--- a/ortools/constraint_solver/routing_search.cc
+++ b/ortools/constraint_solver/routing_search.cc
@@ -736,7 +736,8 @@ void TypeRegulationsFilter::OnSynchronizePathFromStart(int64 start) {
while (node < Size()) {
DCHECK(IsVarSynced(node));
const int type = routing_model_.GetVisitType(node);
- if (type >= 0) {
+ if (type >= 0 && routing_model_.GetVisitTypePolicy(node) !=
+ RoutingModel::ADDED_TYPE_REMOVED_FROM_VEHICLE) {
CHECK_LT(type, num_types);
type_counts[type]++;
}
@@ -759,7 +760,8 @@ bool TypeRegulationsFilter::HardIncompatibilitiesRespected(int vehicle,
int64 node = GetNext(chain_start);
while (node != chain_end) {
const int type = routing_model_.GetVisitType(node);
- if (type >= 0) {
+ if (type >= 0 && routing_model_.GetVisitTypePolicy(node) !=
+ RoutingModel::ADDED_TYPE_REMOVED_FROM_VEHICLE) {
DCHECK_LT(type, previous_type_counts.size());
int& type_count = gtl::LookupOrInsert(&new_type_counts, type,
previous_type_counts[type]);
@@ -776,7 +778,8 @@ bool TypeRegulationsFilter::HardIncompatibilitiesRespected(int vehicle,
node = Value(chain_start);
while (node != chain_end) {
const int type = routing_model_.GetVisitType(node);
- if (type >= 0) {
+ if (type >= 0 && routing_model_.GetVisitTypePolicy(node) !=
+ RoutingModel::ADDED_TYPE_REMOVED_FROM_VEHICLE) {
DCHECK_LT(type, previous_type_counts.size());
int& type_count = gtl::LookupOrInsert(&new_type_counts, type,
previous_type_counts[type]);
@@ -1074,6 +1077,18 @@ class PathCumulFilter : public BasePathFilter {
return num_linear_constraints >= 2;
}
+ bool FilterDimensionForbiddenIntervals() const {
+ for (const SortedDisjointIntervalList& intervals :
+ dimension_.forbidden_intervals()) {
+ // TODO(user): Change the following test to check intervals within
+ // the domain of the corresponding variables.
+ if (intervals.NumIntervals() > 0) {
+ return true;
+ }
+ }
+ return false;
+ }
+
int64 GetCumulPiecewiseLinearCost(int64 node, int64 cumul_value) const;
bool FilterCumulSoftLowerBounds() const {
@@ -1182,6 +1197,8 @@ class PathCumulFilter : public BasePathFilter {
LocalDimensionCumulOptimizer* optimizer_;
std::unique_ptr internal_optimizer_;
+ LocalDimensionCumulOptimizer* mp_optimizer_;
+ std::unique_ptr internal_mp_optimizer_;
const bool filter_objective_cost_;
const bool propagate_own_objective_value_;
@@ -1216,6 +1233,7 @@ PathCumulFilter::PathCumulFilter(const RoutingModel& routing_model,
delta_nodes_with_precedences_and_changed_cumul_(routing_model.Size()),
name_(dimension.name()),
optimizer_(routing_model.GetMutableLocalCumulOptimizer(dimension)),
+ mp_optimizer_(routing_model.GetMutableLocalCumulMPOptimizer(dimension)),
filter_objective_cost_(filter_objective_cost),
propagate_own_objective_value_(propagate_own_objective_value),
lns_detected_(false) {
@@ -1312,6 +1330,12 @@ PathCumulFilter::PathCumulFilter(const RoutingModel& routing_model,
internal_optimizer_ = absl::make_unique(
&dimension, parameters.continuous_scheduling_solver());
optimizer_ = internal_optimizer_.get();
+ if (FilterDimensionForbiddenIntervals()) {
+ internal_mp_optimizer_ =
+ absl::make_unique(
+ &dimension, parameters.mixed_integer_scheduling_solver());
+ mp_optimizer_ = internal_mp_optimizer_.get();
+ }
break;
}
}
@@ -1461,10 +1485,24 @@ void PathCumulFilter::OnBeforeSynchronizePaths() {
// TODO(user): Return a status from the optimizer to detect failures
// The only admissible failures here are because of LP timeout.
int64 lp_cumul_cost_value = 0;
- if (!optimizer_->ComputeRouteCumulCostWithoutFixedTransits(
+ const DimensionSchedulingStatus status =
+ optimizer_->ComputeRouteCumulCostWithoutFixedTransits(
vehicle, [this](int64 node) { return Value(node); },
- &lp_cumul_cost_value)) {
- lp_cumul_cost_value = 0;
+ &lp_cumul_cost_value);
+ switch (status) {
+ case DimensionSchedulingStatus::INFEASIBLE:
+ lp_cumul_cost_value = 0;
+ break;
+ case DimensionSchedulingStatus::RELAXED_OPTIMAL_ONLY:
+ if (mp_optimizer_->ComputeRouteCumulCostWithoutFixedTransits(
+ vehicle, [this](int64 node) { return Value(node); },
+ &lp_cumul_cost_value) ==
+ DimensionSchedulingStatus::INFEASIBLE) {
+ lp_cumul_cost_value = 0;
+ }
+ break;
+ default:
+ DCHECK(status == DimensionSchedulingStatus::OPTIMAL);
}
current_cumul_cost_value =
std::max(current_cumul_cost_value, lp_cumul_cost_value);
@@ -1751,31 +1789,71 @@ bool PathCumulFilter::FinalizeAcceptPath(const Assignment* delta,
}
}
- // Filtering on objective value, calling LPs if needed..
+ // Filtering on objective value, calling LPs and MIPs if needed..
accepted_objective_value_ =
CapAdd(cumul_cost_delta_, CapProd(global_span_cost_coefficient_,
CapSub(new_max_end, new_min_start)));
if (filter_objective_cost_ && accepted_objective_value_ <= objective_max) {
- for (int64 start : GetTouchedPathStarts()) {
+ const size_t num_touched_paths = GetTouchedPathStarts().size();
+ std::vector path_delta_cost_values(num_touched_paths, 0);
+ std::vector requires_mp(num_touched_paths, false);
+ for (int i = 0; i < num_touched_paths; ++i) {
+ const int64 start = GetTouchedPathStarts()[i];
const int vehicle = start_to_vehicle_[start];
if (!FilterWithDimensionCumulOptimizerForVehicle(vehicle)) {
continue;
}
int64 path_delta_cost_with_lp = 0;
- if (!optimizer_->ComputeRouteCumulCostWithoutFixedTransits(
+ const DimensionSchedulingStatus status =
+ optimizer_->ComputeRouteCumulCostWithoutFixedTransits(
vehicle, [this](int64 node) { return GetNext(node); },
- &path_delta_cost_with_lp)) {
+ &path_delta_cost_with_lp);
+ if (status == DimensionSchedulingStatus::INFEASIBLE) {
return false;
}
DCHECK(gtl::ContainsKey(delta_paths_, GetPath(start)));
const int64 path_cost_diff_with_lp = CapSub(
path_delta_cost_with_lp, delta_path_cumul_cost_values_[vehicle]);
if (path_cost_diff_with_lp > 0) {
+ path_delta_cost_values[i] = path_delta_cost_with_lp;
accepted_objective_value_ =
CapAdd(accepted_objective_value_, path_cost_diff_with_lp);
if (accepted_objective_value_ > objective_max) {
return false;
}
+ } else {
+ path_delta_cost_values[i] = delta_path_cumul_cost_values_[vehicle];
+ }
+ if (mp_optimizer_ != nullptr) {
+ requires_mp[i] =
+ (status == DimensionSchedulingStatus::RELAXED_OPTIMAL_ONLY);
+ }
+ }
+ if (mp_optimizer_ == nullptr) {
+ return accepted_objective_value_ <= objective_max;
+ }
+ for (int i = 0; i < num_touched_paths; ++i) {
+ if (!requires_mp[i]) {
+ continue;
+ }
+ const int64 start = GetTouchedPathStarts()[i];
+ const int vehicle = start_to_vehicle_[start];
+ int64 path_delta_cost_with_mp = 0;
+ if (mp_optimizer_->ComputeRouteCumulCostWithoutFixedTransits(
+ vehicle, [this](int64 node) { return GetNext(node); },
+ &path_delta_cost_with_mp) ==
+ DimensionSchedulingStatus::INFEASIBLE) {
+ return false;
+ }
+ DCHECK(gtl::ContainsKey(delta_paths_, GetPath(start)));
+ const int64 path_cost_diff_with_mp =
+ CapSub(path_delta_cost_with_mp, path_delta_cost_values[i]);
+ if (path_cost_diff_with_mp > 0) {
+ accepted_objective_value_ =
+ CapAdd(accepted_objective_value_, path_cost_diff_with_mp);
+ if (accepted_objective_value_ > objective_max) {
+ return false;
+ }
}
}
}
@@ -2998,14 +3076,33 @@ GlobalCheapestInsertionFilteredHeuristic::
single_nodes_.push_back(node);
}
}
+ }
+}
+
+void GlobalCheapestInsertionFilteredHeuristic::ComputeNeighborhoods() {
+ if (gci_params_.neighbors_ratio == 1) {
+ // Neighborhood computations not needed.
+ return;
+ }
+ if (!node_index_to_single_neighbors_by_cost_class_.empty()) {
+ // Neigborhoods already computed.
+ DCHECK(!node_index_to_pickup_neighbors_by_cost_class_.empty());
+ DCHECK(!node_index_to_delivery_neighbors_by_cost_class_.empty());
return;
}
// TODO(user): Refactor the neighborhood computations in RoutingModel.
+ const RoutingModel& routing_model = *model();
+ const int64 size = routing_model.Size();
+ const int64 num_neighbors = std::max(1.0, gci_params_.neighbors_ratio * size);
+ // If num_neighbors was greater or equal size - 1, gci_params_.neighbors_ratio
+ // should have been set to 1.
+ DCHECK_LT(num_neighbors, size - 1);
+
node_index_to_single_neighbors_by_cost_class_.resize(size);
node_index_to_pickup_neighbors_by_cost_class_.resize(size);
node_index_to_delivery_neighbors_by_cost_class_.resize(size);
- const int num_cost_classes = model->GetCostClassesCount();
+ const int num_cost_classes = routing_model.GetCostClassesCount();
for (int64 node_index = 0; node_index < size; node_index++) {
node_index_to_single_neighbors_by_cost_class_[node_index].resize(
num_cost_classes);
@@ -3024,14 +3121,15 @@ GlobalCheapestInsertionFilteredHeuristic::
}
for (int64 node_index = 0; node_index < size; ++node_index) {
- DCHECK(!model->IsEnd(node_index));
- const bool node_is_pickup = !model->GetPickupIndexPairs(node_index).empty();
+ DCHECK(!routing_model.IsEnd(node_index));
+ const bool node_is_pickup =
+ !routing_model.GetPickupIndexPairs(node_index).empty();
const bool node_is_delivery =
- !model->GetDeliveryIndexPairs(node_index).empty();
+ !routing_model.GetDeliveryIndexPairs(node_index).empty();
// TODO(user): Use the model's IndexNeighborFinder when available.
for (int cost_class = 0; cost_class < num_cost_classes; cost_class++) {
- if (!model->HasVehicleWithCostClassIndex(
+ if (!routing_model.HasVehicleWithCostClassIndex(
RoutingCostClassIndex(cost_class))) {
// No vehicle with this cost class, avoid unnecessary computations.
continue;
@@ -3040,9 +3138,10 @@ GlobalCheapestInsertionFilteredHeuristic::
costed_after_nodes.reserve(size);
for (int after_node = 0; after_node < size; ++after_node) {
if (after_node != node_index) {
- costed_after_nodes.push_back(std::make_pair(
- model->GetArcCostForClass(node_index, after_node, cost_class),
- after_node));
+ costed_after_nodes.push_back(
+ std::make_pair(routing_model.GetArcCostForClass(
+ node_index, after_node, cost_class),
+ after_node));
}
}
std::nth_element(costed_after_nodes.begin(),
@@ -3054,11 +3153,11 @@ GlobalCheapestInsertionFilteredHeuristic::
const int64 neighbor = costed_neighbor.second;
AddNeighborForCostClass(
cost_class, node_index, neighbor,
- !model->GetPickupIndexPairs(neighbor).empty(),
- !model->GetDeliveryIndexPairs(neighbor).empty());
+ !routing_model.GetPickupIndexPairs(neighbor).empty(),
+ !routing_model.GetDeliveryIndexPairs(neighbor).empty());
// Add reverse neighborhood.
- DCHECK(!model->IsEnd(neighbor));
+ DCHECK(!routing_model.IsEnd(neighbor));
AddNeighborForCostClass(cost_class, neighbor, node_index,
node_is_pickup, node_is_delivery);
}
@@ -3127,6 +3226,7 @@ bool GlobalCheapestInsertionFilteredHeuristic::CheckVehicleIndices() const {
}
bool GlobalCheapestInsertionFilteredHeuristic::BuildSolutionInternal() {
+ ComputeNeighborhoods();
// Insert partially inserted pairs.
absl::flat_hash_map> vehicle_to_pair_nodes;
for (const RoutingModel::IndexPair& index_pair :
diff --git a/ortools/constraint_solver/samples/SimpleCpProgram.csproj b/ortools/constraint_solver/samples/SimpleCpProgram.csproj
index 8b92ebed2e..88f93d3cba 100644
--- a/ortools/constraint_solver/samples/SimpleCpProgram.csproj
+++ b/ortools/constraint_solver/samples/SimpleCpProgram.csproj
@@ -19,6 +19,6 @@
-
+
diff --git a/ortools/constraint_solver/samples/SimpleRoutingProgram.csproj b/ortools/constraint_solver/samples/SimpleRoutingProgram.csproj
index f6dd1dbc54..5a1c1e6065 100644
--- a/ortools/constraint_solver/samples/SimpleRoutingProgram.csproj
+++ b/ortools/constraint_solver/samples/SimpleRoutingProgram.csproj
@@ -19,6 +19,6 @@
-
+
diff --git a/ortools/constraint_solver/samples/Tsp.csproj b/ortools/constraint_solver/samples/Tsp.csproj
index 8fb102db40..c0e678d708 100644
--- a/ortools/constraint_solver/samples/Tsp.csproj
+++ b/ortools/constraint_solver/samples/Tsp.csproj
@@ -19,6 +19,6 @@
-
+
diff --git a/ortools/constraint_solver/samples/TspCircuitBoard.csproj b/ortools/constraint_solver/samples/TspCircuitBoard.csproj
index 1ebd8e1c6d..1311c14d56 100644
--- a/ortools/constraint_solver/samples/TspCircuitBoard.csproj
+++ b/ortools/constraint_solver/samples/TspCircuitBoard.csproj
@@ -19,6 +19,6 @@
-
+
diff --git a/ortools/constraint_solver/samples/TspCities.csproj b/ortools/constraint_solver/samples/TspCities.csproj
index 6e72e6a3fe..8e0b08f211 100644
--- a/ortools/constraint_solver/samples/TspCities.csproj
+++ b/ortools/constraint_solver/samples/TspCities.csproj
@@ -19,6 +19,6 @@
-
+
diff --git a/ortools/constraint_solver/samples/TspDistanceMatrix.csproj b/ortools/constraint_solver/samples/TspDistanceMatrix.csproj
index 260c1aad15..1d0ae9646c 100644
--- a/ortools/constraint_solver/samples/TspDistanceMatrix.csproj
+++ b/ortools/constraint_solver/samples/TspDistanceMatrix.csproj
@@ -19,6 +19,6 @@
-
+
diff --git a/ortools/constraint_solver/samples/Vrp.csproj b/ortools/constraint_solver/samples/Vrp.csproj
index f11d4aa645..5505164dbb 100644
--- a/ortools/constraint_solver/samples/Vrp.csproj
+++ b/ortools/constraint_solver/samples/Vrp.csproj
@@ -19,6 +19,6 @@
-
+
diff --git a/ortools/constraint_solver/samples/VrpCapacity.csproj b/ortools/constraint_solver/samples/VrpCapacity.csproj
index a064e9f28a..50c2ad84ac 100644
--- a/ortools/constraint_solver/samples/VrpCapacity.csproj
+++ b/ortools/constraint_solver/samples/VrpCapacity.csproj
@@ -19,6 +19,6 @@
-
+
diff --git a/ortools/constraint_solver/samples/VrpDropNodes.csproj b/ortools/constraint_solver/samples/VrpDropNodes.csproj
index 22cb7a06e6..8c9d9a49f0 100644
--- a/ortools/constraint_solver/samples/VrpDropNodes.csproj
+++ b/ortools/constraint_solver/samples/VrpDropNodes.csproj
@@ -19,6 +19,6 @@
-
+
diff --git a/ortools/constraint_solver/samples/VrpGlobalSpan.csproj b/ortools/constraint_solver/samples/VrpGlobalSpan.csproj
index 00fc45c834..665b6e21e5 100644
--- a/ortools/constraint_solver/samples/VrpGlobalSpan.csproj
+++ b/ortools/constraint_solver/samples/VrpGlobalSpan.csproj
@@ -19,6 +19,6 @@
-
+
diff --git a/ortools/constraint_solver/samples/VrpInitialRoutes.csproj b/ortools/constraint_solver/samples/VrpInitialRoutes.csproj
index 25d988988c..fe1961ab18 100644
--- a/ortools/constraint_solver/samples/VrpInitialRoutes.csproj
+++ b/ortools/constraint_solver/samples/VrpInitialRoutes.csproj
@@ -19,6 +19,6 @@
-
+
diff --git a/ortools/constraint_solver/samples/VrpPickupDelivery.csproj b/ortools/constraint_solver/samples/VrpPickupDelivery.csproj
index 3eacf6c20a..626874a3fc 100644
--- a/ortools/constraint_solver/samples/VrpPickupDelivery.csproj
+++ b/ortools/constraint_solver/samples/VrpPickupDelivery.csproj
@@ -19,6 +19,6 @@
-
+
diff --git a/ortools/constraint_solver/samples/VrpPickupDeliveryFifo.csproj b/ortools/constraint_solver/samples/VrpPickupDeliveryFifo.csproj
index 2e98a35022..98ed5abb46 100644
--- a/ortools/constraint_solver/samples/VrpPickupDeliveryFifo.csproj
+++ b/ortools/constraint_solver/samples/VrpPickupDeliveryFifo.csproj
@@ -19,6 +19,6 @@
-
+
diff --git a/ortools/constraint_solver/samples/VrpPickupDeliveryLifo.csproj b/ortools/constraint_solver/samples/VrpPickupDeliveryLifo.csproj
index 2d8d3b5623..862ae91982 100644
--- a/ortools/constraint_solver/samples/VrpPickupDeliveryLifo.csproj
+++ b/ortools/constraint_solver/samples/VrpPickupDeliveryLifo.csproj
@@ -19,6 +19,6 @@
-
+
diff --git a/ortools/constraint_solver/samples/VrpResources.csproj b/ortools/constraint_solver/samples/VrpResources.csproj
index 3db7bd22f4..aaa36a073f 100644
--- a/ortools/constraint_solver/samples/VrpResources.csproj
+++ b/ortools/constraint_solver/samples/VrpResources.csproj
@@ -19,6 +19,6 @@
-
+
diff --git a/ortools/constraint_solver/samples/VrpStartsEnds.csproj b/ortools/constraint_solver/samples/VrpStartsEnds.csproj
index d6cd682295..290158a0a0 100644
--- a/ortools/constraint_solver/samples/VrpStartsEnds.csproj
+++ b/ortools/constraint_solver/samples/VrpStartsEnds.csproj
@@ -19,6 +19,6 @@
-
+
diff --git a/ortools/constraint_solver/samples/VrpTimeWindows.csproj b/ortools/constraint_solver/samples/VrpTimeWindows.csproj
index 873fbc810f..75a677fd63 100644
--- a/ortools/constraint_solver/samples/VrpTimeWindows.csproj
+++ b/ortools/constraint_solver/samples/VrpTimeWindows.csproj
@@ -19,6 +19,6 @@
-
+
diff --git a/ortools/constraint_solver/samples/VrpWithTimeLimit.csproj b/ortools/constraint_solver/samples/VrpWithTimeLimit.csproj
index fe6662388a..9bfbf8a563 100644
--- a/ortools/constraint_solver/samples/VrpWithTimeLimit.csproj
+++ b/ortools/constraint_solver/samples/VrpWithTimeLimit.csproj
@@ -19,6 +19,6 @@
-
+
diff --git a/ortools/constraint_solver/search.cc b/ortools/constraint_solver/search.cc
index eb19b92610..5e95e1b6ca 100644
--- a/ortools/constraint_solver/search.cc
+++ b/ortools/constraint_solver/search.cc
@@ -26,6 +26,7 @@
#include "absl/strings/str_cat.h"
#include "absl/strings/str_format.h"
#include "absl/strings/str_join.h"
+#include "absl/time/time.h"
#include "ortools/base/bitmap.h"
#include "ortools/base/commandlineflags.h"
#include "ortools/base/hash.h"
@@ -3942,12 +3943,11 @@ void SearchLimit::TopPeriodicCheck() {
// ----- Regular Limit -----
-RegularLimit::RegularLimit(Solver* const s, int64 time, int64 branches,
+RegularLimit::RegularLimit(Solver* const s, absl::Duration time, int64 branches,
int64 failures, int64 solutions,
bool smart_time_check, bool cumulative)
: SearchLimit(s),
- duration_limit_(time == kint64max ? absl::InfiniteDuration()
- : absl::Milliseconds(time)),
+ duration_limit_(time),
solver_time_at_limit_start_(s->Now()),
last_time_elapsed_(absl::ZeroDuration()),
check_count_(0),
@@ -4115,8 +4115,9 @@ RegularLimit* Solver::MakeLimit(int64 time, int64 branches, int64 failures,
RegularLimit* Solver::MakeLimit(int64 time, int64 branches, int64 failures,
int64 solutions, bool smart_time_check,
bool cumulative) {
- return RevAlloc(new RegularLimit(this, time, branches, failures, solutions,
- smart_time_check, cumulative));
+ return RevAlloc(new RegularLimit(this, absl::Milliseconds(time), branches,
+ failures, solutions, smart_time_check,
+ cumulative));
}
RegularLimit* Solver::MakeLimit(const RegularLimitParameters& proto) {
diff --git a/ortools/graph/samples/SimpleMaxFlowProgram.csproj b/ortools/graph/samples/SimpleMaxFlowProgram.csproj
index e0a79e15c7..b973102a73 100644
--- a/ortools/graph/samples/SimpleMaxFlowProgram.csproj
+++ b/ortools/graph/samples/SimpleMaxFlowProgram.csproj
@@ -19,6 +19,6 @@
-
+
diff --git a/ortools/graph/samples/SimpleMinCostFlowProgram.csproj b/ortools/graph/samples/SimpleMinCostFlowProgram.csproj
index 9c04f2fef3..3be35b6179 100644
--- a/ortools/graph/samples/SimpleMinCostFlowProgram.csproj
+++ b/ortools/graph/samples/SimpleMinCostFlowProgram.csproj
@@ -19,6 +19,6 @@
-
+
diff --git a/ortools/graph/util.h b/ortools/graph/util.h
index 0d7f74f369..a848653b51 100644
--- a/ortools/graph/util.h
+++ b/ortools/graph/util.h
@@ -24,6 +24,7 @@
#include
#include
+#include "absl/container/inlined_vector.h"
#include "ortools/base/hash.h"
#include "ortools/base/map_util.h"
#include "ortools/graph/connected_components.h"
@@ -382,9 +383,13 @@ template
std::vector ComputeOnePossibleReverseArcMapping(
const Graph& graph, bool die_if_not_symmetric) {
std::vector reverse_arc(graph.num_arcs(), -1);
- std::unordered_multimap,
- /*arc index*/ int>
+ // We need a multi-map since a given (tail,head) may appear several times.
+ // NOTE(user): It's free, in terms of space, to use InlinedVector
+ // rather than std::vector. See go/inlined-vector-size.
+ absl::flat_hash_map,
+ absl::InlinedVector>
arc_map;
+
for (int arc = 0; arc < graph.num_arcs(); ++arc) {
const int tail = graph.Tail(arc);
const int head = graph.Head(arc);
@@ -398,17 +403,27 @@ std::vector ComputeOnePossibleReverseArcMapping(
if (it != arc_map.end()) {
// Found a reverse arc! Store the mapping and remove the
// reverse arc from the map.
- reverse_arc[arc] = it->second;
- reverse_arc[it->second] = arc;
- arc_map.erase(it);
+ reverse_arc[arc] = it->second.back();
+ reverse_arc[it->second.back()] = arc;
+ if (it->second.size() > 1) {
+ it->second.pop_back();
+ } else {
+ arc_map.erase(it);
+ }
} else {
// Reverse arc not in the map. Add the current arc to the map.
- arc_map.insert({{tail, head}, arc});
+ arc_map[{tail, head}].push_back(arc);
}
}
// Algorithm check, for debugging.
- DCHECK_EQ(std::count(reverse_arc.begin(), reverse_arc.end(), -1),
- arc_map.size());
+ if (DEBUG_MODE) {
+ int64 num_unmapped_arcs = 0;
+ for (const auto& p : arc_map) {
+ num_unmapped_arcs += p.second.size();
+ }
+ DCHECK_EQ(std::count(reverse_arc.begin(), reverse_arc.end(), -1),
+ num_unmapped_arcs);
+ }
if (die_if_not_symmetric) {
CHECK_EQ(arc_map.size(), 0)
<< "The graph is not symmetric: " << arc_map.size() << " of "
diff --git a/ortools/java/com/google/ortools/sat/IntVar.java b/ortools/java/com/google/ortools/sat/IntVar.java
index 944e952768..99172ebe38 100644
--- a/ortools/java/com/google/ortools/sat/IntVar.java
+++ b/ortools/java/com/google/ortools/sat/IntVar.java
@@ -116,5 +116,5 @@ public final class IntVar implements Literal, LinearExpr {
private final CpModelProto.Builder modelBuilder;
private final int variableIndex;
private final IntegerVariableProto.Builder varBuilder;
- private NotBooleanVariable negation_;
+ private NotBooleanVariable negation_ = null;
}
diff --git a/ortools/linear_solver/gurobi_interface.cc b/ortools/linear_solver/gurobi_interface.cc
index c7e120ea14..87e122636e 100644
--- a/ortools/linear_solver/gurobi_interface.cc
+++ b/ortools/linear_solver/gurobi_interface.cc
@@ -34,6 +34,7 @@
#include "ortools/linear_solver/gurobi_environment.h"
#include "ortools/linear_solver/gurobi_proto_solver.h"
#include "ortools/linear_solver/linear_solver.h"
+#include "ortools/linear_solver/linear_solver_callback.h"
DEFINE_int32(num_gurobi_threads, 4, "Number of threads available for Gurobi.");
@@ -150,6 +151,9 @@ class GurobiInterface : public MPSolverInterface {
// Iterates through the solutions in Gurobi's solution pool.
bool NextSolution() override;
+ void SetCallback(MPCallback* mp_callback) override;
+ bool SupportsCallbacks() const override { return true; }
+
private:
// Sets all parameters in the underlying solver.
void SetParameters(const MPSolverParameters& param) override;
@@ -177,6 +181,7 @@ class GurobiInterface : public MPSolverInterface {
GRBenv* env_;
bool mip_;
int current_solution_index_;
+ MPCallback* callback_ = nullptr;
bool update_branching_priorities_ = false;
};
@@ -187,6 +192,273 @@ void CheckedGurobiCall(int err, GRBenv* const env) {
<< GRBgeterrormsg(env);
}
+// For interacting directly with the Gurobi C API for callbacks.
+struct GurobiInternalCallbackContext {
+ GRBmodel* model;
+ void* gurobi_internal_callback_data;
+ int where;
+};
+
+class GurobiMPCallbackContext : public MPCallbackContext {
+ public:
+ GurobiMPCallbackContext(GRBenv* env, bool might_add_cuts,
+ bool might_add_lazy_constraints);
+
+ // Implementation of the interface.
+ MPCallbackEvent Event() override;
+ bool CanQueryVariableValues() override;
+ double VariableValue(const MPVariable* variable) override;
+ void AddCut(const LinearRange& cutting_plane) override;
+ void AddLazyConstraint(const LinearRange& lazy_constraint) override;
+ double SuggestSolution(
+ const absl::flat_hash_map& solution) override;
+ int64 NumExploredNodes() override;
+
+ // Call this method to update the internal state of the callback context
+ // before passing it to MPCallback::RunCallback().
+ void UpdateFromGurobiState(
+ const GurobiInternalCallbackContext& gurobi_internal_context);
+
+ private:
+ // Wraps GRBcbget(), used to query the state of the solver. See
+ // http://www.gurobi.com/documentation/8.0/refman/callback_codes.html#sec:CallbackCodes
+ // for callback_code values.
+ template
+ T GurobiCallbackGet(
+ const GurobiInternalCallbackContext& gurobi_internal_context,
+ int callback_code);
+ void CheckedGurobiCall(int gurobi_error_code) const;
+
+ template
+ void AddGeneratedConstraint(const LinearRange& linear_range,
+ GRBConstraintFunction grb_constraint_function);
+
+ // Returns the number of variables in the Gurobi model.
+ // WARNING(rander): This is not the same as solver_->variables_.size(), the
+ // use of range constraints adds new variables to the Gurobi model.
+ int NumGurobiVariables() const;
+
+ GRBenv* const env_;
+
+ const bool might_add_cuts_;
+ const bool might_add_lazy_constraints_;
+
+ // Stateful, updated before each call to the callback.
+ GurobiInternalCallbackContext current_gurobi_internal_callback_context_;
+ bool variable_values_extracted_ = false;
+ std::vector variable_values_;
+};
+
+void GurobiMPCallbackContext::CheckedGurobiCall(int gurobi_error_code) const {
+ ::operations_research::CheckedGurobiCall(gurobi_error_code, env_);
+}
+
+GurobiMPCallbackContext::GurobiMPCallbackContext(
+ GRBenv* env, bool might_add_cuts, bool might_add_lazy_constraints)
+ : env_(ABSL_DIE_IF_NULL(env)),
+ might_add_cuts_(might_add_cuts),
+ might_add_lazy_constraints_(might_add_lazy_constraints) {}
+
+void GurobiMPCallbackContext::UpdateFromGurobiState(
+ const GurobiInternalCallbackContext& gurobi_internal_context) {
+ current_gurobi_internal_callback_context_ = gurobi_internal_context;
+ variable_values_extracted_ = false;
+}
+
+int64 GurobiMPCallbackContext::NumExploredNodes() {
+ switch (Event()) {
+ case MPCallbackEvent::kMipNode:
+ return static_cast(GurobiCallbackGet(
+ current_gurobi_internal_callback_context_, GRB_CB_MIPNODE_NODCNT));
+ case MPCallbackEvent::kMipSolution:
+ return static_cast(GurobiCallbackGet(
+ current_gurobi_internal_callback_context_, GRB_CB_MIPSOL_NODCNT));
+ default:
+ LOG(FATAL) << "Node count is supported only for callback events MIP_NODE "
+ "and MIP_SOL, but was requested at: "
+ << ToString(Event());
+ }
+}
+
+template
+T GurobiMPCallbackContext::GurobiCallbackGet(
+ const GurobiInternalCallbackContext& gurobi_internal_context,
+ const int callback_code) {
+ T result = 0;
+ CheckedGurobiCall(
+ GRBcbget(gurobi_internal_context.gurobi_internal_callback_data,
+ gurobi_internal_context.where, callback_code,
+ static_cast(&result)));
+ return result;
+}
+
+MPCallbackEvent GurobiMPCallbackContext::Event() {
+ switch (current_gurobi_internal_callback_context_.where) {
+ case GRB_CB_POLLING:
+ return MPCallbackEvent::kPolling;
+ case GRB_CB_PRESOLVE:
+ return MPCallbackEvent::kPresolve;
+ case GRB_CB_SIMPLEX:
+ return MPCallbackEvent::kSimplex;
+ case GRB_CB_MIP:
+ return MPCallbackEvent::kMip;
+ case GRB_CB_MIPSOL:
+ return MPCallbackEvent::kMipSolution;
+ case GRB_CB_MIPNODE:
+ return MPCallbackEvent::kMipNode;
+ case GRB_CB_MESSAGE:
+ return MPCallbackEvent::kMessage;
+ case GRB_CB_BARRIER:
+ return MPCallbackEvent::kBarrier;
+ // TODO(b/112427356): in Gurobi 8.0, there is a new callback location.
+ // case GRB_CB_MULTIOBJ:
+ // return MPCallbackEvent::kMultiObj;
+ default:
+ LOG_FIRST_N(ERROR, 1) << "Gurobi callback at unknown where="
+ << current_gurobi_internal_callback_context_.where;
+ return MPCallbackEvent::kUnknown;
+ }
+}
+
+bool GurobiMPCallbackContext::CanQueryVariableValues() {
+ const MPCallbackEvent where = Event();
+ if (where == MPCallbackEvent::kMipSolution) {
+ return true;
+ }
+ if (where == MPCallbackEvent::kMipNode) {
+ const int gurobi_node_status = GurobiCallbackGet(
+ current_gurobi_internal_callback_context_, GRB_CB_MIPNODE_STATUS);
+ return gurobi_node_status == GRB_OPTIMAL;
+ }
+ return false;
+}
+
+double GurobiMPCallbackContext::VariableValue(const MPVariable* variable) {
+ CHECK(variable != nullptr);
+ if (!variable_values_extracted_) {
+ const MPCallbackEvent where = Event();
+ CHECK(where == MPCallbackEvent::kMipSolution ||
+ where == MPCallbackEvent::kMipNode)
+ << "You can only call VariableValue at "
+ << ToString(MPCallbackEvent::kMipSolution) << " or "
+ << ToString(MPCallbackEvent::kMipNode)
+ << " but called from: " << ToString(where);
+ const int gurobi_get_var_param = where == MPCallbackEvent::kMipNode
+ ? GRB_CB_MIPNODE_REL
+ : GRB_CB_MIPSOL_SOL;
+
+ variable_values_.resize(NumGurobiVariables());
+ CheckedGurobiCall(GRBcbget(
+ current_gurobi_internal_callback_context_.gurobi_internal_callback_data,
+ current_gurobi_internal_callback_context_.where, gurobi_get_var_param,
+ static_cast(variable_values_.data())));
+ variable_values_extracted_ = true;
+ }
+ return variable_values_[variable->index()];
+}
+
+template
+void GurobiMPCallbackContext::AddGeneratedConstraint(
+ const LinearRange& linear_range,
+ GRBConstraintFunction grb_constraint_function) {
+ std::vector variable_indices;
+ std::vector variable_coefficients;
+ const int num_terms = linear_range.linear_expr().terms().size();
+ variable_indices.reserve(num_terms);
+ variable_coefficients.reserve(num_terms);
+ for (const auto& var_coef_pair : linear_range.linear_expr().terms()) {
+ variable_indices.push_back(var_coef_pair.first->index());
+ variable_coefficients.push_back(var_coef_pair.second);
+ }
+ if (std::isfinite(linear_range.upper_bound())) {
+ CheckedGurobiCall(grb_constraint_function(
+ current_gurobi_internal_callback_context_.gurobi_internal_callback_data,
+ variable_indices.size(), variable_indices.data(),
+ variable_coefficients.data(), GRB_LESS_EQUAL,
+ linear_range.upper_bound()));
+ }
+ if (std::isfinite(linear_range.lower_bound())) {
+ CheckedGurobiCall(grb_constraint_function(
+ current_gurobi_internal_callback_context_.gurobi_internal_callback_data,
+ variable_indices.size(), variable_indices.data(),
+ variable_coefficients.data(), GRB_GREATER_EQUAL,
+ linear_range.lower_bound()));
+ }
+}
+
+void GurobiMPCallbackContext::AddCut(const LinearRange& cutting_plane) {
+ CHECK(might_add_cuts_);
+ const MPCallbackEvent where = Event();
+ CHECK(where == MPCallbackEvent::kMipNode)
+ << "Cuts can only be added at MIP_NODE, tried to add cut at: "
+ << ToString(where);
+ AddGeneratedConstraint(cutting_plane, GRBcbcut);
+}
+
+void GurobiMPCallbackContext::AddLazyConstraint(
+ const LinearRange& lazy_constraint) {
+ CHECK(might_add_lazy_constraints_);
+ const MPCallbackEvent where = Event();
+ CHECK(where == MPCallbackEvent::kMipNode ||
+ where == MPCallbackEvent::kMipSolution)
+ << "Lazy constraints can only be added at MIP_NODE or MIP_SOL, tried to "
+ "add lazy constraint at: "
+ << ToString(where);
+ AddGeneratedConstraint(lazy_constraint, GRBcblazy);
+}
+
+double GurobiMPCallbackContext::SuggestSolution(
+ const absl::flat_hash_map& solution) {
+ const MPCallbackEvent where = Event();
+ CHECK(where == MPCallbackEvent::kMipNode)
+ << "Feasible solutions can only be added at MIP_NODE, tried to add "
+ "solution at: "
+ << ToString(where);
+
+ std::vector full_solution(NumGurobiVariables(), GRB_UNDEFINED);
+ for (const auto& variable_value : solution) {
+ const MPVariable* var = variable_value.first;
+ full_solution[var->index()] = variable_value.second;
+ }
+
+ double objval;
+ CheckedGurobiCall(GRBcbsolution(
+ current_gurobi_internal_callback_context_.gurobi_internal_callback_data,
+ full_solution.data(), &objval));
+
+ return objval;
+}
+
+int GurobiMPCallbackContext::NumGurobiVariables() const {
+ int num_gurobi_variables = 0;
+ CheckedGurobiCall(
+ GRBgetintattr(current_gurobi_internal_callback_context_.model, "NumVars",
+ &num_gurobi_variables));
+ return num_gurobi_variables;
+}
+
+struct MPCallbackWithGurobiContext {
+ GurobiMPCallbackContext* context;
+ MPCallback* callback;
+};
+
+// NOTE(user): This function must have this exact API, because we are passing
+// it to Gurobi as a callback.
+int __stdcall CallbackImpl(GRBmodel* model, void* gurobi_internal_callback_data,
+ int where, void* raw_model_and_callback) {
+ MPCallbackWithGurobiContext* const callback_with_context =
+ static_cast(raw_model_and_callback);
+ CHECK(callback_with_context != nullptr);
+ CHECK(callback_with_context->context != nullptr);
+ CHECK(callback_with_context->callback != nullptr);
+ GurobiInternalCallbackContext gurobi_internal_context{
+ model, gurobi_internal_callback_data, where};
+ callback_with_context->context->UpdateFromGurobiState(
+ gurobi_internal_context);
+ callback_with_context->callback->RunCallback(callback_with_context->context);
+ return 0;
+}
+
} // namespace
void GurobiInterface::CheckedGurobiCall(int err) const {
@@ -733,6 +1005,28 @@ MPSolver::ResultStatus GurobiInterface::Solve(const MPSolverParameters& param) {
solver_->SetSolverSpecificParametersAsString(
solver_->solver_specific_parameter_string_);
+ std::unique_ptr gurobi_context;
+ MPCallbackWithGurobiContext mp_callback_with_context;
+ int gurobi_precrush = 0;
+ int gurobi_lazy_constraint = 0;
+ if (callback_ == nullptr) {
+ CheckedGurobiCall(GRBsetcallbackfunc(model_, nullptr, nullptr));
+ } else {
+ gurobi_context = absl::make_unique(
+ env_, callback_->might_add_cuts(),
+ callback_->might_add_lazy_constraints());
+ mp_callback_with_context.context = gurobi_context.get();
+ mp_callback_with_context.callback = callback_;
+ CheckedGurobiCall(GRBsetcallbackfunc(
+ model_, CallbackImpl, static_cast(&mp_callback_with_context)));
+ gurobi_precrush = callback_->might_add_cuts();
+ gurobi_lazy_constraint = callback_->might_add_lazy_constraints();
+ }
+ CheckedGurobiCall(
+ GRBsetintparam(GRBgetenv(model_), GRB_INT_PAR_PRECRUSH, gurobi_precrush));
+ CheckedGurobiCall(GRBsetintparam(
+ GRBgetenv(model_), GRB_INT_PAR_LAZYCONSTRAINTS, gurobi_lazy_constraint));
+
// Solve
timer.Restart();
const int status = GRBoptimize(model_);
@@ -907,5 +1201,9 @@ MPSolverInterface* BuildGurobiInterface(bool mip, MPSolver* const solver) {
return new GurobiInterface(solver, mip);
}
+void GurobiInterface::SetCallback(MPCallback* mp_callback) {
+ callback_ = mp_callback;
+}
+
} // namespace operations_research
#endif // #if defined(USE_GUROBI)
diff --git a/ortools/linear_solver/gurobi_proto_solver.cc b/ortools/linear_solver/gurobi_proto_solver.cc
index 33a69bf85a..8f74097881 100644
--- a/ortools/linear_solver/gurobi_proto_solver.cc
+++ b/ortools/linear_solver/gurobi_proto_solver.cc
@@ -479,6 +479,15 @@ util::StatusOr GurobiSolveProto(
RETURN_IF_GUROBI_ERROR(
GRBgetdblattrarray(gurobi_model, GRB_DBL_ATTR_X, 0, variable_size,
response.mutable_variable_value()->mutable_data()));
+ // NOTE, GurobiSolveProto() is exposed to external clients via MPSolver API,
+ // which assumes the solution values of integer variables are rounded to
+ // integer values.
+ for (int v = 0; v < variable_size; ++v) {
+ if (model.variable(v).is_integer()) {
+ (*response.mutable_variable_value())[v] =
+ std::round(response.variable_value(v));
+ }
+ }
if (!has_integer_variables && model.general_constraint_size() == 0) {
response.mutable_dual_value()->Resize(model.constraint_size(), 0);
RETURN_IF_GUROBI_ERROR(GRBgetdblattrarray(
diff --git a/ortools/linear_solver/linear_solver.cc b/ortools/linear_solver/linear_solver.cc
index 41e17d8774..ffb0f983a0 100644
--- a/ortools/linear_solver/linear_solver.cc
+++ b/ortools/linear_solver/linear_solver.cc
@@ -490,6 +490,9 @@ constexpr
#if defined(USE_GUROBI)
{MPSolver::GUROBI_LINEAR_PROGRAMMING, "gurobi_lp"},
#endif
+#if defined(USE_CPLEX)
+ {MPSolver::CPLEX_LINEAR_PROGRAMMING, "cplex_lp"},
+#endif
#if defined(USE_XPRESS)
{MPSolver::XPRESS_LINEAR_PROGRAMMING, "xpress_lp"},
#endif
@@ -507,9 +510,13 @@ constexpr
#if defined(USE_GUROBI)
{MPSolver::GUROBI_MIXED_INTEGER_PROGRAMMING, "gurobi_mip"},
#endif
+#if defined(USE_CPLEX)
+ {MPSolver::CPLEX_MIXED_INTEGER_PROGRAMMING, "cplex_mip"},
+#endif
#if defined(USE_XPRESS)
{MPSolver::XPRESS_MIXED_INTEGER_PROGRAMMING, "xpress_mip"},
#endif
+#endif
};
// static
@@ -1489,6 +1496,48 @@ void MPSolver::GenerateConstraintNameIndex() const {
bool MPSolver::NextSolution() { return interface_->NextSolution(); }
+void MPSolver::SetCallback(MPCallback* mp_callback) {
+ interface_->SetCallback(mp_callback);
+}
+
+bool MPSolver::SupportsCallbacks() const {
+ return interface_->SupportsCallbacks();
+}
+
+bool MPSolverResponseStatusIsRpcError(MPSolverResponseStatus status) {
+ switch (status) {
+ // Cases that don't yield an RPC error when they happen on the server.
+ case MPSOLVER_OPTIMAL:
+ case MPSOLVER_FEASIBLE:
+ case MPSOLVER_INFEASIBLE:
+ case MPSOLVER_NOT_SOLVED:
+ case MPSOLVER_UNBOUNDED:
+ case MPSOLVER_ABNORMAL:
+ case MPSOLVER_UNKNOWN_STATUS:
+ return false;
+ // Cases that should never happen with the linear solver server. We prefer
+ // to consider those as "not RPC errors".
+ case MPSOLVER_MODEL_IS_VALID:
+ return false;
+ // Cases that yield an RPC error when they happen on the server.
+ case MPSOLVER_MODEL_INVALID:
+ case MPSOLVER_MODEL_INVALID_SOLUTION_HINT:
+ case MPSOLVER_MODEL_INVALID_SOLVER_PARAMETERS:
+ case MPSOLVER_SOLVER_TYPE_UNAVAILABLE:
+ case MPSOLVER_SERVER_ERROR:
+ case MPSOLVER_SERVER_ERROR_REQUEST_IS_QOD:
+ case MPSOLVER_SERVER_ERROR_RPC_DEADLINE_TOO_SMALL:
+ case MPSOLVER_SERVER_ERROR_SERVER_IS_SHUTTING_DOWN:
+ case MPSOLVER_SERVER_ERROR_REQUEST_TOO_LARGE:
+ case MPSOLVER_SERVER_ERROR_FULL:
+ return true;
+ }
+ LOG(DFATAL)
+ << "MPSolverResponseStatusIsRpcError() called with invalid status "
+ << "(value: " << status << ")";
+ return false;
+}
+
// ---------- MPSolverInterface ----------
const int MPSolverInterface::kDummyVariableIndex = 0;
diff --git a/ortools/linear_solver/linear_solver.h b/ortools/linear_solver/linear_solver.h
index edaf876ea4..1c272ec6d8 100644
--- a/ortools/linear_solver/linear_solver.h
+++ b/ortools/linear_solver/linear_solver.h
@@ -154,6 +154,7 @@
#include "ortools/base/timer.h"
#include "ortools/linear_solver/linear_expr.h"
#include "ortools/linear_solver/linear_solver.pb.h"
+#include "ortools/linear_solver/linear_solver_callback.h"
#include "ortools/port/proto_utils.h"
namespace operations_research {
@@ -416,7 +417,7 @@ class MPSolver {
NOT_SOLVED = 6
};
- /// Solves the problem using default parameter values.
+ /// Solves the problem using the default parameter values.
ResultStatus Solve();
/// Solves the problem using the specified parameter values.
@@ -733,6 +734,15 @@ class MPSolver {
*/
ABSL_MUST_USE_RESULT bool NextSolution();
+ // Does not take ownership of "mp_callback".
+ //
+ // As of 2019-10-22, only SCIP and Gurobi support Callbacks.
+ // SCIP does not support suggesting a heuristic solution in the callback.
+ //
+ // See go/mpsolver-callbacks for additional documentation.
+ void SetCallback(MPCallback* mp_callback);
+ bool SupportsCallbacks() const;
+
// DEPRECATED: Use TimeLimit() and SetTimeLimit(absl::Duration) instead.
// NOTE: These deprecated functions used the convention time_limit = 0 to mean
// "no limit", which now corresponds to time_limit_ = InfiniteDuration().
@@ -1095,25 +1105,25 @@ class MPVariable {
: index_(index),
lb_(lb),
ub_(ub),
+ integer_(integer),
name_(name.empty() ? absl::StrFormat("auto_v_%09d", index) : name),
solution_value_(0.0),
reduced_cost_(0.0),
- interface_(interface_in),
- integer_(integer){}
+ interface_(interface_in) {}
void set_solution_value(double value) { solution_value_ = value; }
void set_reduced_cost(double reduced_cost) { reduced_cost_ = reduced_cost; }
private:
const int index_;
- int branching_priority_ = 0;
double lb_;
double ub_;
+ bool integer_;
const std::string name_;
double solution_value_;
double reduced_cost_;
+ int branching_priority_ = 0;
MPSolverInterface* const interface_;
- bool integer_;
DISALLOW_COPY_AND_ASSIGN(MPVariable);
};
@@ -1238,8 +1248,8 @@ class MPConstraint {
lb_(lb),
ub_(ub),
name_(name.empty() ? absl::StrFormat("auto_c_%09d", index) : name),
- indicator_variable_(nullptr),
is_lazy_(false),
+ indicator_variable_(nullptr),
dual_value_(0.0),
interface_(interface_in) {}
@@ -1264,16 +1274,16 @@ class MPConstraint {
// Name.
const std::string name_;
- // If given, this constraint is only active if `indicator_variable_`'s value
- // is equal to `indicator_value_`.
- const MPVariable* indicator_variable_;
- bool indicator_value_;
-
// True if the constraint is "lazy", i.e. the constraint is added to the
// underlying Linear Programming solver only if it is violated.
// By default this parameter is 'false'.
bool is_lazy_;
+ // If given, this constraint is only active if `indicator_variable_`'s value
+ // is equal to `indicator_value_`.
+ const MPVariable* indicator_variable_;
+ bool indicator_value_;
+
double dual_value_;
MPSolverInterface* const interface_;
DISALLOW_COPY_AND_ASSIGN(MPConstraint);
@@ -1445,6 +1455,13 @@ class MPSolverParameters {
DISALLOW_COPY_AND_ASSIGN(MPSolverParameters);
};
+// Whether the given MPSolverResponseStatus (of a solve) would yield an RPC
+// error when happening on the linear solver stubby server, see
+// ./linear_solver_service.proto.
+// Note that RPC errors forbid to carry a response to the client, who can only
+// see the RPC error itself (error code + error message).
+bool MPSolverResponseStatusIsRpcError(MPSolverResponseStatus status);
+
// This class wraps the actual mathematical programming solvers. Each
// solver (GLOP, CLP, CBC, GLPK, SCIP) has its own interface class that
// derives from this abstract class. This class is never directly
@@ -1644,6 +1661,13 @@ class MPSolverInterface {
// See MPSolver::NextSolution() for contract.
virtual bool NextSolution() { return false; }
+ // See MPSolver::SetCallback() for details.
+ virtual void SetCallback(MPCallback* mp_callback) {
+ LOG(FATAL) << "Callbacks not supported for this solver.";
+ }
+
+ virtual bool SupportsCallbacks() const { return false; }
+
friend class MPSolver;
// To access the maximize_ bool and the MPSolver.
diff --git a/ortools/linear_solver/linear_solver.proto b/ortools/linear_solver/linear_solver.proto
index 6a70b359d0..b22debb3b3 100644
--- a/ortools/linear_solver/linear_solver.proto
+++ b/ortools/linear_solver/linear_solver.proto
@@ -411,8 +411,7 @@ message MPModelRequest {
GLPK_MIXED_INTEGER_PROGRAMMING = 4;
CBC_MIXED_INTEGER_PROGRAMMING = 5;
GUROBI_MIXED_INTEGER_PROGRAMMING = 7; // Commercial, needs a valid license.
- XPRESS_MIXED_INTEGER_PROGRAMMING =
- 102; // Commercial, needs a valid license.
+ XPRESS_MIXED_INTEGER_PROGRAMMING = 102; // Commercial, needs a valid license.
CPLEX_MIXED_INTEGER_PROGRAMMING = 11; // Commercial, needs a
// valid license.
BOP_INTEGER_PROGRAMMING = 12;
diff --git a/ortools/linear_solver/model_exporter.cc b/ortools/linear_solver/model_exporter.cc
index b96214fba3..1b0aa619ab 100644
--- a/ortools/linear_solver/model_exporter.cc
+++ b/ortools/linear_solver/model_exporter.cc
@@ -804,34 +804,37 @@ bool MPModelProtoExporter::ExportModelAsMpsFormat(
AppendMpsLineHeader("BV", "BOUND", &bounds_section);
absl::StrAppendFormat(&bounds_section, " %s\n", var_name);
} else {
- if (lb == -kInfinity && ub > 0) {
- // Non-standard MPS use seen on miplib2017/ns1456591 and adopted.
- // "MI" (indicating [-inf, 0] bounds) is supposed to be used only for
- // continuous variables, but solvers seem to read it as expected.
- AppendMpsLineHeader("MI", "BOUND", &bounds_section);
- absl::StrAppendFormat(&bounds_section, " %s\n", var_name);
- }
- // "LI" can be skipped if it's -inf, or if it's 0.
- // There is one exception to that rule: if UI=+inf, we can't skip LI=0
- // or the variable will be parsed as binary.
- if (lb != -kInfinity && (lb != 0.0 || ub == kInfinity)) {
- AppendMpsBound("LI", var_name, lb, &bounds_section);
- }
- if (ub != kInfinity) {
- AppendMpsBound("UI", var_name, ub, &bounds_section);
+ if (lb == ub) {
+ AppendMpsBound("FX", var_name, lb, &bounds_section);
+ } else {
+ if (lb == -kInfinity) {
+ AppendMpsLineHeader("MI", "BOUND", &bounds_section);
+ absl::StrAppendFormat(&bounds_section, " %s\n", var_name);
+ } else if (lb != 0.0 || ub == kInfinity) {
+ // "LI" can be skipped if it's 0.
+ // There is one exception to that rule: if UI=+inf, we can't skip
+ // LI=0 or the variable will be parsed as binary.
+ AppendMpsBound("LI", var_name, lb, &bounds_section);
+ }
+ if (ub != kInfinity) {
+ AppendMpsBound("UI", var_name, ub, &bounds_section);
+ }
}
}
} else {
if (lb == ub) {
AppendMpsBound("FX", var_name, lb, &bounds_section);
} else {
- if (lb != 0.0) {
+ if (lb == -kInfinity) {
+ AppendMpsLineHeader("MI", "BOUND", &bounds_section);
+ absl::StrAppendFormat(&bounds_section, " %s\n", var_name);
+ } else if (lb != 0.0) {
AppendMpsBound("LO", var_name, lb, &bounds_section);
- } else if (ub == +kInfinity) {
+ }
+ if (lb == 0.0 && ub == +kInfinity) {
AppendMpsLineHeader("PL", "BOUND", &bounds_section);
absl::StrAppendFormat(&bounds_section, " %s\n", var_name);
- }
- if (ub != +kInfinity) {
+ } else if (ub != +kInfinity) {
AppendMpsBound("UP", var_name, ub, &bounds_section);
}
}
diff --git a/ortools/linear_solver/python/linear_solver.i b/ortools/linear_solver/python/linear_solver.i
index fc15025c90..b48546c1f0 100644
--- a/ortools/linear_solver/python/linear_solver.i
+++ b/ortools/linear_solver/python/linear_solver.i
@@ -346,6 +346,7 @@ PY_CONVERT(MPVariable);
%unignore operations_research::MPVariable::SetLb;
%unignore operations_research::MPVariable::SetUb;
%unignore operations_research::MPVariable::SetBounds;
+%unignore operations_research::MPVariable::SetInteger;
// MPVariable: reader API.
%unignore operations_research::MPVariable::solution_value;
@@ -363,6 +364,7 @@ PY_CONVERT(MPVariable);
%unignore operations_research::MPConstraint::SetUb;
%unignore operations_research::MPConstraint::SetBounds;
%unignore operations_research::MPConstraint::set_is_lazy;
+%unignore operations_research::MPConstraint::Clear; // No unit test
// MPConstraint: reader API.
%unignore operations_research::MPConstraint::GetCoefficient;
diff --git a/ortools/linear_solver/samples/LinearProgrammingExample.csproj b/ortools/linear_solver/samples/LinearProgrammingExample.csproj
index bc4cc91e63..899043dbd8 100644
--- a/ortools/linear_solver/samples/LinearProgrammingExample.csproj
+++ b/ortools/linear_solver/samples/LinearProgrammingExample.csproj
@@ -19,6 +19,6 @@
-
+
diff --git a/ortools/linear_solver/samples/MipVarArray.cs b/ortools/linear_solver/samples/MipVarArray.cs
index ed14d2c764..517b4e1197 100644
--- a/ortools/linear_solver/samples/MipVarArray.cs
+++ b/ortools/linear_solver/samples/MipVarArray.cs
@@ -52,7 +52,7 @@ public class MipVarArray
MPVariable[] x = new MPVariable[data.NumVars];
for (int j = 0; j < data.NumVars; j++)
{
- x[j] = MakeIntVar(0.0, double.PositiveInfinity, "x");
+ x[j] = MakeIntVar(0.0, double.PositiveInfinity, String.Format("x_{0}", j));
}
Console.WriteLine("Number of variables = " + solver.NumVariables());
// [END variables]
diff --git a/ortools/linear_solver/samples/MipVarArray.csproj b/ortools/linear_solver/samples/MipVarArray.csproj
index ca8c2bf176..84133ee723 100644
--- a/ortools/linear_solver/samples/MipVarArray.csproj
+++ b/ortools/linear_solver/samples/MipVarArray.csproj
@@ -19,6 +19,6 @@
-
+
diff --git a/ortools/linear_solver/samples/SimpleLpProgram.csproj b/ortools/linear_solver/samples/SimpleLpProgram.csproj
index 50e489bc64..2ae953ea97 100644
--- a/ortools/linear_solver/samples/SimpleLpProgram.csproj
+++ b/ortools/linear_solver/samples/SimpleLpProgram.csproj
@@ -19,6 +19,6 @@
-
+
diff --git a/ortools/linear_solver/samples/SimpleMipProgram.csproj b/ortools/linear_solver/samples/SimpleMipProgram.csproj
index 745d9ca6f6..36dc177822 100644
--- a/ortools/linear_solver/samples/SimpleMipProgram.csproj
+++ b/ortools/linear_solver/samples/SimpleMipProgram.csproj
@@ -19,6 +19,6 @@
-
+
diff --git a/ortools/linear_solver/samples/mip_var_array.cc b/ortools/linear_solver/samples/mip_var_array.cc
index 70f5ced1fd..1b329d17d5 100644
--- a/ortools/linear_solver/samples/mip_var_array.cc
+++ b/ortools/linear_solver/samples/mip_var_array.cc
@@ -16,6 +16,7 @@
#include "ortools/linear_solver/linear_solver.h"
// [END import]
+// [START program_part1]
namespace operations_research {
// [START data_model]
struct DataModel {
@@ -36,6 +37,7 @@ void IntegerProgrammingExample() {
// [START data]
DataModel data;
// [END data]
+ // [END program_part1]
// [START solver]
// Create the mip solver with the CBC backend.
@@ -43,6 +45,7 @@ void IntegerProgrammingExample() {
MPSolver::CBC_MIXED_INTEGER_PROGRAMMING);
// [END solver]
+ // [START program_part2]
// [START variables]
const double infinity = solver.infinity();
// x[j] is an array of non-negative, integer variables.
@@ -96,3 +99,5 @@ int main(int argc, char** argv) {
operations_research::IntegerProgrammingExample();
return EXIT_SUCCESS;
}
+// [END program_part2]
+// [END program]
diff --git a/ortools/linear_solver/samples/mip_var_array.py b/ortools/linear_solver/samples/mip_var_array.py
index 884174038f..e449088200 100644
--- a/ortools/linear_solver/samples/mip_var_array.py
+++ b/ortools/linear_solver/samples/mip_var_array.py
@@ -19,6 +19,7 @@ from ortools.linear_solver import pywraplp
# [END import]
+# [START program_part1]
# [START data_model]
def create_data_model():
"""Stores the data for the problem."""
@@ -43,13 +44,14 @@ def main():
# [START data]
data = create_data_model()
# [END data]
-
+ # [END program_part1]
# [START solver]
# Create the mip solver with the CBC backend.
solver = pywraplp.Solver('simple_mip_program',
pywraplp.Solver.CBC_MIXED_INTEGER_PROGRAMMING)
# [END solver]
+ # [START program_part2]
# [START variables]
infinity = solver.infinity()
x = {}
@@ -101,4 +103,5 @@ def main():
if __name__ == '__main__':
main()
+# [END program_part2]
# [END program]
diff --git a/ortools/linear_solver/scip_interface.cc b/ortools/linear_solver/scip_interface.cc
index 6fd7d9b2e9..c42bd4c850 100644
--- a/ortools/linear_solver/scip_interface.cc
+++ b/ortools/linear_solver/scip_interface.cc
@@ -33,10 +33,13 @@
#include "ortools/base/timer.h"
#include "ortools/linear_solver/linear_solver.h"
#include "ortools/linear_solver/linear_solver.pb.h"
+#include "ortools/linear_solver/linear_solver_callback.h"
+#include "ortools/linear_solver/scip_callback.h"
#include "ortools/linear_solver/scip_helper_macros.h"
#include "ortools/linear_solver/scip_proto_solver.h"
#include "scip/cons_indicator.h"
#include "scip/scip.h"
+#include "scip/scip_param.h"
#include "scip/scip_prob.h"
#include "scip/scipdefplugins.h"
@@ -45,6 +48,12 @@ DEFINE_bool(scip_feasibility_emphasis, false,
"may not result in speedups in some problems.");
namespace operations_research {
+namespace {
+// See the class ScipConstraintHandlerForMPCallback below.
+struct EmptyStruct {};
+} // namespace
+
+class ScipConstraintHandlerForMPCallback;
class SCIPInterface : public MPSolverInterface {
public:
@@ -106,6 +115,33 @@ class SCIPInterface : public MPSolverInterface {
void* underlying_solver() override { return reinterpret_cast(scip_); }
+ // MULTIPLE SOLUTIONS SUPPORT
+ // The default behavior of scip is to store the top incidentally generated
+ // integer solutions in the solution pool. The default maximum size is 100.
+ // This can be adjusted by setting the param limits/maxsol. There is no way
+ // to ensure that the pool will actually be full.
+ //
+ // You can also ask SCIP to enumerate all feasible solutions. Combined with
+ // an equality or inequality constraint on the objective (after solving once
+ // to find the optimal solution), you can use this to find all high quality
+ // solutions. See https://scip.zib.de/doc/html/COUNTER.php. This behavior is
+ // not supported directly through MPSolver, but in theory can be controlled
+ // entirely through scip parameters.
+ bool NextSolution() override;
+
+ // CALLBACK SUPPORT:
+ // * We support MPSolver's callback API via MPCallback.
+ // See ./linear_solver_callback.h.
+ // * We also support SCIP's more general callback interface, built on
+ // 'constraint handlers'. See ./scip_callback.h and test, these are added
+ // directly to the underlying SCIP object, bypassing SCIPInterface.
+ // The former works by calling the latter. See go/scip-callbacks for
+ // a complete documentation of this design.
+
+ // MPCallback API
+ void SetCallback(MPCallback* mp_callback) override;
+ bool SupportsCallbacks() const override { return true; }
+
private:
void SetParameters(const MPSolverParameters& param) override;
void SetRelativeMipGap(double value) override;
@@ -132,6 +168,10 @@ class SCIPInterface : public MPSolverInterface {
MPSolverParameters::IntegerParam param) override;
void SetIntegerParamToUnsupportedValue(MPSolverParameters::IntegerParam param,
int value) override;
+ // How many solutions SCIP found.
+ int SolutionCount();
+ // Copy sol from SCIP to MPSolver.
+ void SetSolution(SCIP_SOL* solution);
util::Status CreateSCIP();
void DeleteSCIP();
@@ -147,7 +187,32 @@ class SCIPInterface : public MPSolverInterface {
SCIP* scip_;
std::vector scip_variables_;
std::vector scip_constraints_;
+ int current_solution_index_ = 0;
+ MPCallback* callback_ = nullptr;
+ std::unique_ptr scip_constraint_handler_;
+ // See ScipConstraintHandlerForMPCallback below.
+ EmptyStruct constraint_data_for_handler_;
bool branching_priority_reset_ = false;
+ bool callback_reset_ = false;
+};
+
+class ScipConstraintHandlerForMPCallback
+ : public ScipConstraintHandler {
+ public:
+ explicit ScipConstraintHandlerForMPCallback(MPCallback* mp_callback);
+
+ std::vector SeparateFractionalSolution(
+ const ScipConstraintHandlerContext& context, const EmptyStruct&) override;
+
+ std::vector SeparateIntegerSolution(
+ const ScipConstraintHandlerContext& context, const EmptyStruct&) override;
+
+ private:
+ std::vector SeparateSolution(
+ const ScipConstraintHandlerContext& context,
+ const bool at_integer_solution);
+
+ MPCallback* mp_callback_;
};
SCIPInterface::SCIPInterface(MPSolver* solver)
@@ -159,6 +224,7 @@ SCIPInterface::~SCIPInterface() { DeleteSCIP(); }
void SCIPInterface::Reset() {
DeleteSCIP();
+ scip_constraint_handler_.reset();
status_ = CreateSCIP();
ResetExtractionInformation();
}
@@ -576,9 +642,10 @@ MPSolver::ResultStatus SCIPInterface::Solve(const MPSolverParameters& param) {
// TODO(user): Is that still true now (2018) ?
if (param.GetIntegerParam(MPSolverParameters::INCREMENTALITY) ==
MPSolverParameters::INCREMENTALITY_OFF ||
- branching_priority_reset_) {
+ branching_priority_reset_ || callback_reset_) {
Reset();
branching_priority_reset_ = false;
+ callback_reset_ = false;
}
// Set log level.
@@ -595,6 +662,16 @@ MPSolver::ResultStatus SCIPInterface::Solve(const MPSolverParameters& param) {
ExtractModel();
VLOG(1) << absl::StrFormat("Model built in %s.",
absl::FormatDuration(timer.GetDuration()));
+ if (callback_ != nullptr) {
+ scip_constraint_handler_ =
+ absl::make_unique(callback_);
+ RegisterConstraintHandler(scip_constraint_handler_.get(),
+ scip_);
+ AddCallbackConstraint(scip_, scip_constraint_handler_.get(),
+ "mp_solver_callback_constraint_for_scip",
+ &constraint_data_for_handler_,
+ ScipCallbackConstraintOptions());
+ }
// Time limit.
if (solver_->time_limit() != 0) {
@@ -669,21 +746,12 @@ MPSolver::ResultStatus SCIPInterface::Solve(const MPSolverParameters& param) {
: SCIPsolve(scip_));
VLOG(1) << absl::StrFormat("Solved in %s.",
absl::FormatDuration(timer.GetDuration()));
-
+ current_solution_index_ = 0;
// Get the results.
SCIP_SOL* const solution = SCIPgetBestSol(scip_);
if (solution != nullptr) {
// If optimal or feasible solution is found.
- objective_value_ = SCIPgetSolOrigObj(scip_, solution);
- VLOG(1) << "objective=" << objective_value_;
- for (int i = 0; i < solver_->variables_.size(); ++i) {
- MPVariable* const var = solver_->variables_[i];
- const int var_index = var->index();
- const double val =
- SCIPgetSolVal(scip_, solution, scip_variables_[var_index]);
- var->set_solution_value(val);
- VLOG(3) << var->name() << "=" << val;
- }
+ SetSolution(solution);
} else {
VLOG(1) << "No feasible solution found.";
}
@@ -727,6 +795,19 @@ MPSolver::ResultStatus SCIPInterface::Solve(const MPSolverParameters& param) {
return result_status_;
}
+void SCIPInterface::SetSolution(SCIP_SOL* solution) {
+ objective_value_ = SCIPgetSolOrigObj(scip_, solution);
+ VLOG(1) << "objective=" << objective_value_;
+ for (int i = 0; i < solver_->variables_.size(); ++i) {
+ MPVariable* const var = solver_->variables_[i];
+ const int var_index = var->index();
+ const double val =
+ SCIPgetSolVal(scip_, solution, scip_variables_[var_index]);
+ var->set_solution_value(val);
+ VLOG(3) << var->name() << "=" << val;
+ }
+}
+
absl::optional SCIPInterface::DirectlySolveProto(
const MPModelRequest& request) {
// ScipSolveProto doesn't solve concurrently.
@@ -747,6 +828,22 @@ absl::optional SCIPInterface::DirectlySolveProto(
return response;
}
+int SCIPInterface::SolutionCount() { return SCIPgetNSols(scip_); }
+
+bool SCIPInterface::NextSolution() {
+ // Make sure we have successfully solved the problem and not modified it.
+ if (!CheckSolutionIsSynchronizedAndExists()) {
+ return false;
+ }
+ if (current_solution_index_ + 1 >= SolutionCount()) {
+ return false;
+ }
+ current_solution_index_++;
+ SCIP_SOL** all_solutions = SCIPgetSols(scip_);
+ SetSolution(all_solutions[current_solution_index_]);
+ return true;
+}
+
int64 SCIPInterface::iterations() const {
// NOTE(user): As of 2018-12 it doesn't run in the stubby server, and is
// a specialized call, so it's ok to crash if the status is broken.
@@ -912,8 +1009,115 @@ util::Status SCIPInterface::SetNumThreads(int num_threads) {
bool SCIPInterface::SetSolverSpecificParametersAsString(
const std::string& parameters) {
- return operations_research::ScipSetSolverSpecificParameters(parameters, scip_)
- .ok();
+ const util::Status s =
+ operations_research::ScipSetSolverSpecificParameters(parameters, scip_);
+ if (!s.ok()) {
+ LOG(WARNING) << "Failed to set SCIP parameter string: " << parameters
+ << ", error is: " << s;
+ }
+ return s.ok();
+}
+
+class ScipMPCallbackContext : public MPCallbackContext {
+ public:
+ ScipMPCallbackContext(const ScipConstraintHandlerContext* scip_context,
+ bool at_integer_solution)
+ : scip_context_(scip_context),
+ at_integer_solution_(at_integer_solution) {}
+
+ MPCallbackEvent Event() override {
+ if (at_integer_solution_) {
+ return MPCallbackEvent::kMipSolution;
+ }
+ return MPCallbackEvent::kMipNode;
+ }
+
+ bool CanQueryVariableValues() override {
+ return !scip_context_->is_pseudo_solution();
+ }
+
+ double VariableValue(const MPVariable* variable) override {
+ CHECK(CanQueryVariableValues());
+ return scip_context_->VariableValue(variable);
+ }
+
+ void AddCut(const LinearRange& cutting_plane) override {
+ CallbackRangeConstraint constraint;
+ constraint.is_cut = true;
+ constraint.range = cutting_plane;
+ constraint.local = false;
+ constraints_added_.push_back(std::move(constraint));
+ }
+
+ void AddLazyConstraint(const LinearRange& lazy_constraint) override {
+ CallbackRangeConstraint constraint;
+ constraint.is_cut = false;
+ constraint.range = lazy_constraint;
+ constraint.local = false;
+ constraints_added_.push_back(std::move(constraint));
+ }
+
+ double SuggestSolution(
+ const absl::flat_hash_map& solution) override {
+ LOG(FATAL) << "SuggestSolution() not currently supported for SCIP.";
+ }
+
+ int64 NumExploredNodes() override {
+ // scip_context_->NumNodesProcessed() returns:
+ // 0 before the root node is solved, e.g. if a heuristic finds a solution.
+ // 1 at the root node
+ // > 1 after the root node.
+ // The NumExploredNodes spec requires that we return 0 at the root node,
+ // (this is consistent with gurobi). Below is a bandaid to try and make the
+ // behavior consistent, although some information is lost.
+ return std::max(int64{0}, scip_context_->NumNodesProcessed() - 1);
+ }
+
+ const std::vector& constraints_added() {
+ return constraints_added_;
+ }
+
+ private:
+ const ScipConstraintHandlerContext* scip_context_;
+ bool at_integer_solution_;
+ // second value of pair is true for cuts and false for lazy constraints.
+ std::vector constraints_added_;
+};
+
+ScipConstraintHandlerForMPCallback::ScipConstraintHandlerForMPCallback(
+ MPCallback* mp_callback)
+ : ScipConstraintHandler(
+ {/*name=*/"mp_solver_constraint_handler",
+ /*description=*/
+ "A single constraint handler for all MPSolver models."}),
+ mp_callback_(mp_callback) {}
+
+std::vector
+ScipConstraintHandlerForMPCallback::SeparateFractionalSolution(
+ const ScipConstraintHandlerContext& context, const EmptyStruct&) {
+ return SeparateSolution(context, /*at_integer_solution=*/false);
+}
+
+std::vector
+ScipConstraintHandlerForMPCallback::SeparateIntegerSolution(
+ const ScipConstraintHandlerContext& context, const EmptyStruct&) {
+ return SeparateSolution(context, /*at_integer_solution=*/true);
+}
+
+std::vector
+ScipConstraintHandlerForMPCallback::SeparateSolution(
+ const ScipConstraintHandlerContext& context,
+ const bool at_integer_solution) {
+ ScipMPCallbackContext mp_context(&context, at_integer_solution);
+ mp_callback_->RunCallback(&mp_context);
+ return mp_context.constraints_added();
+}
+
+void SCIPInterface::SetCallback(MPCallback* mp_callback) {
+ if (callback_ != nullptr) {
+ callback_reset_ = true;
+ }
+ callback_ = mp_callback;
}
MPSolverInterface* BuildSCIPInterface(MPSolver* const solver) {
diff --git a/ortools/linear_solver/scip_proto_solver.cc b/ortools/linear_solver/scip_proto_solver.cc
index a26caf5fca..ccd1208db3 100644
--- a/ortools/linear_solver/scip_proto_solver.cc
+++ b/ortools/linear_solver/scip_proto_solver.cc
@@ -19,6 +19,7 @@
#include
#include
#include
+#include
#include
#include
@@ -458,6 +459,8 @@ util::Status AddMinMaxConstraint(const MPGeneralConstraintProto& gen_cst,
CHECK(gen_cst.has_min_constraint() || gen_cst.has_max_constraint());
const auto& minmax = gen_cst.has_min_constraint() ? gen_cst.min_constraint()
: gen_cst.max_constraint();
+ const std::set unique_var_indices(minmax.var_index().begin(),
+ minmax.var_index().end());
SCIP_VAR* scip_resultant_var = scip_variables[minmax.resultant_var_index()];
std::vector vars;
@@ -481,7 +484,7 @@ util::Status AddMinMaxConstraint(const MPGeneralConstraintProto& gen_cst,
};
// Create intermediary constraints such that y = xi
- for (const int var_index : minmax.var_index()) {
+ for (const int var_index : unique_var_indices) {
vars = {scip_resultant_var, scip_variables[var_index]};
vals = {1, -1};
RETURN_IF_ERROR(add_lin_constraint(absl::StrCat("_", var_index)));
@@ -506,7 +509,7 @@ util::Status AddMinMaxConstraint(const MPGeneralConstraintProto& gen_cst,
// Add all of the inequality constraints.
constexpr double kInfinity = std::numeric_limits::infinity();
cons.clear();
- for (const int var_index : minmax.var_index()) {
+ for (const int var_index : unique_var_indices) {
vars = {scip_resultant_var, scip_variables[var_index]};
vals = {1, -1};
if (gen_cst.has_min_constraint()) {
@@ -608,6 +611,7 @@ util::Status AddSolutionHint(const MPModelProto& model, SCIP* scip,
return util::OkStatus();
}
+} // namespace
// Returns "" iff the model seems valid for SCIP, else returns a human-readable
// error message. Assumes that FindErrorInMPModelProto(model) found no error.
@@ -674,6 +678,17 @@ std::string FindErrorInMPModelForScip(const MPModelProto& model, SCIP* scip) {
c, i);
}
}
+ for (int i = 0; i < cst.quadratic_constraint().qcoefficient_size();
+ ++i) {
+ const double qcoefficient =
+ cst.quadratic_constraint().qcoefficient(i);
+ if (qcoefficient >= infinity || qcoefficient <= -infinity) {
+ return absl::StrFormat(
+ "Quadratic constraint %d's quadratic coefficient #%d "
+ "considered infinite",
+ c, i);
+ }
+ }
break;
case MPGeneralConstraintProto::kMinConstraint:
if (cst.min_constraint().constant() >= infinity ||
@@ -723,7 +738,6 @@ std::string FindErrorInMPModelForScip(const MPModelProto& model, SCIP* scip) {
return "";
}
-} // namespace
util::StatusOr ScipSolveProto(
const MPModelRequest& request) {
diff --git a/ortools/linear_solver/scip_proto_solver.h b/ortools/linear_solver/scip_proto_solver.h
index 714b89cd20..a5e52925c3 100644
--- a/ortools/linear_solver/scip_proto_solver.h
+++ b/ortools/linear_solver/scip_proto_solver.h
@@ -23,9 +23,15 @@ namespace operations_research {
util::Status ScipSetSolverSpecificParameters(const std::string& parameters,
SCIP* scip);
+// Note, here we do not override any of SCIP default parameters. This behavior
+// *differs* from `MPSolver::Solve()` which sets the feasibility tolerance to
+// 1e-7, and the gap limit to 0.0001 (whereas SCIP defaults are 1e-6 and 0,
+// respectively, and they are being used here).
util::StatusOr ScipSolveProto(
const MPModelRequest& request);
+std::string FindErrorInMPModelForScip(const MPModelProto& model, SCIP* scip);
+
} // namespace operations_research
#endif // OR_TOOLS_LINEAR_SOLVER_SCIP_PROTO_SOLVER_H_
diff --git a/ortools/sat/python/cp_model.py b/ortools/sat/python/cp_model.py
index 9decfc2b13..a5336370a7 100644
--- a/ortools/sat/python/cp_model.py
+++ b/ortools/sat/python/cp_model.py
@@ -816,7 +816,7 @@ class CpModel(object):
if not variables:
raise ValueError('AddElement expects a non-empty variables array')
-
+
if isinstance(index, numbers.Integral):
return self.Add(list(variables)[index] == target)
diff --git a/ortools/sat/samples/BinPackingProblemSat.csproj b/ortools/sat/samples/BinPackingProblemSat.csproj
index 8ed4ab5ec9..ad50d78912 100644
--- a/ortools/sat/samples/BinPackingProblemSat.csproj
+++ b/ortools/sat/samples/BinPackingProblemSat.csproj
@@ -19,6 +19,6 @@
-
+
diff --git a/ortools/sat/samples/BoolOrSampleSat.csproj b/ortools/sat/samples/BoolOrSampleSat.csproj
index 7d9e114b13..3813dfa85f 100644
--- a/ortools/sat/samples/BoolOrSampleSat.csproj
+++ b/ortools/sat/samples/BoolOrSampleSat.csproj
@@ -19,6 +19,6 @@
-
+
diff --git a/ortools/sat/samples/ChannelingSampleSat.csproj b/ortools/sat/samples/ChannelingSampleSat.csproj
index 910589cdbb..0b4e3571ee 100644
--- a/ortools/sat/samples/ChannelingSampleSat.csproj
+++ b/ortools/sat/samples/ChannelingSampleSat.csproj
@@ -19,6 +19,6 @@
-
+
diff --git a/ortools/sat/samples/CpIsFunSat.csproj b/ortools/sat/samples/CpIsFunSat.csproj
index c345ef1856..f9049d2e6a 100644
--- a/ortools/sat/samples/CpIsFunSat.csproj
+++ b/ortools/sat/samples/CpIsFunSat.csproj
@@ -19,6 +19,6 @@
-
+
diff --git a/ortools/sat/samples/EarlinessTardinessCostSampleSat.csproj b/ortools/sat/samples/EarlinessTardinessCostSampleSat.csproj
index 7feac0fd8b..0523aca000 100644
--- a/ortools/sat/samples/EarlinessTardinessCostSampleSat.csproj
+++ b/ortools/sat/samples/EarlinessTardinessCostSampleSat.csproj
@@ -19,6 +19,6 @@
-
+
diff --git a/ortools/sat/samples/IntervalSampleSat.csproj b/ortools/sat/samples/IntervalSampleSat.csproj
index f144b1e334..88e9c57831 100644
--- a/ortools/sat/samples/IntervalSampleSat.csproj
+++ b/ortools/sat/samples/IntervalSampleSat.csproj
@@ -19,6 +19,6 @@
-
+
diff --git a/ortools/sat/samples/LiteralSampleSat.csproj b/ortools/sat/samples/LiteralSampleSat.csproj
index b3b905b469..3d19ce58ff 100644
--- a/ortools/sat/samples/LiteralSampleSat.csproj
+++ b/ortools/sat/samples/LiteralSampleSat.csproj
@@ -19,6 +19,6 @@
-
+
diff --git a/ortools/sat/samples/NoOverlapSampleSat.csproj b/ortools/sat/samples/NoOverlapSampleSat.csproj
index e12e57baf2..2b376716b0 100644
--- a/ortools/sat/samples/NoOverlapSampleSat.csproj
+++ b/ortools/sat/samples/NoOverlapSampleSat.csproj
@@ -19,6 +19,6 @@
-
+
diff --git a/ortools/sat/samples/OptionalIntervalSampleSat.csproj b/ortools/sat/samples/OptionalIntervalSampleSat.csproj
index 45a9a63872..26d666bfbc 100644
--- a/ortools/sat/samples/OptionalIntervalSampleSat.csproj
+++ b/ortools/sat/samples/OptionalIntervalSampleSat.csproj
@@ -19,6 +19,6 @@
-
+
diff --git a/ortools/sat/samples/RabbitsAndPheasantsSat.csproj b/ortools/sat/samples/RabbitsAndPheasantsSat.csproj
index 899018da9a..b72c7c6863 100644
--- a/ortools/sat/samples/RabbitsAndPheasantsSat.csproj
+++ b/ortools/sat/samples/RabbitsAndPheasantsSat.csproj
@@ -19,6 +19,6 @@
-
+
diff --git a/ortools/sat/samples/RankingSampleSat.csproj b/ortools/sat/samples/RankingSampleSat.csproj
index a96c637c27..39b472fef0 100644
--- a/ortools/sat/samples/RankingSampleSat.csproj
+++ b/ortools/sat/samples/RankingSampleSat.csproj
@@ -19,6 +19,6 @@
-
+
diff --git a/ortools/sat/samples/ReifiedSampleSat.csproj b/ortools/sat/samples/ReifiedSampleSat.csproj
index e722cc2d2b..158544c9d7 100644
--- a/ortools/sat/samples/ReifiedSampleSat.csproj
+++ b/ortools/sat/samples/ReifiedSampleSat.csproj
@@ -19,6 +19,6 @@
-
+
diff --git a/ortools/sat/samples/SearchForAllSolutionsSampleSat.csproj b/ortools/sat/samples/SearchForAllSolutionsSampleSat.csproj
index c6f5c289ce..9bbaf245a9 100644
--- a/ortools/sat/samples/SearchForAllSolutionsSampleSat.csproj
+++ b/ortools/sat/samples/SearchForAllSolutionsSampleSat.csproj
@@ -19,6 +19,6 @@
-
+
diff --git a/ortools/sat/samples/SimpleSatProgram.csproj b/ortools/sat/samples/SimpleSatProgram.csproj
index 97b9f02840..b752e76db5 100644
--- a/ortools/sat/samples/SimpleSatProgram.csproj
+++ b/ortools/sat/samples/SimpleSatProgram.csproj
@@ -19,6 +19,6 @@
-
+
diff --git a/ortools/sat/samples/SolutionHintingSampleSat.csproj b/ortools/sat/samples/SolutionHintingSampleSat.csproj
index 73abb65ef6..acaa0a53a7 100644
--- a/ortools/sat/samples/SolutionHintingSampleSat.csproj
+++ b/ortools/sat/samples/SolutionHintingSampleSat.csproj
@@ -19,6 +19,6 @@
-
+
diff --git a/ortools/sat/samples/SolveAndPrintIntermediateSolutionsSampleSat.csproj b/ortools/sat/samples/SolveAndPrintIntermediateSolutionsSampleSat.csproj
index 34e038ab53..c3c165725b 100644
--- a/ortools/sat/samples/SolveAndPrintIntermediateSolutionsSampleSat.csproj
+++ b/ortools/sat/samples/SolveAndPrintIntermediateSolutionsSampleSat.csproj
@@ -19,6 +19,6 @@
-
+
diff --git a/ortools/sat/samples/SolveWithTimeLimitSampleSat.csproj b/ortools/sat/samples/SolveWithTimeLimitSampleSat.csproj
index 3d70577c11..d5472d1bfc 100644
--- a/ortools/sat/samples/SolveWithTimeLimitSampleSat.csproj
+++ b/ortools/sat/samples/SolveWithTimeLimitSampleSat.csproj
@@ -19,6 +19,6 @@
-
+
diff --git a/ortools/sat/samples/StepFunctionSampleSat.csproj b/ortools/sat/samples/StepFunctionSampleSat.csproj
index 599ff01547..c4072ba91d 100644
--- a/ortools/sat/samples/StepFunctionSampleSat.csproj
+++ b/ortools/sat/samples/StepFunctionSampleSat.csproj
@@ -19,6 +19,6 @@
-
+
diff --git a/ortools/sat/samples/StopAfterNSolutionsSampleSat.csproj b/ortools/sat/samples/StopAfterNSolutionsSampleSat.csproj
index fb90bc4a6a..32397e9d10 100644
--- a/ortools/sat/samples/StopAfterNSolutionsSampleSat.csproj
+++ b/ortools/sat/samples/StopAfterNSolutionsSampleSat.csproj
@@ -19,6 +19,6 @@
-
+
diff --git a/ortools/util/csharp/vector.i b/ortools/util/csharp/vector.i
index 253b35cb0a..02d4b516ce 100644
--- a/ortools/util/csharp/vector.i
+++ b/ortools/util/csharp/vector.i
@@ -11,6 +11,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
+
%include "ortools/base/base.i"
%{