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 > + 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" %{