From 27121a1068a84c67ce8ca0494b542481244e578f Mon Sep 17 00:00:00 2001 From: Corentin Le Molgat Date: Wed, 4 Mar 2020 14:33:52 +0100 Subject: [PATCH] Update examples/notebook generated using ./tools/gen_all_notebook.sh --- examples/notebook/algorithms/knapsack.ipynb | 77 + .../algorithms/simple_knapsack_program.ipynb | 67 + .../assignment_with_constraints_sat.ipynb | 126 -- examples/notebook/balance_group_sat.ipynb | 89 - examples/notebook/code_samples_sat.ipynb | 473 ---- .../notebook/constraint_solver/cvrp.ipynb | 206 ++ .../constraint_solver/cvrp_reload.ipynb | 362 +++ .../notebook/constraint_solver/cvrptw.ipynb | 305 +++ .../constraint_solver/cvrptw_break.ipynb | 340 +++ .../constraint_solver/simple_cp_program.ipynb | 82 + .../simple_routing_program.ipynb | 102 + examples/notebook/constraint_solver/tsp.ipynb | 155 ++ .../constraint_solver/tsp_circuit_board.ipynb | 187 ++ .../constraint_solver/tsp_cities.ipynb | 133 ++ .../tsp_distance_matrix.ipynb | 188 ++ examples/notebook/constraint_solver/vrp.ipynb | 193 ++ .../constraint_solver/vrp_capacity.ipynb | 223 ++ .../constraint_solver/vrp_drop_nodes.ipynb | 236 ++ .../constraint_solver/vrp_global_span.ipynb | 207 ++ .../vrp_initial_routes.ipynb | 223 ++ .../vrp_pickup_delivery.ipynb | 227 ++ .../vrp_pickup_delivery_fifo.ipynb | 229 ++ .../vrp_pickup_delivery_lifo.ipynb | 229 ++ .../constraint_solver/vrp_resources.ipynb | 227 ++ .../constraint_solver/vrp_starts_ends.ipynb | 209 ++ .../constraint_solver/vrp_time_windows.ipynb | 196 ++ .../vrp_with_time_limit.ipynb | 130 ++ .../notebook/constraint_solver/vrpgs.ipynb | 174 ++ examples/notebook/contrib/3_jugs_mip.ipynb | 176 ++ .../notebook/contrib/3_jugs_regular.ipynb | 269 +++ .../notebook/contrib/a_round_of_golf.ipynb | 178 ++ examples/notebook/contrib/all_interval.ipynb | 125 ++ .../contrib/alldifferent_except_0.ipynb | 137 ++ examples/notebook/contrib/alphametic.ipynb | 166 ++ examples/notebook/contrib/assignment.ipynb | 135 ++ .../notebook/contrib/assignment6_mip.ipynb | 162 ++ examples/notebook/contrib/bacp.ipynb | 101 + examples/notebook/contrib/blending.ipynb | 152 ++ .../notebook/contrib/broken_weights.ipynb | 147 ++ examples/notebook/contrib/bus_schedule.ipynb | 118 + examples/notebook/contrib/car.ipynb | 149 ++ .../notebook/contrib/check_dependencies.ipynb | 58 + examples/notebook/contrib/circuit.ipynb | 140 ++ examples/notebook/contrib/coins3.ipynb | 115 + examples/notebook/contrib/coins_grid.ipynb | 129 ++ .../notebook/contrib/coins_grid_mip.ipynb | 113 + examples/notebook/contrib/coloring_ip.ipynb | 152 ++ .../contrib/combinatorial_auction2.ipynb | 119 + .../notebook/contrib/contiguity_regular.ipynb | 187 ++ examples/notebook/contrib/costas_array.ipynb | 178 ++ examples/notebook/contrib/covering_opl.ipynb | 153 ++ examples/notebook/contrib/crew.ipynb | 219 ++ examples/notebook/contrib/crossword2.ipynb | 207 ++ examples/notebook/contrib/crypta.ipynb | 126 ++ examples/notebook/contrib/crypto.ipynb | 148 ++ .../contrib/curious_set_of_integers.ipynb | 138 ++ .../notebook/contrib/debruijn_binary.ipynb | 199 ++ examples/notebook/contrib/diet1.ipynb | 112 + examples/notebook/contrib/diet1_b.ipynb | 122 + examples/notebook/contrib/diet1_mip.ipynb | 113 + .../contrib/discrete_tomography.ipynb | 170 ++ .../contrib/divisible_by_9_through_1.ipynb | 179 ++ examples/notebook/contrib/dudeney.ipynb | 57 + examples/notebook/contrib/einav_puzzle.ipynb | 210 ++ examples/notebook/contrib/einav_puzzle2.ipynb | 204 ++ examples/notebook/contrib/eq10.ipynb | 116 + examples/notebook/contrib/eq20.ipynb | 127 ++ examples/notebook/contrib/fill_a_pix.ipynb | 182 ++ .../notebook/contrib/furniture_moving.ipynb | 187 ++ examples/notebook/contrib/futoshiki.ipynb | 169 ++ .../notebook/contrib/game_theory_taha.ipynb | 121 + examples/notebook/contrib/grocery.ipynb | 104 + examples/notebook/contrib/hidato.ipynb | 218 ++ .../notebook/contrib/just_forgotten.ipynb | 125 ++ examples/notebook/contrib/kakuro.ipynb | 190 ++ examples/notebook/contrib/kenken2.ipynb | 205 ++ examples/notebook/contrib/killer_sudoku.ipynb | 200 ++ examples/notebook/contrib/knapsack_cp.ipynb | 102 + examples/notebook/contrib/knapsack_mip.ipynb | 112 + examples/notebook/contrib/labeled_dice.ipynb | 147 ++ examples/notebook/contrib/langford.ipynb | 119 + examples/notebook/contrib/least_diff.ipynb | 126 ++ examples/notebook/contrib/least_square.ipynb | 104 + examples/notebook/contrib/lectures.ipynb | 134 ++ .../notebook/contrib/magic_sequence_sat.ipynb | 65 + examples/notebook/contrib/magic_square.ipynb | 119 + .../contrib/magic_square_and_cards.ipynb | 120 + .../notebook/contrib/magic_square_mip.ipynb | 199 ++ examples/notebook/contrib/map.ipynb | 115 + examples/notebook/contrib/marathon2.ipynb | 145 ++ examples/notebook/contrib/max_flow_taha.ipynb | 131 ++ .../notebook/contrib/max_flow_winston1.ipynb | 149 ++ examples/notebook/contrib/minesweeper.ipynb | 235 ++ examples/notebook/contrib/mr_smith.ipynb | 131 ++ .../contrib/nonogram_default_search.ipynb | 209 ++ .../notebook/contrib/nonogram_regular.ipynb | 335 +++ .../notebook/contrib/nonogram_table.ipynb | 331 +++ .../notebook/contrib/nonogram_table2.ipynb | 231 ++ .../notebook/contrib/nontransitive_dice.ipynb | 217 ++ examples/notebook/contrib/nqueens.ipynb | 108 + examples/notebook/contrib/nqueens2.ipynb | 110 + examples/notebook/contrib/nqueens3.ipynb | 107 + .../notebook/contrib/nurse_rostering.ipynb | 278 +++ examples/notebook/contrib/nurses_cp.ipynb | 190 ++ examples/notebook/contrib/olympic.ipynb | 127 ++ examples/notebook/contrib/organize_day.ipynb | 126 ++ examples/notebook/contrib/p_median.ipynb | 133 ++ .../notebook/contrib/pandigital_numbers.ipynb | 168 ++ examples/notebook/contrib/photo_problem.ipynb | 164 ++ .../contrib/place_number_puzzle.ipynb | 111 + .../contrib/post_office_problem2.ipynb | 141 ++ examples/notebook/contrib/production.ipynb | 122 + .../contrib/project_scheduling_sat.ipynb | 74 + examples/notebook/contrib/pyls_api.ipynb | 134 ++ .../contrib/quasigroup_completion.ipynb | 212 ++ examples/notebook/contrib/regular.ipynb | 235 ++ examples/notebook/contrib/regular_table.ipynb | 228 ++ .../notebook/contrib/regular_table2.ipynb | 212 ++ examples/notebook/contrib/rogo2.ipynb | 199 ++ .../contrib/rostering_with_travel.ipynb | 127 ++ examples/notebook/contrib/safe_cracking.ipynb | 115 + .../contrib/scheduling_speakers.ipynb | 103 + .../scheduling_with_transitions_sat.ipynb | 347 +++ .../{ => contrib}/school_scheduling_sat.ipynb | 6 +- examples/notebook/contrib/secret_santa.ipynb | 128 ++ examples/notebook/contrib/secret_santa2.ipynb | 227 ++ .../contrib/send_more_money_any_base.ipynb | 117 + .../notebook/contrib/send_most_money.ipynb | 126 ++ examples/notebook/contrib/seseman.ipynb | 147 ++ examples/notebook/contrib/seseman_b.ipynb | 143 ++ examples/notebook/contrib/set_covering.ipynb | 103 + examples/notebook/contrib/set_covering2.ipynb | 106 + examples/notebook/contrib/set_covering3.ipynb | 134 ++ examples/notebook/contrib/set_covering4.ipynb | 162 ++ .../contrib/set_covering_deployment.ipynb | 143 ++ .../contrib/set_covering_skiena.ipynb | 130 ++ examples/notebook/contrib/set_partition.ipynb | 180 ++ .../notebook/contrib/sicherman_dice.ipynb | 150 ++ .../notebook/contrib/ski_assignment.ipynb | 131 ++ examples/notebook/contrib/slitherlink.ipynb | 299 +++ .../contrib/sports_schedule_sat.ipynb | 575 +++++ .../notebook/contrib/stable_marriage.ipynb | 195 ++ .../contrib/stable_marriage_sat.ipynb | 112 + examples/notebook/contrib/steel.ipynb | 200 ++ examples/notebook/contrib/steel_lns.ipynb | 272 +++ examples/notebook/contrib/stigler.ipynb | 355 +++ examples/notebook/contrib/strimko2.ipynb | 149 ++ examples/notebook/contrib/subset_sum.ipynb | 118 + examples/notebook/contrib/survo_puzzle.ipynb | 180 ++ examples/notebook/contrib/toNum.ipynb | 95 + .../notebook/contrib/traffic_lights.ipynb | 143 ++ .../notebook/contrib/vendor_scheduling.ipynb | 112 + examples/notebook/contrib/volsay.ipynb | 81 + examples/notebook/contrib/volsay2.ipynb | 89 + examples/notebook/contrib/volsay3.ipynb | 102 + .../contrib/wedding_optimal_chart.ipynb | 173 ++ .../notebook/contrib/who_killed_agatha.ipynb | 221 ++ examples/notebook/contrib/xkcd.ipynb | 121 + .../notebook/contrib/young_tableaux.ipynb | 172 ++ examples/notebook/cp_is_fun_sat.ipynb | 104 - examples/notebook/examples/appointments.ipynb | 176 ++ .../examples/arc_flow_cutting_stock_sat.ipynb | 286 +++ .../{ => examples}/assignment_sat.ipynb | 50 +- .../assignment_with_constraints_sat.ipynb | 136 ++ .../notebook/examples/balance_group_sat.ipynb | 190 ++ .../bus_driver_scheduling_flow_sat.ipynb | 1837 +++++++++++++++ .../examples/bus_driver_scheduling_sat.ipynb | 1972 +++++++++++++++++ .../examples/chemical_balance_lp.ipynb | 108 + .../{ => examples}/chemical_balance_sat.ipynb | 72 +- .../notebook/examples/clustering_sat.ipynb | 142 ++ .../examples/cover_rectangle_sat.ipynb | 126 ++ examples/notebook/examples/cvrptw_plot.ipynb | 765 +++++++ .../examples/flexible_job_shop_sat.ipynb | 194 ++ .../examples/gate_scheduling_sat.ipynb | 147 ++ examples/notebook/examples/golomb8.ipynb | 86 + examples/notebook/examples/hidato_sat.ipynb | 207 ++ .../examples/integer_programming.ipynb | 153 ++ .../examples/jobshop_ft06_distance_sat.ipynb | 146 ++ .../notebook/examples/jobshop_ft06_sat.ipynb | 118 + .../jobshop_with_maintenance_sat.ipynb | 147 ++ .../examples/linear_assignment_api.ipynb | 68 + .../examples/linear_programming.ipynb | 96 + .../examples/magic_sequence_distribute.ipynb | 56 + .../notebook/{ => examples}/nqueens_sat.ipynb | 66 +- .../notebook/examples/pyflow_example.ipynb | 91 + examples/notebook/examples/qubo_sat.ipynb | 600 +++++ examples/notebook/examples/random_tsp.ipynb | 159 ++ examples/notebook/examples/rcpsp_sat.ipynb | 299 +++ .../notebook/examples/reallocate_sat.ipynb | 148 ++ .../examples/shift_scheduling_sat.ipynb | 437 ++++ ...ing_with_setup_release_due_dates_sat.ipynb | 289 +++ .../examples/steel_mill_slab_sat.ipynb | 780 +++++++ examples/notebook/examples/stigler_diet.ipynb | 239 ++ examples/notebook/examples/sudoku_sat.ipynb | 90 + .../examples/task_allocation_sat.ipynb | 298 +++ .../tasks_and_workers_assignment_sat.ipynb | 136 ++ examples/notebook/examples/transit_time.ipynb | 237 ++ examples/notebook/examples/tsp_sat.ipynb | 117 + .../examples/vendor_scheduling_sat.ipynb | 155 ++ .../examples/wedding_optimal_chart_sat.ipynb | 226 ++ .../examples/worker_schedule_sat.ipynb | 172 ++ examples/notebook/examples/zebra_sat.ipynb | 143 ++ examples/notebook/flexible_job_shop_sat.ipynb | 187 -- examples/notebook/gate_scheduling_sat.ipynb | 145 -- .../graph/simple_max_flow_program.ipynb | 71 + .../graph/simple_min_cost_flow_program.ipynb | 83 + examples/notebook/hidato_sat.ipynb | 198 -- examples/notebook/jobshop_ft06_sat.ipynb | 99 - .../integer_programming_example.ipynb | 94 + .../linear_programming_example.ipynb | 95 + .../linear_solver/mip_var_array.ipynb | 114 + .../linear_solver/simple_lp_program.ipynb | 77 + .../linear_solver/simple_mip_program.ipynb | 86 + examples/notebook/nurses_sat.ipynb | 142 -- examples/notebook/rcpsp_sat.ipynb | 286 --- .../notebook/sat/binpacking_problem_sat.ipynb | 95 + .../notebook/sat/bool_or_sample_sat.ipynb | 47 + .../sat/boolean_product_sample_sat.ipynb | 61 + .../notebook/sat/channeling_sample_sat.ipynb | 94 + examples/notebook/sat/cp_is_fun_sat.ipynb | 113 + .../earliness_tardiness_cost_sample_sat.ipynb | 108 + .../notebook/sat/interval_sample_sat.ipynb | 52 + .../notebook/sat/literal_sample_sat.ipynb | 46 + .../notebook/sat/minimal_jobshop_sat.ipynb | 159 ++ .../notebook/sat/multiple_knapsack_sat.ipynb | 139 ++ .../notebook/sat/no_overlap_sample_sat.ipynb | 88 + examples/notebook/sat/nurses_sat.ipynb | 147 ++ .../sat/optional_interval_sample_sat.ipynb | 54 + .../sat/rabbits_and_pheasants_sat.ipynb | 59 + .../notebook/sat/ranking_sample_sat.ipynb | 173 ++ .../notebook/sat/reified_sample_sat.ipynb | 58 + .../notebook/sat/schedule_requests_sat.ipynb | 130 ++ .../scheduling_with_calendar_sample_sat.ipynb | 98 + .../search_for_all_solutions_sample_sat.ipynb | 91 + .../notebook/sat/simple_sat_program.ipynb | 72 + .../sat/solution_hinting_sample_sat.ipynb | 79 + ...nt_intermediate_solutions_sample_sat.ipynb | 98 + .../solve_with_time_limit_sample_sat.ipynb | 64 + .../sat/step_function_sample_sat.ipynb | 112 + .../stop_after_n_solutions_sample_sat.ipynb | 79 + examples/notebook/steel_mill_slab_sat.ipynb | 788 ------- examples/notebook/vendor_scheduling_sat.ipynb | 151 -- .../notebook/wedding_optimal_chart_sat.ipynb | 224 -- examples/notebook/worker_schedule_sat.ipynb | 162 -- 244 files changed, 40558 insertions(+), 3270 deletions(-) create mode 100644 examples/notebook/algorithms/knapsack.ipynb create mode 100644 examples/notebook/algorithms/simple_knapsack_program.ipynb delete mode 100644 examples/notebook/assignment_with_constraints_sat.ipynb delete mode 100644 examples/notebook/balance_group_sat.ipynb delete mode 100644 examples/notebook/code_samples_sat.ipynb create mode 100644 examples/notebook/constraint_solver/cvrp.ipynb create mode 100644 examples/notebook/constraint_solver/cvrp_reload.ipynb create mode 100644 examples/notebook/constraint_solver/cvrptw.ipynb create mode 100644 examples/notebook/constraint_solver/cvrptw_break.ipynb create mode 100644 examples/notebook/constraint_solver/simple_cp_program.ipynb create mode 100644 examples/notebook/constraint_solver/simple_routing_program.ipynb create mode 100644 examples/notebook/constraint_solver/tsp.ipynb create mode 100644 examples/notebook/constraint_solver/tsp_circuit_board.ipynb create mode 100644 examples/notebook/constraint_solver/tsp_cities.ipynb create mode 100644 examples/notebook/constraint_solver/tsp_distance_matrix.ipynb create mode 100644 examples/notebook/constraint_solver/vrp.ipynb create mode 100644 examples/notebook/constraint_solver/vrp_capacity.ipynb create mode 100644 examples/notebook/constraint_solver/vrp_drop_nodes.ipynb create mode 100644 examples/notebook/constraint_solver/vrp_global_span.ipynb create mode 100644 examples/notebook/constraint_solver/vrp_initial_routes.ipynb create mode 100644 examples/notebook/constraint_solver/vrp_pickup_delivery.ipynb create mode 100644 examples/notebook/constraint_solver/vrp_pickup_delivery_fifo.ipynb create mode 100644 examples/notebook/constraint_solver/vrp_pickup_delivery_lifo.ipynb create mode 100644 examples/notebook/constraint_solver/vrp_resources.ipynb create mode 100644 examples/notebook/constraint_solver/vrp_starts_ends.ipynb create mode 100644 examples/notebook/constraint_solver/vrp_time_windows.ipynb create mode 100644 examples/notebook/constraint_solver/vrp_with_time_limit.ipynb create mode 100644 examples/notebook/constraint_solver/vrpgs.ipynb create mode 100644 examples/notebook/contrib/3_jugs_mip.ipynb create mode 100644 examples/notebook/contrib/3_jugs_regular.ipynb create mode 100644 examples/notebook/contrib/a_round_of_golf.ipynb create mode 100644 examples/notebook/contrib/all_interval.ipynb create mode 100644 examples/notebook/contrib/alldifferent_except_0.ipynb create mode 100644 examples/notebook/contrib/alphametic.ipynb create mode 100644 examples/notebook/contrib/assignment.ipynb create mode 100644 examples/notebook/contrib/assignment6_mip.ipynb create mode 100644 examples/notebook/contrib/bacp.ipynb create mode 100644 examples/notebook/contrib/blending.ipynb create mode 100644 examples/notebook/contrib/broken_weights.ipynb create mode 100644 examples/notebook/contrib/bus_schedule.ipynb create mode 100644 examples/notebook/contrib/car.ipynb create mode 100644 examples/notebook/contrib/check_dependencies.ipynb create mode 100644 examples/notebook/contrib/circuit.ipynb create mode 100644 examples/notebook/contrib/coins3.ipynb create mode 100644 examples/notebook/contrib/coins_grid.ipynb create mode 100644 examples/notebook/contrib/coins_grid_mip.ipynb create mode 100644 examples/notebook/contrib/coloring_ip.ipynb create mode 100644 examples/notebook/contrib/combinatorial_auction2.ipynb create mode 100644 examples/notebook/contrib/contiguity_regular.ipynb create mode 100644 examples/notebook/contrib/costas_array.ipynb create mode 100644 examples/notebook/contrib/covering_opl.ipynb create mode 100644 examples/notebook/contrib/crew.ipynb create mode 100644 examples/notebook/contrib/crossword2.ipynb create mode 100644 examples/notebook/contrib/crypta.ipynb create mode 100644 examples/notebook/contrib/crypto.ipynb create mode 100644 examples/notebook/contrib/curious_set_of_integers.ipynb create mode 100644 examples/notebook/contrib/debruijn_binary.ipynb create mode 100644 examples/notebook/contrib/diet1.ipynb create mode 100644 examples/notebook/contrib/diet1_b.ipynb create mode 100644 examples/notebook/contrib/diet1_mip.ipynb create mode 100644 examples/notebook/contrib/discrete_tomography.ipynb create mode 100644 examples/notebook/contrib/divisible_by_9_through_1.ipynb create mode 100644 examples/notebook/contrib/dudeney.ipynb create mode 100644 examples/notebook/contrib/einav_puzzle.ipynb create mode 100644 examples/notebook/contrib/einav_puzzle2.ipynb create mode 100644 examples/notebook/contrib/eq10.ipynb create mode 100644 examples/notebook/contrib/eq20.ipynb create mode 100644 examples/notebook/contrib/fill_a_pix.ipynb create mode 100644 examples/notebook/contrib/furniture_moving.ipynb create mode 100644 examples/notebook/contrib/futoshiki.ipynb create mode 100644 examples/notebook/contrib/game_theory_taha.ipynb create mode 100644 examples/notebook/contrib/grocery.ipynb create mode 100644 examples/notebook/contrib/hidato.ipynb create mode 100644 examples/notebook/contrib/just_forgotten.ipynb create mode 100644 examples/notebook/contrib/kakuro.ipynb create mode 100644 examples/notebook/contrib/kenken2.ipynb create mode 100644 examples/notebook/contrib/killer_sudoku.ipynb create mode 100644 examples/notebook/contrib/knapsack_cp.ipynb create mode 100644 examples/notebook/contrib/knapsack_mip.ipynb create mode 100644 examples/notebook/contrib/labeled_dice.ipynb create mode 100644 examples/notebook/contrib/langford.ipynb create mode 100644 examples/notebook/contrib/least_diff.ipynb create mode 100644 examples/notebook/contrib/least_square.ipynb create mode 100644 examples/notebook/contrib/lectures.ipynb create mode 100644 examples/notebook/contrib/magic_sequence_sat.ipynb create mode 100644 examples/notebook/contrib/magic_square.ipynb create mode 100644 examples/notebook/contrib/magic_square_and_cards.ipynb create mode 100644 examples/notebook/contrib/magic_square_mip.ipynb create mode 100644 examples/notebook/contrib/map.ipynb create mode 100644 examples/notebook/contrib/marathon2.ipynb create mode 100644 examples/notebook/contrib/max_flow_taha.ipynb create mode 100644 examples/notebook/contrib/max_flow_winston1.ipynb create mode 100644 examples/notebook/contrib/minesweeper.ipynb create mode 100644 examples/notebook/contrib/mr_smith.ipynb create mode 100644 examples/notebook/contrib/nonogram_default_search.ipynb create mode 100644 examples/notebook/contrib/nonogram_regular.ipynb create mode 100644 examples/notebook/contrib/nonogram_table.ipynb create mode 100644 examples/notebook/contrib/nonogram_table2.ipynb create mode 100644 examples/notebook/contrib/nontransitive_dice.ipynb create mode 100644 examples/notebook/contrib/nqueens.ipynb create mode 100644 examples/notebook/contrib/nqueens2.ipynb create mode 100644 examples/notebook/contrib/nqueens3.ipynb create mode 100644 examples/notebook/contrib/nurse_rostering.ipynb create mode 100644 examples/notebook/contrib/nurses_cp.ipynb create mode 100644 examples/notebook/contrib/olympic.ipynb create mode 100644 examples/notebook/contrib/organize_day.ipynb create mode 100644 examples/notebook/contrib/p_median.ipynb create mode 100644 examples/notebook/contrib/pandigital_numbers.ipynb create mode 100644 examples/notebook/contrib/photo_problem.ipynb create mode 100644 examples/notebook/contrib/place_number_puzzle.ipynb create mode 100644 examples/notebook/contrib/post_office_problem2.ipynb create mode 100644 examples/notebook/contrib/production.ipynb create mode 100644 examples/notebook/contrib/project_scheduling_sat.ipynb create mode 100644 examples/notebook/contrib/pyls_api.ipynb create mode 100644 examples/notebook/contrib/quasigroup_completion.ipynb create mode 100644 examples/notebook/contrib/regular.ipynb create mode 100644 examples/notebook/contrib/regular_table.ipynb create mode 100644 examples/notebook/contrib/regular_table2.ipynb create mode 100644 examples/notebook/contrib/rogo2.ipynb create mode 100644 examples/notebook/contrib/rostering_with_travel.ipynb create mode 100644 examples/notebook/contrib/safe_cracking.ipynb create mode 100644 examples/notebook/contrib/scheduling_speakers.ipynb create mode 100644 examples/notebook/contrib/scheduling_with_transitions_sat.ipynb rename examples/notebook/{ => contrib}/school_scheduling_sat.ipynb (97%) create mode 100644 examples/notebook/contrib/secret_santa.ipynb create mode 100644 examples/notebook/contrib/secret_santa2.ipynb create mode 100644 examples/notebook/contrib/send_more_money_any_base.ipynb create mode 100644 examples/notebook/contrib/send_most_money.ipynb create mode 100644 examples/notebook/contrib/seseman.ipynb create mode 100644 examples/notebook/contrib/seseman_b.ipynb create mode 100644 examples/notebook/contrib/set_covering.ipynb create mode 100644 examples/notebook/contrib/set_covering2.ipynb create mode 100644 examples/notebook/contrib/set_covering3.ipynb create mode 100644 examples/notebook/contrib/set_covering4.ipynb create mode 100644 examples/notebook/contrib/set_covering_deployment.ipynb create mode 100644 examples/notebook/contrib/set_covering_skiena.ipynb create mode 100644 examples/notebook/contrib/set_partition.ipynb create mode 100644 examples/notebook/contrib/sicherman_dice.ipynb create mode 100644 examples/notebook/contrib/ski_assignment.ipynb create mode 100644 examples/notebook/contrib/slitherlink.ipynb create mode 100644 examples/notebook/contrib/sports_schedule_sat.ipynb create mode 100644 examples/notebook/contrib/stable_marriage.ipynb create mode 100644 examples/notebook/contrib/stable_marriage_sat.ipynb create mode 100644 examples/notebook/contrib/steel.ipynb create mode 100644 examples/notebook/contrib/steel_lns.ipynb create mode 100644 examples/notebook/contrib/stigler.ipynb create mode 100644 examples/notebook/contrib/strimko2.ipynb create mode 100644 examples/notebook/contrib/subset_sum.ipynb create mode 100644 examples/notebook/contrib/survo_puzzle.ipynb create mode 100644 examples/notebook/contrib/toNum.ipynb create mode 100644 examples/notebook/contrib/traffic_lights.ipynb create mode 100644 examples/notebook/contrib/vendor_scheduling.ipynb create mode 100644 examples/notebook/contrib/volsay.ipynb create mode 100644 examples/notebook/contrib/volsay2.ipynb create mode 100644 examples/notebook/contrib/volsay3.ipynb create mode 100644 examples/notebook/contrib/wedding_optimal_chart.ipynb create mode 100644 examples/notebook/contrib/who_killed_agatha.ipynb create mode 100644 examples/notebook/contrib/xkcd.ipynb create mode 100644 examples/notebook/contrib/young_tableaux.ipynb delete mode 100644 examples/notebook/cp_is_fun_sat.ipynb create mode 100644 examples/notebook/examples/appointments.ipynb create mode 100644 examples/notebook/examples/arc_flow_cutting_stock_sat.ipynb rename examples/notebook/{ => examples}/assignment_sat.ipynb (62%) create mode 100644 examples/notebook/examples/assignment_with_constraints_sat.ipynb create mode 100644 examples/notebook/examples/balance_group_sat.ipynb create mode 100644 examples/notebook/examples/bus_driver_scheduling_flow_sat.ipynb create mode 100644 examples/notebook/examples/bus_driver_scheduling_sat.ipynb create mode 100644 examples/notebook/examples/chemical_balance_lp.ipynb rename examples/notebook/{ => examples}/chemical_balance_sat.ipynb (50%) create mode 100644 examples/notebook/examples/clustering_sat.ipynb create mode 100644 examples/notebook/examples/cover_rectangle_sat.ipynb create mode 100644 examples/notebook/examples/cvrptw_plot.ipynb create mode 100644 examples/notebook/examples/flexible_job_shop_sat.ipynb create mode 100644 examples/notebook/examples/gate_scheduling_sat.ipynb create mode 100644 examples/notebook/examples/golomb8.ipynb create mode 100644 examples/notebook/examples/hidato_sat.ipynb create mode 100644 examples/notebook/examples/integer_programming.ipynb create mode 100644 examples/notebook/examples/jobshop_ft06_distance_sat.ipynb create mode 100644 examples/notebook/examples/jobshop_ft06_sat.ipynb create mode 100644 examples/notebook/examples/jobshop_with_maintenance_sat.ipynb create mode 100644 examples/notebook/examples/linear_assignment_api.ipynb create mode 100644 examples/notebook/examples/linear_programming.ipynb create mode 100644 examples/notebook/examples/magic_sequence_distribute.ipynb rename examples/notebook/{ => examples}/nqueens_sat.ipynb (59%) create mode 100644 examples/notebook/examples/pyflow_example.ipynb create mode 100644 examples/notebook/examples/qubo_sat.ipynb create mode 100644 examples/notebook/examples/random_tsp.ipynb create mode 100644 examples/notebook/examples/rcpsp_sat.ipynb create mode 100644 examples/notebook/examples/reallocate_sat.ipynb create mode 100644 examples/notebook/examples/shift_scheduling_sat.ipynb create mode 100644 examples/notebook/examples/single_machine_scheduling_with_setup_release_due_dates_sat.ipynb create mode 100644 examples/notebook/examples/steel_mill_slab_sat.ipynb create mode 100644 examples/notebook/examples/stigler_diet.ipynb create mode 100644 examples/notebook/examples/sudoku_sat.ipynb create mode 100644 examples/notebook/examples/task_allocation_sat.ipynb create mode 100644 examples/notebook/examples/tasks_and_workers_assignment_sat.ipynb create mode 100644 examples/notebook/examples/transit_time.ipynb create mode 100644 examples/notebook/examples/tsp_sat.ipynb create mode 100644 examples/notebook/examples/vendor_scheduling_sat.ipynb create mode 100644 examples/notebook/examples/wedding_optimal_chart_sat.ipynb create mode 100644 examples/notebook/examples/worker_schedule_sat.ipynb create mode 100644 examples/notebook/examples/zebra_sat.ipynb delete mode 100644 examples/notebook/flexible_job_shop_sat.ipynb delete mode 100644 examples/notebook/gate_scheduling_sat.ipynb create mode 100644 examples/notebook/graph/simple_max_flow_program.ipynb create mode 100644 examples/notebook/graph/simple_min_cost_flow_program.ipynb delete mode 100644 examples/notebook/hidato_sat.ipynb delete mode 100644 examples/notebook/jobshop_ft06_sat.ipynb create mode 100644 examples/notebook/linear_solver/integer_programming_example.ipynb create mode 100644 examples/notebook/linear_solver/linear_programming_example.ipynb create mode 100644 examples/notebook/linear_solver/mip_var_array.ipynb create mode 100644 examples/notebook/linear_solver/simple_lp_program.ipynb create mode 100644 examples/notebook/linear_solver/simple_mip_program.ipynb delete mode 100644 examples/notebook/nurses_sat.ipynb delete mode 100644 examples/notebook/rcpsp_sat.ipynb create mode 100644 examples/notebook/sat/binpacking_problem_sat.ipynb create mode 100644 examples/notebook/sat/bool_or_sample_sat.ipynb create mode 100644 examples/notebook/sat/boolean_product_sample_sat.ipynb create mode 100644 examples/notebook/sat/channeling_sample_sat.ipynb create mode 100644 examples/notebook/sat/cp_is_fun_sat.ipynb create mode 100644 examples/notebook/sat/earliness_tardiness_cost_sample_sat.ipynb create mode 100644 examples/notebook/sat/interval_sample_sat.ipynb create mode 100644 examples/notebook/sat/literal_sample_sat.ipynb create mode 100644 examples/notebook/sat/minimal_jobshop_sat.ipynb create mode 100644 examples/notebook/sat/multiple_knapsack_sat.ipynb create mode 100644 examples/notebook/sat/no_overlap_sample_sat.ipynb create mode 100644 examples/notebook/sat/nurses_sat.ipynb create mode 100644 examples/notebook/sat/optional_interval_sample_sat.ipynb create mode 100644 examples/notebook/sat/rabbits_and_pheasants_sat.ipynb create mode 100644 examples/notebook/sat/ranking_sample_sat.ipynb create mode 100644 examples/notebook/sat/reified_sample_sat.ipynb create mode 100644 examples/notebook/sat/schedule_requests_sat.ipynb create mode 100644 examples/notebook/sat/scheduling_with_calendar_sample_sat.ipynb create mode 100644 examples/notebook/sat/search_for_all_solutions_sample_sat.ipynb create mode 100644 examples/notebook/sat/simple_sat_program.ipynb create mode 100644 examples/notebook/sat/solution_hinting_sample_sat.ipynb create mode 100644 examples/notebook/sat/solve_and_print_intermediate_solutions_sample_sat.ipynb create mode 100644 examples/notebook/sat/solve_with_time_limit_sample_sat.ipynb create mode 100644 examples/notebook/sat/step_function_sample_sat.ipynb create mode 100644 examples/notebook/sat/stop_after_n_solutions_sample_sat.ipynb delete mode 100644 examples/notebook/steel_mill_slab_sat.ipynb delete mode 100644 examples/notebook/vendor_scheduling_sat.ipynb delete mode 100644 examples/notebook/wedding_optimal_chart_sat.ipynb delete mode 100644 examples/notebook/worker_schedule_sat.ipynb diff --git a/examples/notebook/algorithms/knapsack.ipynb b/examples/notebook/algorithms/knapsack.ipynb new file mode 100644 index 0000000000..44ecfb819c --- /dev/null +++ b/examples/notebook/algorithms/knapsack.ipynb @@ -0,0 +1,77 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010-2018 Google LLC\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"A simple knapsack problem.\"\"\"\n", + "# [START program]\n", + "# [START import]\n", + "from __future__ import print_function\n", + "from ortools.algorithms import pywrapknapsack_solver\n", + "# [END import]\n", + "\n", + "\n", + "# Create the solver.\n", + "# [START solver]\n", + "solver = pywrapknapsack_solver.KnapsackSolver(\n", + " pywrapknapsack_solver.KnapsackSolver.\n", + " KNAPSACK_MULTIDIMENSION_BRANCH_AND_BOUND_SOLVER, 'KnapsackExample')\n", + "# [END solver]\n", + "\n", + "# [START data]\n", + "values = [\n", + " 360, 83, 59, 130, 431, 67, 230, 52, 93, 125, 670, 892, 600, 38, 48, 147,\n", + " 78, 256, 63, 17, 120, 164, 432, 35, 92, 110, 22, 42, 50, 323, 514, 28,\n", + " 87, 73, 78, 15, 26, 78, 210, 36, 85, 189, 274, 43, 33, 10, 19, 389, 276,\n", + " 312\n", + "]\n", + "weights = [[\n", + " 7, 0, 30, 22, 80, 94, 11, 81, 70, 64, 59, 18, 0, 36, 3, 8, 15, 42, 9, 0,\n", + " 42, 47, 52, 32, 26, 48, 55, 6, 29, 84, 2, 4, 18, 56, 7, 29, 93, 44, 71,\n", + " 3, 86, 66, 31, 65, 0, 79, 20, 65, 52, 13\n", + "]]\n", + "capacities = [850]\n", + "# [END data]\n", + "\n", + "# [START solve]\n", + "solver.Init(values, weights, capacities)\n", + "computed_value = solver.Solve()\n", + "# [END solve]\n", + "\n", + "# [START print_solution]\n", + "packed_items = []\n", + "packed_weights = []\n", + "total_weight = 0\n", + "print('Total value =', computed_value)\n", + "for i in range(len(values)):\n", + " if solver.BestSolutionContains(i):\n", + " packed_items.append(i)\n", + " packed_weights.append(weights[0][i])\n", + " total_weight += weights[0][i]\n", + "print('Total weight:', total_weight)\n", + "print('Packed items:', packed_items)\n", + "print('Packed_weights:', packed_weights)\n", + "# [END print_solution]\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/algorithms/simple_knapsack_program.ipynb b/examples/notebook/algorithms/simple_knapsack_program.ipynb new file mode 100644 index 0000000000..8ab7083197 --- /dev/null +++ b/examples/notebook/algorithms/simple_knapsack_program.ipynb @@ -0,0 +1,67 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010-2018 Google LLC\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "# [START program]\n", + "\"\"\"A simple knapsack problem.\"\"\"\n", + "# [START import]\n", + "from __future__ import print_function\n", + "from ortools.algorithms import pywrapknapsack_solver\n", + "# [END import]\n", + "\n", + "\n", + "# Create the solver.\n", + "# [START solver]\n", + "solver = pywrapknapsack_solver.KnapsackSolver(\n", + " pywrapknapsack_solver.KnapsackSolver.\n", + " KNAPSACK_DYNAMIC_PROGRAMMING_SOLVER, \"test\")\n", + "# [END solver]\n", + "\n", + "# [START data]\n", + "weights = [[\n", + " 565, 406, 194, 130, 435, 367, 230, 315, 393, 125, 670, 892, 600, 293,\n", + " 712, 147, 421, 255\n", + "]]\n", + "capacities = [850]\n", + "values = weights[0]\n", + "# [END data]\n", + "\n", + "# [START solve]\n", + "solver.Init(values, weights, capacities)\n", + "computed_value = solver.Solve()\n", + "# [END solve]\n", + "\n", + "# [START print_solution]\n", + "packed_items = [\n", + " x for x in range(0, len(weights[0])) if solver.BestSolutionContains(x)\n", + "]\n", + "packed_weights = [weights[0][i] for i in packed_items]\n", + "\n", + "print(\"Packed items: \", packed_items)\n", + "print(\"Packed weights: \", packed_weights)\n", + "print(\"Total weight (same as total value): \", computed_value)\n", + "# [END print_solution]\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/assignment_with_constraints_sat.ipynb b/examples/notebook/assignment_with_constraints_sat.ipynb deleted file mode 100644 index eabebb55aa..0000000000 --- a/examples/notebook/assignment_with_constraints_sat.ipynb +++ /dev/null @@ -1,126 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Copyright 2010-2017 Google\n", - "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", - "# you may not use this file except in compliance with the License.\n", - "# You may obtain a copy of the License at\n", - "#\n", - "# http://www.apache.org/licenses/LICENSE-2.0\n", - "#\n", - "# Unless required by applicable law or agreed to in writing, software\n", - "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", - "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", - "# See the License for the specific language governing permissions and\n", - "# limitations under the License.\n", - "from __future__ import print_function\n", - "from ortools.sat.python import cp_model\n", - "\n", - "\n", - "# Data.\n", - "cost = [[90, 76, 75, 70, 50, 74], [35, 85, 55, 65, 48,\n", - " 101], [125, 95, 90, 105, 59, 120],\n", - " [45, 110, 95, 115, 104, 83], [60, 105, 80, 75, 59, 62], [\n", - " 45, 65, 110, 95, 47, 31\n", - " ], [38, 51, 107, 41, 69, 99], [47, 85, 57, 71,\n", - " 92, 77], [39, 63, 97, 49, 118, 56],\n", - " [47, 101, 71, 60, 88, 109], [17, 39, 103, 64, 61,\n", - " 92], [101, 45, 83, 59, 92, 27]]\n", - "\n", - "group1 = [\n", - " [0, 0, 1, 1], # Workers 2, 3\n", - " [0, 1, 0, 1], # Workers 1, 3\n", - " [0, 1, 1, 0], # Workers 1, 2\n", - " [1, 1, 0, 0], # Workers 0, 1\n", - " [1, 0, 1, 0]\n", - "] # Workers 0, 2\n", - "\n", - "group2 = [\n", - " [0, 0, 1, 1], # Workers 6, 7\n", - " [0, 1, 0, 1], # Workers 5, 7\n", - " [0, 1, 1, 0], # Workers 5, 6\n", - " [1, 1, 0, 0], # Workers 4, 5\n", - " [1, 0, 0, 1]\n", - "] # Workers 4, 7\n", - "\n", - "group3 = [\n", - " [0, 0, 1, 1], # Workers 10, 11\n", - " [0, 1, 0, 1], # Workers 9, 11\n", - " [0, 1, 1, 0], # Workers 9, 10\n", - " [1, 0, 1, 0], # Workers 8, 10\n", - " [1, 0, 0, 1]\n", - "] # Workers 8, 11\n", - "\n", - "sizes = [10, 7, 3, 12, 15, 4, 11, 5]\n", - "total_size_max = 15\n", - "num_workers = len(cost)\n", - "num_tasks = len(cost[1])\n", - "all_workers = range(num_workers)\n", - "all_tasks = range(num_tasks)\n", - "\n", - "# Model.\n", - "\n", - "model = cp_model.CpModel()\n", - "# Variables\n", - "total_cost = model.NewIntVar(0, 1000, 'total_cost')\n", - "x = [[model.NewBoolVar('x[%i,%i]' % (i, j))\n", - " for j in all_tasks]\n", - " for i in all_workers]\n", - "works = [model.NewBoolVar('works[%i]' % i) for i in all_workers]\n", - "\n", - "# Constraints\n", - "\n", - "# Link x and workers.\n", - "for i in range(num_workers):\n", - " model.AddMaxEquality(works[i], x[i])\n", - "\n", - "# Each task is assigned to at least one worker.\n", - "for j in all_tasks:\n", - " model.Add(sum(x[i][j] for i in all_workers) >= 1)\n", - "\n", - "# Total task size for each worker is at most total_size_max\n", - "for i in all_workers:\n", - " model.Add(sum(sizes[j] * x[i][j] for j in all_tasks) <= total_size_max)\n", - "\n", - "# Group constraints.\n", - "model.AddAllowedAssignments([works[0], works[1], works[2], works[3]], group1)\n", - "model.AddAllowedAssignments([works[4], works[5], works[6], works[7]], group2)\n", - "model.AddAllowedAssignments([works[8], works[9], works[10], works[11]],\n", - " group3)\n", - "\n", - "# Total cost\n", - "model.Add(total_cost == sum(\n", - " x[i][j] * cost[i][j] for j in all_tasks for i in all_workers))\n", - "model.Minimize(total_cost)\n", - "\n", - "# Solve and output solution.\n", - "solver = cp_model.CpSolver()\n", - "status = solver.Solve(model)\n", - "\n", - "if status == cp_model.OPTIMAL:\n", - " print('Total cost = %i' % solver.ObjectiveValue())\n", - " print()\n", - " for i in all_workers:\n", - " for j in all_tasks:\n", - " if solver.Value(x[i][j]) == 1:\n", - " print('Worker ', i, ' assigned to task ', j, ' Cost = ', cost[i][j])\n", - "\n", - " print()\n", - "\n", - "print('Statistics')\n", - "print(' - conflicts : %i' % solver.NumConflicts())\n", - "print(' - branches : %i' % solver.NumBranches())\n", - "print(' - wall time : %f ms' % solver.WallTime())\n", - "\n" - ] - } - ], - "metadata": {}, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/examples/notebook/balance_group_sat.ipynb b/examples/notebook/balance_group_sat.ipynb deleted file mode 100644 index d151a57501..0000000000 --- a/examples/notebook/balance_group_sat.ipynb +++ /dev/null @@ -1,89 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "# Copyright 2010-2017 Google\n", - "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", - "# you may not use this file except in compliance with the License.\n", - "# You may obtain a copy of the License at\n", - "#\n", - "# http://www.apache.org/licenses/LICENSE-2.0\n", - "#\n", - "# Unless required by applicable law or agreed to in writing, software\n", - "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", - "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", - "# See the License for the specific language governing permissions and\n", - "# limitations under the License.\n", - "\n", - "from __future__ import print_function\n", - "\n", - "from ortools.sat.python import cp_model\n", - "\n", - "num_groups = 11\n", - "num_values = 121\n", - "\n", - "\n", - "model = cp_model.CpModel()\n", - "\n", - "boo_x_i_j = {}\n", - "for i in range(num_values):\n", - " for j in range(num_groups):\n", - " boo_x_i_j[(i, j)] = model.NewBoolVar('x%d belongs to group %d' % (i, j))\n", - "\n", - "e = model.NewIntVar(0, 5, 'epsilon')\n", - "\n", - "values = [i + 1 + 3 * (i > 99) for i in range(num_values)]\n", - "sum_of_values = sum(values)\n", - "average_value = sum_of_values / num_groups\n", - "\n", - "for j in range(num_groups):\n", - " model.Add(sum(boo_x_i_j[(i, j)]\n", - " for i in range(num_values)) == num_values / num_groups)\n", - "\n", - "for i in range(num_values):\n", - " model.Add(sum(boo_x_i_j[(i, j)] for j in range(num_groups)) == 1)\n", - "\n", - "for j in range(num_groups):\n", - " model.Add(sum(boo_x_i_j[(i, j)] * values[i] for i in range(num_values)) -\n", - " average_value <= e)\n", - " model.Add(sum(boo_x_i_j[(i, j)] * values[i] for i in range(num_values)) -\n", - " average_value >= -e)\n", - "\n", - "model.Minimize(e)\n", - "\n", - "\n", - "solver = cp_model.CpSolver()\n", - "status = solver.Solve(model)\n", - "print('Optimal epsilon: %i' % solver.ObjectiveValue())\n", - "print('Statistics')\n", - "print(' - conflicts : %i' % solver.NumConflicts())\n", - "print(' - branches : %i' % solver.NumBranches())\n", - "print(' - wall time : %f s' % solver.WallTime())\n", - "\n", - "groups = {}\n", - "for j in range(num_groups):\n", - " groups[j] = []\n", - "for i in range(num_values):\n", - " for j in range(num_groups):\n", - " if solver.Value(boo_x_i_j[(i, j)]):\n", - " groups[j].append(values[i])\n", - "\n", - "for j in range(num_groups):\n", - " print ('group %i: average = %0.2f [' % (\n", - " j, 1.0 * sum(groups[j]) / len(groups[j])), end='')\n", - " for v in groups[j]:\n", - " print(' %i' % v, end='')\n", - " print(' ]')" - ] - } - ], - "metadata": {}, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/examples/notebook/code_samples_sat.ipynb b/examples/notebook/code_samples_sat.ipynb deleted file mode 100644 index 77e6e0a734..0000000000 --- a/examples/notebook/code_samples_sat.ipynb +++ /dev/null @@ -1,473 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Copyright 2010-2017 Google\n", - "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", - "# you may not use this file except in compliance with the License.\n", - "# You may obtain a copy of the License at\n", - "#\n", - "# http://www.apache.org/licenses/LICENSE-2.0\n", - "#\n", - "# Unless required by applicable law or agreed to in writing, software\n", - "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", - "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", - "# See the License for the specific language governing permissions and\n", - "# limitations under the License.\n", - "\"\"\"SAT code samples used in documentation.\"\"\"\n", - "\n", - "from __future__ import absolute_import\n", - "from __future__ import division\n", - "from __future__ import print_function\n", - "\n", - "import collections\n", - "\n", - "from ortools.sat.python import cp_model\n", - "\n", - "\n", - "def CodeSample():\n", - " model = cp_model.CpModel()\n", - " x = model.NewBoolVar('x')\n", - " print(x)\n", - "\n", - "\n", - "def LiteralSample():\n", - " model = cp_model.CpModel()\n", - " x = model.NewBoolVar('x')\n", - " not_x = x.Not()\n", - " print(x)\n", - " print(not_x)\n", - "\n", - "\n", - "def BoolOrSample():\n", - " model = cp_model.CpModel()\n", - "\n", - " x = model.NewBoolVar('x')\n", - " y = model.NewBoolVar('y')\n", - "\n", - " model.AddBoolOr([x, y.Not()])\n", - "\n", - "\n", - "def ReifiedSample():\n", - " \"\"\"Showcase creating a reified constraint.\"\"\"\n", - " model = cp_model.CpModel()\n", - "\n", - " x = model.NewBoolVar('x')\n", - " y = model.NewBoolVar('y')\n", - " b = model.NewBoolVar('b')\n", - "\n", - " # First version using a half-reified bool and.\n", - " model.AddBoolAnd([x, y.Not()]).OnlyEnforceIf(b)\n", - "\n", - " # Second version using implications.\n", - " model.AddImplication(b, x)\n", - " model.AddImplication(b, y.Not())\n", - "\n", - " # Third version using bool or.\n", - " model.AddBoolOr([b.Not(), x])\n", - " model.AddBoolOr([b.Not(), y.Not()])\n", - "\n", - "\n", - "def RabbitsAndPheasants():\n", - " \"\"\"Solves the rabbits + pheasants problem.\"\"\"\n", - " model = cp_model.CpModel()\n", - "\n", - " r = model.NewIntVar(0, 100, 'r')\n", - " p = model.NewIntVar(0, 100, 'p')\n", - "\n", - " # 20 heads.\n", - " model.Add(r + p == 20)\n", - " # 56 legs.\n", - " model.Add(4 * r + 2 * p == 56)\n", - "\n", - " # Solves and prints out the solution.\n", - " solver = cp_model.CpSolver()\n", - " status = solver.Solve(model)\n", - "\n", - " if status == cp_model.FEASIBLE:\n", - " print('%i rabbits and %i pheasants' % (solver.Value(r), solver.Value(p)))\n", - "\n", - "\n", - "def BinpackingProblem():\n", - " \"\"\"Solves a bin-packing problem.\"\"\"\n", - " # Data.\n", - " bin_capacity = 100\n", - " slack_capacity = 20\n", - " num_bins = 10\n", - " all_bins = range(num_bins)\n", - "\n", - " items = [(20, 12), (15, 12), (30, 8), (45, 5)]\n", - " num_items = len(items)\n", - " all_items = range(num_items)\n", - "\n", - " # Model.\n", - " model = cp_model.CpModel()\n", - "\n", - " # Main variables.\n", - " x = {}\n", - " for i in all_items:\n", - " num_copies = items[i][1]\n", - " for b in all_bins:\n", - " x[(i, b)] = model.NewIntVar(0, num_copies, 'x_%i_%i' % (i, b))\n", - "\n", - " # Load variables.\n", - " load = [model.NewIntVar(0, bin_capacity, 'load_%i' % b) for b in all_bins]\n", - "\n", - " # Slack variables.\n", - " slacks = [model.NewBoolVar('slack_%i' % b) for b in all_bins]\n", - "\n", - " # Links load and x.\n", - " for b in all_bins:\n", - " model.Add(load[b] == sum(x[(i, b)] * items[i][0] for i in all_items))\n", - "\n", - " # Place all items.\n", - " for i in all_items:\n", - " model.Add(sum(x[(i, b)] for b in all_bins) == items[i][1])\n", - "\n", - " # Links load and slack through an equivalence relation.\n", - " safe_capacity = bin_capacity - slack_capacity\n", - " for b in all_bins:\n", - " # slack[b] => load[b] <= safe_capacity.\n", - " model.Add(load[b] <= safe_capacity).OnlyEnforceIf(slacks[b])\n", - " # not(slack[b]) => load[b] > safe_capacity.\n", - " model.Add(load[b] > safe_capacity).OnlyEnforceIf(slacks[b].Not())\n", - "\n", - " # Maximize sum of slacks.\n", - " model.Maximize(sum(slacks))\n", - "\n", - " # Solves and prints out the solution.\n", - " solver = cp_model.CpSolver()\n", - " status = solver.Solve(model)\n", - " print('Solve status: %s' % solver.StatusName(status))\n", - " if status == cp_model.OPTIMAL:\n", - " print('Optimal objective value: %i' % solver.ObjectiveValue())\n", - " print('Statistics')\n", - " print(' - conflicts : %i' % solver.NumConflicts())\n", - " print(' - branches : %i' % solver.NumBranches())\n", - " print(' - wall time : %f s' % solver.WallTime())\n", - "\n", - "\n", - "def IntervalSample():\n", - " model = cp_model.CpModel()\n", - " horizon = 100\n", - " start_var = model.NewIntVar(0, horizon, 'start')\n", - " duration = 10 # Python cp/sat code accept integer variables or constants.\n", - " end_var = model.NewIntVar(0, horizon, 'end')\n", - " interval_var = model.NewIntervalVar(start_var, duration, end_var, 'interval')\n", - " print('start = %s, duration = %i, end = %s, interval = %s' %\n", - " (start_var, duration, end_var, interval_var))\n", - "\n", - "\n", - "def OptionalIntervalSample():\n", - " model = cp_model.CpModel()\n", - " horizon = 100\n", - " start_var = model.NewIntVar(0, horizon, 'start')\n", - " duration = 10 # Python cp/sat code accept integer variables or constants.\n", - " end_var = model.NewIntVar(0, horizon, 'end')\n", - " presence_var = model.NewBoolVar('presence')\n", - " interval_var = model.NewOptionalIntervalVar(start_var, duration, end_var,\n", - " presence_var, 'interval')\n", - " print('start = %s, duration = %i, end = %s, presence = %s, interval = %s' %\n", - " (start_var, duration, end_var, presence_var, interval_var))\n", - "\n", - "\n", - "def MinimalCpSat():\n", - " \"\"\"Minimal CP-SAT example to showcase calling the solver.\"\"\"\n", - " # Creates the model.\n", - " model = cp_model.CpModel()\n", - " # Creates the variables.\n", - " num_vals = 3\n", - " x = model.NewIntVar(0, num_vals - 1, 'x')\n", - " y = model.NewIntVar(0, num_vals - 1, 'y')\n", - " z = model.NewIntVar(0, num_vals - 1, 'z')\n", - " # Creates the constraints.\n", - " model.Add(x != y)\n", - "\n", - " # Creates a solver and solves the model.\n", - " solver = cp_model.CpSolver()\n", - " status = solver.Solve(model)\n", - "\n", - " if status == cp_model.FEASIBLE:\n", - " print('x = %i' % solver.Value(x))\n", - " print('y = %i' % solver.Value(y))\n", - " print('z = %i' % solver.Value(z))\n", - "\n", - "\n", - "def MinimalCpSatWithTimeLimit():\n", - " \"\"\"Minimal CP-SAT example to showcase calling the solver.\"\"\"\n", - " # Creates the model.\n", - " model = cp_model.CpModel()\n", - " # Creates the variables.\n", - " num_vals = 3\n", - " x = model.NewIntVar(0, num_vals - 1, 'x')\n", - " y = model.NewIntVar(0, num_vals - 1, 'y')\n", - " z = model.NewIntVar(0, num_vals - 1, 'z')\n", - " # Adds an all-different constraint.\n", - " model.Add(x != y)\n", - "\n", - " # Creates a solver and solves the model.\n", - " solver = cp_model.CpSolver()\n", - "\n", - " # Sets a time limit of 10 seconds.\n", - " solver.parameters.max_time_in_seconds = 10.0\n", - "\n", - " status = solver.Solve(model)\n", - "\n", - " if status == cp_model.FEASIBLE:\n", - " print('x = %i' % solver.Value(x))\n", - " print('y = %i' % solver.Value(y))\n", - " print('z = %i' % solver.Value(z))\n", - "\n", - "\n", - "# You need to subclass the cp_model.CpSolverSolutionCallback class.\n", - "class VarArrayAndObjectiveSolutionPrinter(cp_model.CpSolverSolutionCallback):\n", - " \"\"\"Print intermediate solutions.\"\"\"\n", - "\n", - " def __init__(self, variables):\n", - " cp_model.CpSolverSolutionCallback.__init__(self)\n", - " self.__variables = variables\n", - " self.__solution_count = 0\n", - "\n", - " def OnSolutionCallback(self):\n", - " print('Solution %i' % self.__solution_count)\n", - " print(' objective value = %i' % self.ObjectiveValue())\n", - " for v in self.__variables:\n", - " print(' %s = %i' % (v, self.Value(v)), end=' ')\n", - " print()\n", - " self.__solution_count += 1\n", - "\n", - " def SolutionCount(self):\n", - " return self.__solution_count\n", - "\n", - "\n", - "def MinimalCpSatPrintIntermediateSolutions():\n", - " \"\"\"Showcases printing intermediate solutions found during search.\"\"\"\n", - " # Creates the model.\n", - " model = cp_model.CpModel()\n", - " # Creates the variables.\n", - " num_vals = 3\n", - " x = model.NewIntVar(0, num_vals - 1, 'x')\n", - " y = model.NewIntVar(0, num_vals - 1, 'y')\n", - " z = model.NewIntVar(0, num_vals - 1, 'z')\n", - " # Creates the constraints.\n", - " model.Add(x != y)\n", - " model.Maximize(x + 2 * y + 3 * z)\n", - "\n", - " # Creates a solver and solves.\n", - " solver = cp_model.CpSolver()\n", - " solution_printer = VarArrayAndObjectiveSolutionPrinter([x, y, z])\n", - " status = solver.SolveWithSolutionCallback(model, solution_printer)\n", - "\n", - " print('Status = %s' % solver.StatusName(status))\n", - " print('Number of solutions found: %i' % solution_printer.SolutionCount())\n", - "\n", - "\n", - "class VarArraySolutionPrinter(cp_model.CpSolverSolutionCallback):\n", - " \"\"\"Print intermediate solutions.\"\"\"\n", - "\n", - " def __init__(self, variables):\n", - " cp_model.CpSolverSolutionCallback.__init__(self)\n", - " self.__variables = variables\n", - " self.__solution_count = 0\n", - "\n", - " def OnSolutionCallback(self):\n", - " self.__solution_count += 1\n", - " for v in self.__variables:\n", - " print('%s=%i' % (v, self.Value(v)), end=' ')\n", - " print()\n", - "\n", - " def SolutionCount(self):\n", - " return self.__solution_count\n", - "\n", - "\n", - "def MinimalCpSatAllSolutions():\n", - " \"\"\"Showcases calling the solver to search for all solutions.\"\"\"\n", - " # Creates the model.\n", - " model = cp_model.CpModel()\n", - " # Creates the variables.\n", - " num_vals = 3\n", - " x = model.NewIntVar(0, num_vals - 1, 'x')\n", - " y = model.NewIntVar(0, num_vals - 1, 'y')\n", - " z = model.NewIntVar(0, num_vals - 1, 'z')\n", - " # Create the constraints.\n", - " model.Add(x != y)\n", - "\n", - " # Create a solver and solve.\n", - " solver = cp_model.CpSolver()\n", - " solution_printer = VarArraySolutionPrinter([x, y, z])\n", - " status = solver.SearchForAllSolutions(model, solution_printer)\n", - " print('Status = %s' % solver.StatusName(status))\n", - " print('Number of solutions found: %i' % solution_printer.SolutionCount())\n", - "\n", - "\n", - "def SolvingLinearProblem():\n", - " \"\"\"CP-SAT linear solver problem.\"\"\"\n", - " # Create a model.\n", - " model = cp_model.CpModel()\n", - "\n", - " # x and y are integer non-negative variables.\n", - " x = model.NewIntVar(0, 17, 'x')\n", - " y = model.NewIntVar(0, 17, 'y')\n", - " model.Add(2 * x + 14 * y <= 35)\n", - " model.Add(2 * x <= 7)\n", - " obj_var = model.NewIntVar(0, 1000, 'obj_var')\n", - " model.Add(obj_var == x + 10 * y)\n", - " model.Maximize(obj_var)\n", - "\n", - " # Create a solver and solve.\n", - " solver = cp_model.CpSolver()\n", - " status = solver.Solve(model)\n", - " if status == cp_model.OPTIMAL:\n", - " print('Objective value: %i' % solver.ObjectiveValue())\n", - " print()\n", - " print('x= %i' % solver.Value(x))\n", - " print('y= %i' % solver.Value(y))\n", - "\n", - "\n", - "def MinimalJobShop():\n", - " \"\"\"Minimal jobshop problem.\"\"\"\n", - " # Create the model.\n", - " model = cp_model.CpModel()\n", - "\n", - " machines_count = 3\n", - " jobs_count = 3\n", - " all_machines = range(0, machines_count)\n", - " all_jobs = range(0, jobs_count)\n", - " # Define data.\n", - " machines = [[0, 1, 2], [0, 2, 1], [1, 2]]\n", - "\n", - " processing_times = [[3, 2, 2], [2, 1, 4], [4, 3]]\n", - " # Computes horizon.\n", - " horizon = 0\n", - " for job in all_jobs:\n", - " horizon += sum(processing_times[job])\n", - "\n", - " task_type = collections.namedtuple('task_type', 'start end interval')\n", - " assigned_task_type = collections.namedtuple('assigned_task_type',\n", - " 'start job index')\n", - "\n", - " # Creates jobs.\n", - " all_tasks = {}\n", - " for job in all_jobs:\n", - " for index in range(0, len(machines[job])):\n", - " start_var = model.NewIntVar(0, horizon, 'start_%i_%i' % (job, index))\n", - " duration = processing_times[job][index]\n", - " end_var = model.NewIntVar(0, horizon, 'end_%i_%i' % (job, index))\n", - " interval_var = model.NewIntervalVar(start_var, duration, end_var,\n", - " 'interval_%i_%i' % (job, index))\n", - " all_tasks[(job, index)] = task_type(\n", - " start=start_var, end=end_var, interval=interval_var)\n", - "\n", - " # Creates sequence variables and add disjunctive constraints.\n", - " for machine in all_machines:\n", - " intervals = []\n", - " for job in all_jobs:\n", - " for index in range(0, len(machines[job])):\n", - " if machines[job][index] == machine:\n", - " intervals.append(all_tasks[(job, index)].interval)\n", - " model.AddNoOverlap(intervals)\n", - "\n", - " # Add precedence contraints.\n", - " for job in all_jobs:\n", - " for index in range(0, len(machines[job]) - 1):\n", - " model.Add(all_tasks[(job, index + 1)].start >= all_tasks[(job,\n", - " index)].end)\n", - "\n", - " # Makespan objective.\n", - " obj_var = model.NewIntVar(0, horizon, 'makespan')\n", - " model.AddMaxEquality(\n", - " obj_var,\n", - " [all_tasks[(job, len(machines[job]) - 1)].end for job in all_jobs])\n", - " model.Minimize(obj_var)\n", - "\n", - " # Solve model.\n", - " solver = cp_model.CpSolver()\n", - " status = solver.Solve(model)\n", - "\n", - " if status == cp_model.OPTIMAL:\n", - " # Print out makespan.\n", - " print('Optimal Schedule Length: %i' % solver.ObjectiveValue())\n", - " print()\n", - "\n", - " # Create one list of assigned tasks per machine.\n", - " assigned_jobs = [[] for _ in range(machines_count)]\n", - " for job in all_jobs:\n", - " for index in range(len(machines[job])):\n", - " machine = machines[job][index]\n", - " assigned_jobs[machine].append(\n", - " assigned_task_type(\n", - " start=solver.Value(all_tasks[(job, index)].start),\n", - " job=job,\n", - " index=index))\n", - "\n", - " disp_col_width = 10\n", - " sol_line = ''\n", - " sol_line_tasks = ''\n", - "\n", - " print('Optimal Schedule', '\\n')\n", - "\n", - " for machine in all_machines:\n", - " # Sort by starting time.\n", - " assigned_jobs[machine].sort()\n", - " sol_line += 'Machine ' + str(machine) + ': '\n", - " sol_line_tasks += 'Machine ' + str(machine) + ': '\n", - "\n", - " for assigned_task in assigned_jobs[machine]:\n", - " name = 'job_%i_%i' % (assigned_task.job, assigned_task.index)\n", - " # Add spaces to output to align columns.\n", - " sol_line_tasks += name + ' ' * (disp_col_width - len(name))\n", - " start = assigned_task.start\n", - " duration = processing_times[assigned_task.job][assigned_task.index]\n", - "\n", - " sol_tmp = '[%i,%i]' % (start, start + duration)\n", - " # Add spaces to output to align columns.\n", - " sol_line += sol_tmp + ' ' * (disp_col_width - len(sol_tmp))\n", - "\n", - " sol_line += '\\n'\n", - " sol_line_tasks += '\\n'\n", - "\n", - " print(sol_line_tasks)\n", - " print('Time Intervals for task_types\\n')\n", - " print(sol_line)\n", - "\n", - "\n", - "print('--- CodeSample ---')\n", - "CodeSample()\n", - "print('--- LiteralSample ---')\n", - "LiteralSample()\n", - "print('--- BoolOrSample ---')\n", - "BoolOrSample()\n", - "print('--- ReifiedSample ---')\n", - "ReifiedSample()\n", - "print('--- RabbitsAndPheasants ---')\n", - "RabbitsAndPheasants()\n", - "print('--- BinpackingProblem ---')\n", - "BinpackingProblem()\n", - "print('--- IntervalSample ---')\n", - "IntervalSample()\n", - "print('--- OptionalIntervalSample ---')\n", - "OptionalIntervalSample()\n", - "print('--- MinimalCpSat ---')\n", - "MinimalCpSat()\n", - "print('--- MinimalCpSatWithTimeLimit ---')\n", - "MinimalCpSatWithTimeLimit()\n", - "print('--- MinimalCpSatPrintIntermediateSolutions ---')\n", - "MinimalCpSatPrintIntermediateSolutions()\n", - "print('--- MinimalCpSatAllSolutions ---')\n", - "MinimalCpSatAllSolutions()\n", - "print('--- SolvingLinearProblem ---')\n", - "SolvingLinearProblem()\n", - "print('--- MinimalJobShop ---')\n", - "MinimalJobShop()\n", - "\n" - ] - } - ], - "metadata": {}, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/examples/notebook/constraint_solver/cvrp.ipynb b/examples/notebook/constraint_solver/cvrp.ipynb new file mode 100644 index 0000000000..098aae6ce1 --- /dev/null +++ b/examples/notebook/constraint_solver/cvrp.ipynb @@ -0,0 +1,206 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "#!/usr/bin/env python\n", + "# This Python file uses the following encoding: utf-8\n", + "# Copyright 2015 Tin Arm Engineering AB\n", + "# Copyright 2018 Google LLC\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"Capacitated Vehicle Routing Problem (CVRP).\n", + "\n", + " This is a sample using the routing library python wrapper to solve a CVRP\n", + " problem.\n", + " A description of the problem can be found here:\n", + " http://en.wikipedia.org/wiki/Vehicle_routing_problem.\n", + "\n", + " Distances are in meters.\n", + "\"\"\"\n", + "\n", + "from __future__ import print_function\n", + "\n", + "from functools import partial\n", + "from six.moves import xrange\n", + "\n", + "from ortools.constraint_solver import pywrapcp\n", + "from ortools.constraint_solver import routing_enums_pb2\n", + "\n", + "\n", + "###########################\n", + "# Problem Data Definition #\n", + "###########################\n", + "def create_data_model():\n", + " \"\"\"Stores the data for the problem\"\"\"\n", + " data = {}\n", + " # Locations in block unit\n", + " _locations = \\\n", + " [(4, 4), # depot\n", + " (2, 0), (8, 0), # locations to visit\n", + " (0, 1), (1, 1),\n", + " (5, 2), (7, 2),\n", + " (3, 3), (6, 3),\n", + " (5, 5), (8, 5),\n", + " (1, 6), (2, 6),\n", + " (3, 7), (6, 7),\n", + " (0, 8), (7, 8)]\n", + " # Compute locations in meters using the block dimension defined as follow\n", + " # Manhattan average block: 750ft x 264ft -> 228m x 80m\n", + " # here we use: 114m x 80m city block\n", + " # src: https://nyti.ms/2GDoRIe 'NY Times: Know Your distance'\n", + " data['locations'] = [(l[0] * 114, l[1] * 80) for l in _locations]\n", + " data['num_locations'] = len(data['locations'])\n", + " data['demands'] = \\\n", + " [0, # depot\n", + " 1, 1, # 1, 2\n", + " 2, 4, # 3, 4\n", + " 2, 4, # 5, 6\n", + " 8, 8, # 7, 8\n", + " 1, 2, # 9,10\n", + " 1, 2, # 11,12\n", + " 4, 4, # 13, 14\n", + " 8, 8] # 15, 16\n", + " data['num_vehicles'] = 4\n", + " data['vehicle_capacity'] = 15\n", + " data['depot'] = 0\n", + " return data\n", + "\n", + "\n", + "#######################\n", + "# Problem Constraints #\n", + "#######################\n", + "def manhattan_distance(position_1, position_2):\n", + " \"\"\"Computes the Manhattan distance between two points\"\"\"\n", + " return (\n", + " abs(position_1[0] - position_2[0]) + abs(position_1[1] - position_2[1]))\n", + "\n", + "\n", + "def create_distance_evaluator(data):\n", + " \"\"\"Creates callback to return distance between points.\"\"\"\n", + " _distances = {}\n", + " # precompute distance between location to have distance callback in O(1)\n", + " for from_node in xrange(data['num_locations']):\n", + " _distances[from_node] = {}\n", + " for to_node in xrange(data['num_locations']):\n", + " if from_node == to_node:\n", + " _distances[from_node][to_node] = 0\n", + " else:\n", + " _distances[from_node][to_node] = (manhattan_distance(\n", + " data['locations'][from_node], data['locations'][to_node]))\n", + "\n", + " def distance_evaluator(manager, from_node, to_node):\n", + " \"\"\"Returns the manhattan distance between the two nodes\"\"\"\n", + " return _distances[manager.IndexToNode(from_node)][manager.IndexToNode(\n", + " to_node)]\n", + "\n", + " return distance_evaluator\n", + "\n", + "\n", + "def create_demand_evaluator(data):\n", + " \"\"\"Creates callback to get demands at each location.\"\"\"\n", + " _demands = data['demands']\n", + "\n", + " def demand_evaluator(manager, node):\n", + " \"\"\"Returns the demand of the current node\"\"\"\n", + " return _demands[manager.IndexToNode(node)]\n", + "\n", + " return demand_evaluator\n", + "\n", + "\n", + "def add_capacity_constraints(routing, data, demand_evaluator_index):\n", + " \"\"\"Adds capacity constraint\"\"\"\n", + " capacity = 'Capacity'\n", + " routing.AddDimension(\n", + " demand_evaluator_index,\n", + " 0, # null capacity slack\n", + " data['vehicle_capacity'],\n", + " True, # start cumul to zero\n", + " capacity)\n", + "\n", + "\n", + "###########\n", + "# Printer #\n", + "###########\n", + "def print_solution(data, routing, manager, assignment): # pylint:disable=too-many-locals\n", + " \"\"\"Prints assignment on console\"\"\"\n", + " print('Objective: {}'.format(assignment.ObjectiveValue()))\n", + " total_distance = 0\n", + " total_load = 0\n", + " capacity_dimension = routing.GetDimensionOrDie('Capacity')\n", + " for vehicle_id in xrange(data['num_vehicles']):\n", + " index = routing.Start(vehicle_id)\n", + " plan_output = 'Route for vehicle {}:\\n'.format(vehicle_id)\n", + " distance = 0\n", + " while not routing.IsEnd(index):\n", + " load_var = capacity_dimension.CumulVar(index)\n", + " plan_output += ' {} Load({}) -> '.format(\n", + " manager.IndexToNode(index), assignment.Value(load_var))\n", + " previous_index = index\n", + " index = assignment.Value(routing.NextVar(index))\n", + " distance += routing.GetArcCostForVehicle(previous_index, index,\n", + " vehicle_id)\n", + " load_var = capacity_dimension.CumulVar(index)\n", + " plan_output += ' {0} Load({1})\\n'.format(\n", + " manager.IndexToNode(index), assignment.Value(load_var))\n", + " plan_output += 'Distance of the route: {}m\\n'.format(distance)\n", + " plan_output += 'Load of the route: {}\\n'.format(\n", + " assignment.Value(load_var))\n", + " print(plan_output)\n", + " total_distance += distance\n", + " total_load += assignment.Value(load_var)\n", + " print('Total Distance of all routes: {}m'.format(total_distance))\n", + " print('Total Load of all routes: {}'.format(total_load))\n", + "\n", + "\n", + "########\n", + "# Main #\n", + "########\n", + "\"\"\"Entry point of the program\"\"\"\n", + "# Instantiate the data problem.\n", + "data = create_data_model()\n", + "\n", + "# Create the routing index manager\n", + "manager = pywrapcp.RoutingIndexManager(data['num_locations'],\n", + " data['num_vehicles'], data['depot'])\n", + "\n", + "# Create Routing Model\n", + "routing = pywrapcp.RoutingModel(manager)\n", + "\n", + "# Define weight of each edge\n", + "distance_evaluator = routing.RegisterTransitCallback(\n", + " partial(create_distance_evaluator(data), manager))\n", + "routing.SetArcCostEvaluatorOfAllVehicles(distance_evaluator)\n", + "\n", + "# Add Capacity constraint\n", + "demand_evaluator_index = routing.RegisterUnaryTransitCallback(\n", + " partial(create_demand_evaluator(data), manager))\n", + "add_capacity_constraints(routing, data, demand_evaluator_index)\n", + "\n", + "# Setting first solution heuristic (cheapest addition).\n", + "search_parameters = pywrapcp.DefaultRoutingSearchParameters()\n", + "search_parameters.first_solution_strategy = (\n", + " routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC) # pylint: disable=no-member\n", + "# Solve the problem.\n", + "assignment = routing.SolveWithParameters(search_parameters)\n", + "print_solution(data, routing, manager, assignment)\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/constraint_solver/cvrp_reload.ipynb b/examples/notebook/constraint_solver/cvrp_reload.ipynb new file mode 100644 index 0000000000..98d3c6e4b9 --- /dev/null +++ b/examples/notebook/constraint_solver/cvrp_reload.ipynb @@ -0,0 +1,362 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "#!/usr/bin/env python\n", + "# This Python file uses the following encoding: utf-8\n", + "# Copyright 2015 Tin Arm Engineering AB\n", + "# Copyright 2018 Google LLC\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"Capacitated Vehicle Routing Problem (CVRP).\n", + "\n", + " This is a sample using the routing library python wrapper to solve a CVRP\n", + " problem.\n", + " A description of the problem can be found here:\n", + " http://en.wikipedia.org/wiki/Vehicle_routing_problem.\n", + "\n", + " Distances are in meters.\n", + "\"\"\"\n", + "\n", + "from __future__ import print_function\n", + "\n", + "from functools import partial\n", + "from six.moves import xrange\n", + "\n", + "from ortools.constraint_solver import pywrapcp\n", + "from ortools.constraint_solver import routing_enums_pb2\n", + "\n", + "\n", + "###########################\n", + "# Problem Data Definition #\n", + "###########################\n", + "def create_data_model():\n", + " \"\"\"Stores the data for the problem\"\"\"\n", + " data = {}\n", + " _capacity = 15\n", + " # Locations in block unit\n", + " _locations = [\n", + " (4, 4), # depot\n", + " (4, 4), # unload depot_prime\n", + " (4, 4), # unload depot_second\n", + " (4, 4), # unload depot_fourth\n", + " (4, 4), # unload depot_fourth\n", + " (4, 4), # unload depot_fifth\n", + " (2, 0),\n", + " (8, 0), # locations to visit\n", + " (0, 1),\n", + " (1, 1),\n", + " (5, 2),\n", + " (7, 2),\n", + " (3, 3),\n", + " (6, 3),\n", + " (5, 5),\n", + " (8, 5),\n", + " (1, 6),\n", + " (2, 6),\n", + " (3, 7),\n", + " (6, 7),\n", + " (0, 8),\n", + " (7, 8)\n", + " ]\n", + " # Compute locations in meters using the block dimension defined as follow\n", + " # Manhattan average block: 750ft x 264ft -> 228m x 80m\n", + " # here we use: 114m x 80m city block\n", + " # src: https://nyti.ms/2GDoRIe 'NY Times: Know Your distance'\n", + " data['locations'] = [(l[0] * 114, l[1] * 80) for l in _locations]\n", + " data['num_locations'] = len(data['locations'])\n", + " data['demands'] = \\\n", + " [0, # depot\n", + " -_capacity,\n", + " -_capacity,\n", + " -_capacity,\n", + " -_capacity,\n", + " -_capacity,\n", + " 1, 1, # 1, 2\n", + " 2, 4, # 3, 4\n", + " 2, 4, # 5, 6\n", + " 8, 8, # 7, 8\n", + " 1, 2, # 9,10\n", + " 1, 2, # 11,12\n", + " 4, 4, # 13, 14\n", + " 8, 8] # 15, 16\n", + " data['time_per_demand_unit'] = 5 # 5 minutes/unit\n", + " data['time_windows'] = \\\n", + " [(0, 0), # depot\n", + " (0, 1000),\n", + " (0, 1000),\n", + " (0, 1000),\n", + " (0, 1000),\n", + " (0, 1000),\n", + " (75, 8500), (75, 8500), # 1, 2\n", + " (60, 7000), (45, 5500), # 3, 4\n", + " (0, 8000), (50, 6000), # 5, 6\n", + " (0, 1000), (10, 2000), # 7, 8\n", + " (0, 1000), (75, 8500), # 9, 10\n", + " (85, 9500), (5, 1500), # 11, 12\n", + " (15, 2500), (10, 2000), # 13, 14\n", + " (45, 5500), (30, 4000)] # 15, 16\n", + " data['num_vehicles'] = 3\n", + " data['vehicle_capacity'] = _capacity\n", + " data[\n", + " 'vehicle_speed'] = 5 * 60 / 3.6 # Travel speed: 5km/h to convert in m/min\n", + " data['depot'] = 0\n", + " return data\n", + "\n", + "\n", + "#######################\n", + "# Problem Constraints #\n", + "#######################\n", + "def manhattan_distance(position_1, position_2):\n", + " \"\"\"Computes the Manhattan distance between two points\"\"\"\n", + " return (abs(position_1[0] - position_2[0]) +\n", + " abs(position_1[1] - position_2[1]))\n", + "\n", + "\n", + "def create_distance_evaluator(data):\n", + " \"\"\"Creates callback to return distance between points.\"\"\"\n", + " _distances = {}\n", + " # precompute distance between location to have distance callback in O(1)\n", + " for from_node in xrange(data['num_locations']):\n", + " _distances[from_node] = {}\n", + " for to_node in xrange(data['num_locations']):\n", + " if from_node == to_node:\n", + " _distances[from_node][to_node] = 0\n", + " else:\n", + " _distances[from_node][to_node] = (manhattan_distance(\n", + " data['locations'][from_node], data['locations'][to_node]))\n", + "\n", + " def distance_evaluator(manager, from_node, to_node):\n", + " \"\"\"Returns the manhattan distance between the two nodes\"\"\"\n", + " return _distances[manager.IndexToNode(from_node)][manager.IndexToNode(\n", + " to_node)]\n", + "\n", + " return distance_evaluator\n", + "\n", + "\n", + "def add_distance_dimension(routing, distance_evaluator_index):\n", + " \"\"\"Add Global Span constraint\"\"\"\n", + " distance = 'Distance'\n", + " routing.AddDimension(\n", + " distance_evaluator_index,\n", + " 0, # null slack\n", + " 10000, # maximum distance per vehicle\n", + " True, # start cumul to zero\n", + " distance)\n", + " distance_dimension = routing.GetDimensionOrDie(distance)\n", + " # Try to minimize the max distance among vehicles.\n", + " # /!\\ It doesn't mean the standard deviation is minimized\n", + " distance_dimension.SetGlobalSpanCostCoefficient(100)\n", + "\n", + "\n", + "def create_demand_evaluator(data):\n", + " \"\"\"Creates callback to get demands at each location.\"\"\"\n", + " _demands = data['demands']\n", + "\n", + " def demand_evaluator(manager, from_node):\n", + " \"\"\"Returns the demand of the current node\"\"\"\n", + " return _demands[manager.IndexToNode(from_node)]\n", + "\n", + " return demand_evaluator\n", + "\n", + "\n", + "def add_capacity_constraints(routing, manager, data, demand_evaluator_index):\n", + " \"\"\"Adds capacity constraint\"\"\"\n", + " vehicle_capacity = data['vehicle_capacity']\n", + " capacity = 'Capacity'\n", + " routing.AddDimension(\n", + " demand_evaluator_index,\n", + " 0, # Null slack\n", + " vehicle_capacity,\n", + " True, # start cumul to zero\n", + " capacity)\n", + "\n", + " # Add Slack for reseting to zero unload depot nodes.\n", + " # e.g. vehicle with load 10/15 arrives at node 1 (depot unload)\n", + " # so we have CumulVar = 10(current load) + -15(unload) + 5(slack) = 0.\n", + " capacity_dimension = routing.GetDimensionOrDie(capacity)\n", + " for node_index in [1, 2, 3, 4, 5]:\n", + " index = manager.NodeToIndex(node_index)\n", + " capacity_dimension.SlackVar(index).SetRange(0, vehicle_capacity)\n", + " routing.AddDisjunction([node_index], 0)\n", + "\n", + "\n", + "def create_time_evaluator(data):\n", + " \"\"\"Creates callback to get total times between locations.\"\"\"\n", + "\n", + " def service_time(data, node):\n", + " \"\"\"Gets the service time for the specified location.\"\"\"\n", + " return abs(data['demands'][node]) * data['time_per_demand_unit']\n", + "\n", + " def travel_time(data, from_node, to_node):\n", + " \"\"\"Gets the travel times between two locations.\"\"\"\n", + " if from_node == to_node:\n", + " travel_time = 0\n", + " else:\n", + " travel_time = manhattan_distance(data['locations'][\n", + " from_node], data['locations'][to_node]) / data['vehicle_speed']\n", + " return travel_time\n", + "\n", + " _total_time = {}\n", + " # precompute total time to have time callback in O(1)\n", + " for from_node in xrange(data['num_locations']):\n", + " _total_time[from_node] = {}\n", + " for to_node in xrange(data['num_locations']):\n", + " if from_node == to_node:\n", + " _total_time[from_node][to_node] = 0\n", + " else:\n", + " _total_time[from_node][to_node] = int(\n", + " service_time(data, from_node) + travel_time(\n", + " data, from_node, to_node))\n", + "\n", + " def time_evaluator(manager, from_node, to_node):\n", + " \"\"\"Returns the total time between the two nodes\"\"\"\n", + " return _total_time[manager.IndexToNode(from_node)][manager.IndexToNode(\n", + " to_node)]\n", + "\n", + " return time_evaluator\n", + "\n", + "\n", + "def add_time_window_constraints(routing, manager, data, time_evaluator):\n", + " \"\"\"Add Time windows constraint\"\"\"\n", + " time = 'Time'\n", + " horizon = 1500\n", + " routing.AddDimension(\n", + " time_evaluator,\n", + " horizon, # allow waiting time\n", + " horizon, # maximum time per vehicle\n", + " False, # don't force start cumul to zero since we are giving TW to start nodes\n", + " time)\n", + " time_dimension = routing.GetDimensionOrDie(time)\n", + " # Add time window constraints for each location except depot\n", + " # and 'copy' the slack var in the solution object (aka Assignment) to print it\n", + " for location_idx, time_window in enumerate(data['time_windows']):\n", + " if location_idx == 0:\n", + " continue\n", + " index = manager.NodeToIndex(location_idx)\n", + " time_dimension.CumulVar(index).SetRange(time_window[0], time_window[1])\n", + " routing.AddToAssignment(time_dimension.SlackVar(index))\n", + " # Add time window constraints for each vehicle start node\n", + " # and 'copy' the slack var in the solution object (aka Assignment) to print it\n", + " for vehicle_id in xrange(data['num_vehicles']):\n", + " index = routing.Start(vehicle_id)\n", + " time_dimension.CumulVar(index).SetRange(data['time_windows'][0][0],\n", + " data['time_windows'][0][1])\n", + " routing.AddToAssignment(time_dimension.SlackVar(index))\n", + " # Warning: Slack var is not defined for vehicle's end node\n", + " #routing.AddToAssignment(time_dimension.SlackVar(self.routing.End(vehicle_id)))\n", + "\n", + "\n", + "###########\n", + "# Printer #\n", + "###########\n", + "def print_solution(data, manager, routing, assignment): # pylint:disable=too-many-locals\n", + " \"\"\"Prints assignment on console\"\"\"\n", + " print('Objective: {}'.format(assignment.ObjectiveValue()))\n", + " total_distance = 0\n", + " total_load = 0\n", + " total_time = 0\n", + " capacity_dimension = routing.GetDimensionOrDie('Capacity')\n", + " time_dimension = routing.GetDimensionOrDie('Time')\n", + " dropped = []\n", + " for order in xrange(0, routing.nodes()):\n", + " index = manager.NodeToIndex(order)\n", + " if assignment.Value(routing.NextVar(index)) == index:\n", + " dropped.append(order)\n", + " print('dropped orders: {}'.format(dropped))\n", + "\n", + " for vehicle_id in xrange(data['num_vehicles']):\n", + " index = routing.Start(vehicle_id)\n", + " plan_output = 'Route for vehicle {}:\\n'.format(vehicle_id)\n", + " distance = 0\n", + " while not routing.IsEnd(index):\n", + " load_var = capacity_dimension.CumulVar(index)\n", + " time_var = time_dimension.CumulVar(index)\n", + " plan_output += ' {0} Load({1}) Time({2},{3}) ->'.format(\n", + " manager.IndexToNode(index),\n", + " assignment.Value(load_var),\n", + " assignment.Min(time_var), assignment.Max(time_var))\n", + " previous_index = index\n", + " index = assignment.Value(routing.NextVar(index))\n", + " distance += routing.GetArcCostForVehicle(previous_index, index,\n", + " vehicle_id)\n", + " load_var = capacity_dimension.CumulVar(index)\n", + " time_var = time_dimension.CumulVar(index)\n", + " plan_output += ' {0} Load({1}) Time({2},{3})\\n'.format(\n", + " manager.IndexToNode(index),\n", + " assignment.Value(load_var),\n", + " assignment.Min(time_var), assignment.Max(time_var))\n", + " plan_output += 'Distance of the route: {}m\\n'.format(distance)\n", + " plan_output += 'Load of the route: {}\\n'.format(\n", + " assignment.Value(load_var))\n", + " plan_output += 'Time of the route: {}min\\n'.format(\n", + " assignment.Value(time_var))\n", + " print(plan_output)\n", + " total_distance += distance\n", + " total_load += assignment.Value(load_var)\n", + " total_time += assignment.Value(time_var)\n", + " print('Total Distance of all routes: {}m'.format(total_distance))\n", + " print('Total Load of all routes: {}'.format(total_load))\n", + " print('Total Time of all routes: {}min'.format(total_time))\n", + "\n", + "\n", + "########\n", + "# Main #\n", + "########\n", + "\"\"\"Entry point of the program\"\"\"\n", + "# Instantiate the data problem.\n", + "data = create_data_model()\n", + "\n", + "# Create the routing index manager\n", + "manager = pywrapcp.RoutingIndexManager(data['num_locations'],\n", + " data['num_vehicles'], data['depot'])\n", + "\n", + "# Create Routing Model\n", + "routing = pywrapcp.RoutingModel(manager)\n", + "\n", + "# Define weight of each edge\n", + "distance_evaluator_index = routing.RegisterTransitCallback(\n", + " partial(create_distance_evaluator(data), manager))\n", + "routing.SetArcCostEvaluatorOfAllVehicles(distance_evaluator_index)\n", + "\n", + "# Add Distance constraint to minimize the longuest route\n", + "add_distance_dimension(routing, distance_evaluator_index)\n", + "\n", + "# Add Capacity constraint\n", + "demand_evaluator_index = routing.RegisterUnaryTransitCallback(\n", + " partial(create_demand_evaluator(data), manager))\n", + "add_capacity_constraints(routing, manager, data, demand_evaluator_index)\n", + "\n", + "# Add Time Window constraint\n", + "time_evaluator_index = routing.RegisterTransitCallback(\n", + " partial(create_time_evaluator(data), manager))\n", + "add_time_window_constraints(routing, manager, data, time_evaluator_index)\n", + "\n", + "# Setting first solution heuristic (cheapest addition).\n", + "search_parameters = pywrapcp.DefaultRoutingSearchParameters()\n", + "search_parameters.first_solution_strategy = (\n", + " routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC) # pylint: disable=no-member\n", + "# Solve the problem.\n", + "assignment = routing.SolveWithParameters(search_parameters)\n", + "print_solution(data, manager, routing, assignment)\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/constraint_solver/cvrptw.ipynb b/examples/notebook/constraint_solver/cvrptw.ipynb new file mode 100644 index 0000000000..ed349c1c65 --- /dev/null +++ b/examples/notebook/constraint_solver/cvrptw.ipynb @@ -0,0 +1,305 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "#!/usr/bin/env python\n", + "# This Python file uses the following encoding: utf-8\n", + "# Copyright 2015 Tin Arm Engineering AB\n", + "# Copyright 2018 Google LLC\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"Capacitated Vehicle Routing Problem with Time Windows (CVRPTW).\n", + "\n", + " This is a sample using the routing library python wrapper to solve a CVRPTW\n", + " problem.\n", + " A description of the problem can be found here:\n", + " http://en.wikipedia.org/wiki/Vehicle_routing_problem.\n", + "\n", + " Distances are in meters and time in minutes.\n", + "\"\"\"\n", + "\n", + "from __future__ import print_function\n", + "\n", + "from functools import partial\n", + "from six.moves import xrange\n", + "\n", + "from ortools.constraint_solver import pywrapcp\n", + "from ortools.constraint_solver import routing_enums_pb2\n", + "\n", + "\n", + "###########################\n", + "# Problem Data Definition #\n", + "###########################\n", + "def create_data_model():\n", + " \"\"\"Stores the data for the problem\"\"\"\n", + " data = {}\n", + " # Locations in block unit\n", + " _locations = \\\n", + " [(4, 4), # depot\n", + " (2, 0), (8, 0), # locations to visit\n", + " (0, 1), (1, 1),\n", + " (5, 2), (7, 2),\n", + " (3, 3), (6, 3),\n", + " (5, 5), (8, 5),\n", + " (1, 6), (2, 6),\n", + " (3, 7), (6, 7),\n", + " (0, 8), (7, 8)]\n", + " # Compute locations in meters using the block dimension defined as follow\n", + " # Manhattan average block: 750ft x 264ft -> 228m x 80m\n", + " # here we use: 114m x 80m city block\n", + " # src: https://nyti.ms/2GDoRIe \"NY Times: Know Your distance\"\n", + " data['locations'] = [(l[0] * 114, l[1] * 80) for l in _locations]\n", + " data['num_locations'] = len(data['locations'])\n", + " data['time_windows'] = \\\n", + " [(0, 0),\n", + " (75, 85), (75, 85), # 1, 2\n", + " (60, 70), (45, 55), # 3, 4\n", + " (0, 8), (50, 60), # 5, 6\n", + " (0, 10), (10, 20), # 7, 8\n", + " (0, 10), (75, 85), # 9, 10\n", + " (85, 95), (5, 15), # 11, 12\n", + " (15, 25), (10, 20), # 13, 14\n", + " (45, 55), (30, 40)] # 15, 16\n", + " data['demands'] = \\\n", + " [0, # depot\n", + " 1, 1, # 1, 2\n", + " 2, 4, # 3, 4\n", + " 2, 4, # 5, 6\n", + " 8, 8, # 7, 8\n", + " 1, 2, # 9,10\n", + " 1, 2, # 11,12\n", + " 4, 4, # 13, 14\n", + " 8, 8] # 15, 16\n", + " data['time_per_demand_unit'] = 5 # 5 minutes/unit\n", + " data['num_vehicles'] = 4\n", + " data['vehicle_capacity'] = 15\n", + " data['vehicle_speed'] = 83 # Travel speed: 5km/h converted in m/min\n", + " data['depot'] = 0\n", + " return data\n", + "\n", + "\n", + "#######################\n", + "# Problem Constraints #\n", + "#######################\n", + "def manhattan_distance(position_1, position_2):\n", + " \"\"\"Computes the Manhattan distance between two points\"\"\"\n", + " return (\n", + " abs(position_1[0] - position_2[0]) + abs(position_1[1] - position_2[1]))\n", + "\n", + "\n", + "def create_distance_evaluator(data):\n", + " \"\"\"Creates callback to return distance between points.\"\"\"\n", + " _distances = {}\n", + " # precompute distance between location to have distance callback in O(1)\n", + " for from_node in xrange(data['num_locations']):\n", + " _distances[from_node] = {}\n", + " for to_node in xrange(data['num_locations']):\n", + " if from_node == to_node:\n", + " _distances[from_node][to_node] = 0\n", + " else:\n", + " _distances[from_node][to_node] = (manhattan_distance(\n", + " data['locations'][from_node], data['locations'][to_node]))\n", + "\n", + " def distance_evaluator(manager, from_node, to_node):\n", + " \"\"\"Returns the manhattan distance between the two nodes\"\"\"\n", + " return _distances[manager.IndexToNode(from_node)][manager.IndexToNode(\n", + " to_node)]\n", + "\n", + " return distance_evaluator\n", + "\n", + "\n", + "def create_demand_evaluator(data):\n", + " \"\"\"Creates callback to get demands at each location.\"\"\"\n", + " _demands = data['demands']\n", + "\n", + " def demand_evaluator(manager, node):\n", + " \"\"\"Returns the demand of the current node\"\"\"\n", + " return _demands[manager.IndexToNode(node)]\n", + "\n", + " return demand_evaluator\n", + "\n", + "\n", + "def add_capacity_constraints(routing, data, demand_evaluator_index):\n", + " \"\"\"Adds capacity constraint\"\"\"\n", + " capacity = 'Capacity'\n", + " routing.AddDimension(\n", + " demand_evaluator_index,\n", + " 0, # null capacity slack\n", + " data['vehicle_capacity'],\n", + " True, # start cumul to zero\n", + " capacity)\n", + "\n", + "\n", + "def create_time_evaluator(data):\n", + " \"\"\"Creates callback to get total times between locations.\"\"\"\n", + "\n", + " def service_time(data, node):\n", + " \"\"\"Gets the service time for the specified location.\"\"\"\n", + " return data['demands'][node] * data['time_per_demand_unit']\n", + "\n", + " def travel_time(data, from_node, to_node):\n", + " \"\"\"Gets the travel times between two locations.\"\"\"\n", + " if from_node == to_node:\n", + " travel_time = 0\n", + " else:\n", + " travel_time = manhattan_distance(data['locations'][from_node], data[\n", + " 'locations'][to_node]) / data['vehicle_speed']\n", + " return travel_time\n", + "\n", + " _total_time = {}\n", + " # precompute total time to have time callback in O(1)\n", + " for from_node in xrange(data['num_locations']):\n", + " _total_time[from_node] = {}\n", + " for to_node in xrange(data['num_locations']):\n", + " if from_node == to_node:\n", + " _total_time[from_node][to_node] = 0\n", + " else:\n", + " _total_time[from_node][to_node] = int(\n", + " service_time(data, from_node) + travel_time(\n", + " data, from_node, to_node))\n", + "\n", + " def time_evaluator(manager, from_node, to_node):\n", + " \"\"\"Returns the total time between the two nodes\"\"\"\n", + " return _total_time[manager.IndexToNode(from_node)][manager.IndexToNode(\n", + " to_node)]\n", + "\n", + " return time_evaluator\n", + "\n", + "\n", + "def add_time_window_constraints(routing, manager, data, time_evaluator_index):\n", + " \"\"\"Add Global Span constraint\"\"\"\n", + " time = 'Time'\n", + " horizon = 120\n", + " routing.AddDimension(\n", + " time_evaluator_index,\n", + " horizon, # allow waiting time\n", + " horizon, # maximum time per vehicle\n", + " False, # don't force start cumul to zero since we are giving TW to start nodes\n", + " time)\n", + " time_dimension = routing.GetDimensionOrDie(time)\n", + " # Add time window constraints for each location except depot\n", + " # and 'copy' the slack var in the solution object (aka Assignment) to print it\n", + " for location_idx, time_window in enumerate(data['time_windows']):\n", + " if location_idx == 0:\n", + " continue\n", + " index = manager.NodeToIndex(location_idx)\n", + " time_dimension.CumulVar(index).SetRange(time_window[0], time_window[1])\n", + " routing.AddToAssignment(time_dimension.SlackVar(index))\n", + " # Add time window constraints for each vehicle start node\n", + " # and 'copy' the slack var in the solution object (aka Assignment) to print it\n", + " for vehicle_id in xrange(data['num_vehicles']):\n", + " index = routing.Start(vehicle_id)\n", + " time_dimension.CumulVar(index).SetRange(data['time_windows'][0][0],\n", + " data['time_windows'][0][1])\n", + " routing.AddToAssignment(time_dimension.SlackVar(index))\n", + " # Warning: Slack var is not defined for vehicle's end node\n", + " #routing.AddToAssignment(time_dimension.SlackVar(self.routing.End(vehicle_id)))\n", + "\n", + "\n", + "###########\n", + "# Printer #\n", + "###########\n", + "def print_solution(data, manager, routing, assignment): # pylint:disable=too-many-locals\n", + " \"\"\"Prints assignment on console\"\"\"\n", + " print('Objective: {}'.format(assignment.ObjectiveValue()))\n", + " total_distance = 0\n", + " total_load = 0\n", + " total_time = 0\n", + " capacity_dimension = routing.GetDimensionOrDie('Capacity')\n", + " time_dimension = routing.GetDimensionOrDie('Time')\n", + " for vehicle_id in xrange(data['num_vehicles']):\n", + " index = routing.Start(vehicle_id)\n", + " plan_output = 'Route for vehicle {}:\\n'.format(vehicle_id)\n", + " distance = 0\n", + " while not routing.IsEnd(index):\n", + " load_var = capacity_dimension.CumulVar(index)\n", + " time_var = time_dimension.CumulVar(index)\n", + " slack_var = time_dimension.SlackVar(index)\n", + " plan_output += ' {0} Load({1}) Time({2},{3}) Slack({4},{5}) ->'.format(\n", + " manager.IndexToNode(index),\n", + " assignment.Value(load_var),\n", + " assignment.Min(time_var),\n", + " assignment.Max(time_var),\n", + " assignment.Min(slack_var), assignment.Max(slack_var))\n", + " previous_index = index\n", + " index = assignment.Value(routing.NextVar(index))\n", + " distance += routing.GetArcCostForVehicle(previous_index, index,\n", + " vehicle_id)\n", + " load_var = capacity_dimension.CumulVar(index)\n", + " time_var = time_dimension.CumulVar(index)\n", + " slack_var = time_dimension.SlackVar(index)\n", + " plan_output += ' {0} Load({1}) Time({2},{3})\\n'.format(\n", + " manager.IndexToNode(index),\n", + " assignment.Value(load_var),\n", + " assignment.Min(time_var), assignment.Max(time_var))\n", + " plan_output += 'Distance of the route: {0}m\\n'.format(distance)\n", + " plan_output += 'Load of the route: {}\\n'.format(\n", + " assignment.Value(load_var))\n", + " plan_output += 'Time of the route: {}\\n'.format(\n", + " assignment.Value(time_var))\n", + " print(plan_output)\n", + " total_distance += distance\n", + " total_load += assignment.Value(load_var)\n", + " total_time += assignment.Value(time_var)\n", + " print('Total Distance of all routes: {0}m'.format(total_distance))\n", + " print('Total Load of all routes: {}'.format(total_load))\n", + " print('Total Time of all routes: {0}min'.format(total_time))\n", + "\n", + "\n", + "########\n", + "# Main #\n", + "########\n", + "\"\"\"Entry point of the program\"\"\"\n", + "# Instantiate the data problem.\n", + "data = create_data_model()\n", + "\n", + "# Create the routing index manager\n", + "manager = pywrapcp.RoutingIndexManager(data['num_locations'],\n", + " data['num_vehicles'], data['depot'])\n", + "\n", + "# Create Routing Model\n", + "routing = pywrapcp.RoutingModel(manager)\n", + "\n", + "# Define weight of each edge\n", + "distance_evaluator_index = routing.RegisterTransitCallback(\n", + " partial(create_distance_evaluator(data), manager))\n", + "routing.SetArcCostEvaluatorOfAllVehicles(distance_evaluator_index)\n", + "\n", + "# Add Capacity constraint\n", + "demand_evaluator_index = routing.RegisterUnaryTransitCallback(\n", + " partial(create_demand_evaluator(data), manager))\n", + "add_capacity_constraints(routing, data, demand_evaluator_index)\n", + "\n", + "# Add Time Window constraint\n", + "time_evaluator_index = routing.RegisterTransitCallback(\n", + " partial(create_time_evaluator(data), manager))\n", + "add_time_window_constraints(routing, manager, data, time_evaluator_index)\n", + "\n", + "# Setting first solution heuristic (cheapest addition).\n", + "search_parameters = pywrapcp.DefaultRoutingSearchParameters()\n", + "search_parameters.first_solution_strategy = (\n", + " routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC) # pylint: disable=no-member\n", + "# Solve the problem.\n", + "assignment = routing.SolveWithParameters(search_parameters)\n", + "print_solution(data, manager, routing, assignment)\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/constraint_solver/cvrptw_break.ipynb b/examples/notebook/constraint_solver/cvrptw_break.ipynb new file mode 100644 index 0000000000..4e15703fa8 --- /dev/null +++ b/examples/notebook/constraint_solver/cvrptw_break.ipynb @@ -0,0 +1,340 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "#!/usr/bin/env python\n", + "# This Python file uses the following encoding: utf-8\n", + "# Copyright 2015 Tin Arm Engineering AB\n", + "# Copyright 2018 Google LLC\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"Capacitated Vehicle Routing Problem with Time Windows (CVRPTW).\n", + "\n", + " This is a sample using the routing library python wrapper to solve a CVRPTW\n", + " problem.\n", + " A description of the problem can be found here:\n", + " http://en.wikipedia.org/wiki/Vehicle_routing_problem.\n", + "\n", + " Distances are in meters and time in minutes.\n", + "\"\"\"\n", + "\n", + "from __future__ import print_function\n", + "\n", + "from functools import partial\n", + "from six.moves import xrange\n", + "\n", + "from ortools.constraint_solver import pywrapcp\n", + "from ortools.constraint_solver import routing_enums_pb2\n", + "\n", + "\n", + "###########################\n", + "# Problem Data Definition #\n", + "###########################\n", + "def create_data_model():\n", + " \"\"\"Stores the data for the problem\"\"\"\n", + " data = {}\n", + " # Locations in block unit\n", + " _locations = \\\n", + " [(4, 4), # depot\n", + " (2, 0), (8, 0), # locations to visit\n", + " (0, 1), (1, 1),\n", + " (5, 2), (7, 2),\n", + " (3, 3), (6, 3),\n", + " (5, 5), (8, 5),\n", + " (1, 6), (2, 6),\n", + " (3, 7), (6, 7),\n", + " (0, 8), (7, 8)]\n", + " # Compute locations in meters using the block dimension defined as follow\n", + " # Manhattan average block: 750ft x 264ft -> 228m x 80m\n", + " # here we use: 114m x 80m city block\n", + " # src: https://nyti.ms/2GDoRIe \"NY Times: Know Your distance\"\n", + " data['locations'] = [(l[0] * 114, l[1] * 80) for l in _locations]\n", + " data['num_locations'] = len(data['locations'])\n", + " data['time_windows'] = \\\n", + " [(0, 0),\n", + " (75, 85), (75, 85), # 1, 2\n", + " (60, 70), (45, 55), # 3, 4\n", + " (0, 8), (50, 60), # 5, 6\n", + " (0, 10), (10, 20), # 7, 8\n", + " (0, 10), (75, 85), # 9, 10\n", + " (85, 95), (5, 15), # 11, 12\n", + " (15, 25), (10, 20), # 13, 14\n", + " (45, 55), (30, 40)] # 15, 16\n", + " data['demands'] = \\\n", + " [0, # depot\n", + " 1, 1, # 1, 2\n", + " 2, 4, # 3, 4\n", + " 2, 4, # 5, 6\n", + " 8, 8, # 7, 8\n", + " 1, 2, # 9,10\n", + " 1, 2, # 11,12\n", + " 4, 4, # 13, 14\n", + " 8, 8] # 15, 16\n", + " data['time_per_demand_unit'] = 5 # 5 minutes/unit\n", + " data['num_vehicles'] = 4\n", + " data['breaks'] = [(2, False), (2, False), (2, False), (2, False)]\n", + " data['vehicle_capacity'] = 15\n", + " data['vehicle_speed'] = 83 # Travel speed: 5km/h converted in m/min\n", + " data['depot'] = 0\n", + " return data\n", + "\n", + "\n", + "#######################\n", + "# Problem Constraints #\n", + "#######################\n", + "def manhattan_distance(position_1, position_2):\n", + " \"\"\"Computes the Manhattan distance between two points\"\"\"\n", + " return (\n", + " abs(position_1[0] - position_2[0]) + abs(position_1[1] - position_2[1]))\n", + "\n", + "\n", + "def create_distance_evaluator(data):\n", + " \"\"\"Creates callback to return distance between points.\"\"\"\n", + " _distances = {}\n", + " # precompute distance between location to have distance callback in O(1)\n", + " for from_node in xrange(data['num_locations']):\n", + " _distances[from_node] = {}\n", + " for to_node in xrange(data['num_locations']):\n", + " if from_node == to_node:\n", + " _distances[from_node][to_node] = 0\n", + " else:\n", + " _distances[from_node][to_node] = (manhattan_distance(\n", + " data['locations'][from_node], data['locations'][to_node]))\n", + "\n", + " def distance_evaluator(manager, from_node, to_node):\n", + " \"\"\"Returns the manhattan distance between the two nodes\"\"\"\n", + " return _distances[manager.IndexToNode(from_node)][manager.IndexToNode(\n", + " to_node)]\n", + "\n", + " return distance_evaluator\n", + "\n", + "\n", + "def create_demand_evaluator(data):\n", + " \"\"\"Creates callback to get demands at each location.\"\"\"\n", + " _demands = data['demands']\n", + "\n", + " def demand_evaluator(manager, node):\n", + " \"\"\"Returns the demand of the current node\"\"\"\n", + " return _demands[manager.IndexToNode(node)]\n", + "\n", + " return demand_evaluator\n", + "\n", + "\n", + "def add_capacity_constraints(routing, data, demand_evaluator_index):\n", + " \"\"\"Adds capacity constraint\"\"\"\n", + " capacity = 'Capacity'\n", + " routing.AddDimension(\n", + " demand_evaluator_index,\n", + " 0, # null capacity slack\n", + " data['vehicle_capacity'],\n", + " True, # start cumul to zero\n", + " capacity)\n", + "\n", + "\n", + "def create_time_evaluator(data):\n", + " \"\"\"Creates callback to get total times between locations.\"\"\"\n", + "\n", + " def service_time(data, node):\n", + " \"\"\"Gets the service time for the specified location.\"\"\"\n", + " return data['demands'][node] * data['time_per_demand_unit']\n", + "\n", + " def travel_time(data, from_node, to_node):\n", + " \"\"\"Gets the travel times between two locations.\"\"\"\n", + " if from_node == to_node:\n", + " travel_time = 0\n", + " else:\n", + " travel_time = manhattan_distance(data['locations'][from_node], data[\n", + " 'locations'][to_node]) / data['vehicle_speed']\n", + " return travel_time\n", + "\n", + " _total_time = {}\n", + " # precompute total time to have time callback in O(1)\n", + " for from_node in xrange(data['num_locations']):\n", + " _total_time[from_node] = {}\n", + " for to_node in xrange(data['num_locations']):\n", + " if from_node == to_node:\n", + " _total_time[from_node][to_node] = 0\n", + " else:\n", + " _total_time[from_node][to_node] = int(\n", + " service_time(data, from_node) + travel_time(\n", + " data, from_node, to_node))\n", + "\n", + " def time_evaluator(manager, from_node, to_node):\n", + " \"\"\"Returns the total time between the two nodes\"\"\"\n", + " return _total_time[manager.IndexToNode(from_node)][manager.IndexToNode(\n", + " to_node)]\n", + "\n", + " return time_evaluator\n", + "\n", + "\n", + "def add_time_window_constraints(routing, manager, data, time_evaluator_index):\n", + " \"\"\"Add Global Span constraint\"\"\"\n", + " time = 'Time'\n", + " horizon = 120\n", + " routing.AddDimension(\n", + " time_evaluator_index,\n", + " horizon, # allow waiting time\n", + " horizon, # maximum time per vehicle\n", + " False, # don't force start cumul to zero since we are giving TW to start nodes\n", + " time)\n", + " time_dimension = routing.GetDimensionOrDie(time)\n", + " # Add time window constraints for each location except depot\n", + " # and 'copy' the slack var in the solution object (aka Assignment) to print it\n", + " for location_idx, time_window in enumerate(data['time_windows']):\n", + " if location_idx == 0:\n", + " continue\n", + " index = manager.NodeToIndex(location_idx)\n", + " time_dimension.CumulVar(index).SetRange(time_window[0], time_window[1])\n", + " routing.AddToAssignment(time_dimension.SlackVar(index))\n", + " # Add time window constraints for each vehicle start node\n", + " # and 'copy' the slack var in the solution object (aka Assignment) to print it\n", + " for vehicle_id in xrange(data['num_vehicles']):\n", + " index = routing.Start(vehicle_id)\n", + " time_dimension.CumulVar(index).SetRange(data['time_windows'][0][0],\n", + " data['time_windows'][0][1])\n", + " routing.AddToAssignment(time_dimension.SlackVar(index))\n", + " # Warning: Slack var is not defined for vehicle's end node\n", + " #routing.AddToAssignment(time_dimension.SlackVar(self.routing.End(vehicle_id)))\n", + "\n", + "\n", + "###########\n", + "# Printer #\n", + "###########\n", + "def print_solution(data, manager, routing, assignment): # pylint:disable=too-many-locals\n", + " \"\"\"Prints assignment on console\"\"\"\n", + " print('Objective: {}'.format(assignment.ObjectiveValue()))\n", + "\n", + " print('Breaks:')\n", + " intervals = assignment.IntervalVarContainer()\n", + " for i in xrange(intervals.Size()):\n", + " brk = intervals.Element(i)\n", + " if brk.PerformedValue() == 1:\n", + " print('{}: Start({}) Duration({})'.format(\n", + " brk.Var().Name(),\n", + " brk.StartValue(),\n", + " brk.DurationValue()))\n", + " else:\n", + " print('{}: Unperformed'.format(brk.Var().Name()))\n", + "\n", + " total_distance = 0\n", + " total_load = 0\n", + " total_time = 0\n", + " capacity_dimension = routing.GetDimensionOrDie('Capacity')\n", + " time_dimension = routing.GetDimensionOrDie('Time')\n", + " for vehicle_id in xrange(data['num_vehicles']):\n", + " index = routing.Start(vehicle_id)\n", + " plan_output = 'Route for vehicle {}:\\n'.format(vehicle_id)\n", + " distance = 0\n", + " while not routing.IsEnd(index):\n", + " load_var = capacity_dimension.CumulVar(index)\n", + " time_var = time_dimension.CumulVar(index)\n", + " slack_var = time_dimension.SlackVar(index)\n", + " plan_output += ' {0} Load({1}) Time({2},{3}) Slack({4},{5}) ->'.format(\n", + " manager.IndexToNode(index),\n", + " assignment.Value(load_var),\n", + " assignment.Min(time_var),\n", + " assignment.Max(time_var),\n", + " assignment.Min(slack_var), assignment.Max(slack_var))\n", + " previous_index = index\n", + " index = assignment.Value(routing.NextVar(index))\n", + " distance += routing.GetArcCostForVehicle(previous_index, index,\n", + " vehicle_id)\n", + " load_var = capacity_dimension.CumulVar(index)\n", + " time_var = time_dimension.CumulVar(index)\n", + " slack_var = time_dimension.SlackVar(index)\n", + " plan_output += ' {0} Load({1}) Time({2},{3})\\n'.format(\n", + " manager.IndexToNode(index),\n", + " assignment.Value(load_var),\n", + " assignment.Min(time_var), assignment.Max(time_var))\n", + " plan_output += 'Distance of the route: {0}m\\n'.format(distance)\n", + " plan_output += 'Load of the route: {}\\n'.format(\n", + " assignment.Value(load_var))\n", + " plan_output += 'Time of the route: {}\\n'.format(\n", + " assignment.Value(time_var))\n", + " print(plan_output)\n", + " total_distance += distance\n", + " total_load += assignment.Value(load_var)\n", + " total_time += assignment.Value(time_var)\n", + " print('Total Distance of all routes: {0}m'.format(total_distance))\n", + " print('Total Load of all routes: {}'.format(total_load))\n", + " print('Total Time of all routes: {0}min'.format(total_time))\n", + "\n", + "\n", + "########\n", + "# Main #\n", + "########\n", + "\"\"\"Entry point of the program\"\"\"\n", + "# Instantiate the data problem.\n", + "data = create_data_model()\n", + "\n", + "# Create the routing index manager\n", + "manager = pywrapcp.RoutingIndexManager(data['num_locations'],\n", + " data['num_vehicles'], data['depot'])\n", + "\n", + "# Create Routing Model\n", + "routing = pywrapcp.RoutingModel(manager)\n", + "\n", + "# Define weight of each edge\n", + "distance_evaluator_index = routing.RegisterTransitCallback(\n", + " partial(create_distance_evaluator(data), manager))\n", + "routing.SetArcCostEvaluatorOfAllVehicles(distance_evaluator_index)\n", + "\n", + "# Add Capacity constraint\n", + "demand_evaluator_index = routing.RegisterUnaryTransitCallback(\n", + " partial(create_demand_evaluator(data), manager))\n", + "add_capacity_constraints(routing, data, demand_evaluator_index)\n", + "\n", + "# Add Time Window constraint\n", + "time_evaluator_index = routing.RegisterTransitCallback(\n", + " partial(create_time_evaluator(data), manager))\n", + "add_time_window_constraints(routing, manager, data, time_evaluator_index)\n", + "\n", + "# Add breaks\n", + "time_dimension = routing.GetDimensionOrDie(\"Time\")\n", + "node_visit_transit = {}\n", + "for n in xrange(routing.Size()):\n", + " if n >= data['num_locations']:\n", + " node_visit_transit[n] = 0\n", + " else:\n", + " node_visit_transit[n] = int(\n", + " data['demands'][n] * data['time_per_demand_unit'])\n", + "\n", + "break_intervals = {}\n", + "#for v in xrange(data['num_vehicles']):\n", + "for v in [0]:\n", + " vehicle_break = data['breaks'][v]\n", + " break_intervals[v] = [\n", + " routing.solver().FixedDurationIntervalVar(\n", + " 15, 100, vehicle_break[0], vehicle_break[1], 'Break for vehicle {}'.format(v))\n", + " ]\n", + " time_dimension.SetBreakIntervalsOfVehicle(\n", + " break_intervals[v], v, node_visit_transit)\n", + "\n", + "# Setting first solution heuristic (cheapest addition).\n", + "search_parameters = pywrapcp.DefaultRoutingSearchParameters()\n", + "search_parameters.first_solution_strategy = (\n", + " routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC) # pylint: disable=no-member\n", + "# Solve the problem.\n", + "assignment = routing.SolveWithParameters(search_parameters)\n", + "print_solution(data, manager, routing, assignment)\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/constraint_solver/simple_cp_program.ipynb b/examples/notebook/constraint_solver/simple_cp_program.ipynb new file mode 100644 index 0000000000..4e0da87834 --- /dev/null +++ b/examples/notebook/constraint_solver/simple_cp_program.ipynb @@ -0,0 +1,82 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010-2018 Google LLC\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "# [START program]\n", + "\"\"\"Simple Constraint optimization example.\"\"\"\n", + "\n", + "# [START import]\n", + "from __future__ import print_function\n", + "from ortools.constraint_solver import pywrapcp\n", + "# [END import]\n", + "\n", + "\n", + "\"\"\"Entry point of the program.\"\"\"\n", + "# Instantiate the solver.\n", + "# [START solver]\n", + "solver = pywrapcp.Solver('CPSimple')\n", + "# [END solver]\n", + "\n", + "# Create the variables.\n", + "# [START variables]\n", + "num_vals = 3\n", + "x = solver.IntVar(0, num_vals - 1, 'x')\n", + "y = solver.IntVar(0, num_vals - 1, 'y')\n", + "z = solver.IntVar(0, num_vals - 1, 'z')\n", + "# [END variables]\n", + "\n", + "# Constraint 0: x != y.\n", + "# [START constraints]\n", + "solver.Add(x != y)\n", + "print('Number of constraints: ', solver.Constraints())\n", + "# [END constraints]\n", + "\n", + "# Solve the problem.\n", + "# [START solve]\n", + "decision_builder = solver.Phase([x, y, z], solver.CHOOSE_FIRST_UNBOUND,\n", + " solver.ASSIGN_MIN_VALUE)\n", + "# [END solve]\n", + "\n", + "# Print solution on console.\n", + "# [START print_solution]\n", + "count = 0\n", + "solver.NewSearch(decision_builder)\n", + "while solver.NextSolution():\n", + " count += 1\n", + " solution = 'Solution {}:\\n'.format(count)\n", + " for var in [x, y, z]:\n", + " solution += ' {} = {}'.format(var.Name(), var.Value())\n", + " print(solution)\n", + "solver.EndSearch()\n", + "print('Number of solutions found: ', count)\n", + "# [END print_solution]\n", + "\n", + "# [START advanced]\n", + "print('Advanced usage:')\n", + "print('Problem solved in ', solver.WallTime(), 'ms')\n", + "print('Memory usage: ', pywrapcp.Solver.MemoryUsage(), 'bytes')\n", + "# [END advanced]\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/constraint_solver/simple_routing_program.ipynb b/examples/notebook/constraint_solver/simple_routing_program.ipynb new file mode 100644 index 0000000000..8c96e34bd9 --- /dev/null +++ b/examples/notebook/constraint_solver/simple_routing_program.ipynb @@ -0,0 +1,102 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010-2018 Google LLC\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "# [START program]\n", + "\"\"\"Vehicle Routing example.\"\"\"\n", + "\n", + "# [START import]\n", + "from __future__ import print_function\n", + "from ortools.constraint_solver import routing_enums_pb2\n", + "from ortools.constraint_solver import pywrapcp\n", + "from ortools.constraint_solver import pywrapcp\n", + "# [END import]\n", + "\n", + "\n", + "\"\"\"Entry point of the program.\"\"\"\n", + "# Instantiate the data problem.\n", + "# [START data]\n", + "num_locations = 5\n", + "num_vehicles = 1\n", + "depot = 0\n", + "# [END data]\n", + "\n", + "# Create the routing index manager.\n", + "# [START index_manager]\n", + "manager = pywrapcp.RoutingIndexManager(num_locations, num_vehicles, depot)\n", + "# [END index_manager]\n", + "\n", + "# Create Routing Model.\n", + "# [START routing_model]\n", + "routing = pywrapcp.RoutingModel(manager)\n", + "\n", + "# [END routing_model]\n", + "\n", + "# Create and register a transit callback.\n", + "# [START transit_callback]\n", + "def distance_callback(from_index, to_index):\n", + " \"\"\"Returns the absolute difference between the two nodes.\"\"\"\n", + " # Convert from routing variable Index to user NodeIndex.\n", + " from_node = int(manager.IndexToNode(from_index))\n", + " to_node = int(manager.IndexToNode(to_index))\n", + " return abs(to_node - from_node)\n", + "\n", + "transit_callback_index = routing.RegisterTransitCallback(distance_callback)\n", + "# [END transit_callback]\n", + "\n", + "# Define cost of each arc.\n", + "# [START arc_cost]\n", + "routing.SetArcCostEvaluatorOfAllVehicles(transit_callback_index)\n", + "# [END arc_cost]\n", + "\n", + "# Setting first solution heuristic.\n", + "# [START parameters]\n", + "search_parameters = pywrapcp.DefaultRoutingSearchParameters()\n", + "search_parameters.first_solution_strategy = (\n", + " routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC) # pylint: disable=no-member\n", + "# [END parameters]\n", + "\n", + "# Solve the problem.\n", + "# [START solve]\n", + "assignment = routing.SolveWithParameters(search_parameters)\n", + "# [END solve]\n", + "\n", + "# Print solution on console.\n", + "# [START print_solution]\n", + "print('Objective: {}'.format(assignment.ObjectiveValue()))\n", + "index = routing.Start(0)\n", + "plan_output = 'Route for vehicle 0:\\n'\n", + "route_distance = 0\n", + "while not routing.IsEnd(index):\n", + " plan_output += '{} -> '.format(manager.IndexToNode(index))\n", + " previous_index = index\n", + " index = assignment.Value(routing.NextVar(index))\n", + " route_distance += routing.GetArcCostForVehicle(previous_index, index, 0)\n", + "plan_output += '{}\\n'.format(manager.IndexToNode(index))\n", + "plan_output += 'Distance of the route: {}m\\n'.format(route_distance)\n", + "print(plan_output)\n", + "# [END print_solution]\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/constraint_solver/tsp.ipynb b/examples/notebook/constraint_solver/tsp.ipynb new file mode 100644 index 0000000000..015512484d --- /dev/null +++ b/examples/notebook/constraint_solver/tsp.ipynb @@ -0,0 +1,155 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010-2018 Google LLC\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "# [START program]\n", + "\"\"\"Simple Travelling Salesman Problem.\n", + "\n", + "A description of the problem can be found here:\n", + "http://en.wikipedia.org/wiki/Travelling_salesman_problem.\n", + "\"\"\"\n", + "\n", + "# [START import]\n", + "from __future__ import print_function\n", + "from ortools.constraint_solver import routing_enums_pb2\n", + "from ortools.constraint_solver import pywrapcp\n", + "# [END import]\n", + "\n", + "\n", + "# [START data_model]\n", + "def create_data_model():\n", + " \"\"\"Stores the data for the problem.\"\"\"\n", + " data = {}\n", + " # Locations in block units\n", + " locations = \\\n", + " [(4, 4), # depot\n", + " (2, 0), (8, 0), # locations to visit\n", + " (0, 1), (1, 1),\n", + " (5, 2), (7, 2),\n", + " (3, 3), (6, 3),\n", + " (5, 5), (8, 5),\n", + " (1, 6), (2, 6),\n", + " (3, 7), (6, 7),\n", + " (0, 8), (7, 8),]\n", + " # Convert locations in meters using a city block dimension of 114m x 80m.\n", + " data['locations'] = [(l[0] * 114, l[1] * 80) for l in locations]\n", + " data['num_vehicles'] = 1\n", + " data['depot'] = 0\n", + " return data\n", + " # [END data_model]\n", + "\n", + "\n", + "# [START distance_callback]\n", + "def create_distance_callback(data, manager):\n", + " \"\"\"Creates callback to return distance between points.\"\"\"\n", + " distances_ = {}\n", + " index_manager_ = manager\n", + " # precompute distance between location to have distance callback in O(1)\n", + " for from_counter, from_node in enumerate(data['locations']):\n", + " distances_[from_counter] = {}\n", + " for to_counter, to_node in enumerate(data['locations']):\n", + " if from_counter == to_counter:\n", + " distances_[from_counter][to_counter] = 0\n", + " else:\n", + " distances_[from_counter][to_counter] = (\n", + " abs(from_node[0] - to_node[0]) +\n", + " abs(from_node[1] - to_node[1]))\n", + "\n", + " def distance_callback(from_index, to_index):\n", + " \"\"\"Returns the manhattan distance between the two nodes.\"\"\"\n", + " # Convert from routing variable Index to distance matrix NodeIndex.\n", + " from_node = index_manager_.IndexToNode(from_index)\n", + " to_node = index_manager_.IndexToNode(to_index)\n", + " return distances_[from_node][to_node]\n", + "\n", + " return distance_callback\n", + " # [END distance_callback]\n", + "\n", + "\n", + "# [START solution_printer]\n", + "def print_solution(manager, routing, assignment):\n", + " \"\"\"Prints assignment on console.\"\"\"\n", + " print('Objective: {}'.format(assignment.ObjectiveValue()))\n", + " index = routing.Start(0)\n", + " plan_output = 'Route for vehicle 0:\\n'\n", + " route_distance = 0\n", + " while not routing.IsEnd(index):\n", + " plan_output += ' {} ->'.format(manager.IndexToNode(index))\n", + " previous_index = index\n", + " index = assignment.Value(routing.NextVar(index))\n", + " route_distance += routing.GetArcCostForVehicle(previous_index, index, 0)\n", + " plan_output += ' {}\\n'.format(manager.IndexToNode(index))\n", + " plan_output += 'Distance of the route: {}m\\n'.format(route_distance)\n", + " print(plan_output)\n", + " # [END solution_printer]\n", + "\n", + "\n", + "\"\"\"Entry point of the program.\"\"\"\n", + "# Instantiate the data problem.\n", + "# [START data]\n", + "data = create_data_model()\n", + "# [END data]\n", + "\n", + "# Create the routing index manager.\n", + "# [START index_manager]\n", + "manager = pywrapcp.RoutingIndexManager(len(data['locations']),\n", + " data['num_vehicles'], data['depot'])\n", + "# [END index_manager]\n", + "\n", + "# Create Routing Model.\n", + "# [START routing_model]\n", + "routing = pywrapcp.RoutingModel(manager)\n", + "# [END routing_model]\n", + "\n", + "# Create and register a transit callback.\n", + "# [START transit_callback]\n", + "distance_callback = create_distance_callback(data, manager)\n", + "transit_callback_index = routing.RegisterTransitCallback(distance_callback)\n", + "# [END transit_callback]\n", + "\n", + "# Define cost of each arc.\n", + "# [START arc_cost]\n", + "routing.SetArcCostEvaluatorOfAllVehicles(transit_callback_index)\n", + "# [END arc_cost]\n", + "\n", + "# Setting first solution heuristic.\n", + "# [START parameters]\n", + "search_parameters = pywrapcp.DefaultRoutingSearchParameters()\n", + "search_parameters.first_solution_strategy = (\n", + " routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC)\n", + "# [END parameters]\n", + "\n", + "# Solve the problem.\n", + "# [START solve]\n", + "assignment = routing.SolveWithParameters(search_parameters)\n", + "# [END solve]\n", + "\n", + "# Print solution on console.\n", + "# [START print_solution]\n", + "if assignment:\n", + " print_solution(manager, routing, assignment)\n", + "# [END print_solution]\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/constraint_solver/tsp_circuit_board.ipynb b/examples/notebook/constraint_solver/tsp_circuit_board.ipynb new file mode 100644 index 0000000000..0d34113fa5 --- /dev/null +++ b/examples/notebook/constraint_solver/tsp_circuit_board.ipynb @@ -0,0 +1,187 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010-2018 Google LLC\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "# [START program]\n", + "\"\"\"Simple travelling salesman problem on a circuit board.\"\"\"\n", + "\n", + "# [START import]\n", + "from __future__ import print_function\n", + "import math\n", + "from ortools.constraint_solver import routing_enums_pb2\n", + "from ortools.constraint_solver import pywrapcp\n", + "# [END import]\n", + "\n", + "\n", + "# [START data_model]\n", + "def create_data_model():\n", + " \"\"\"Stores the data for the problem.\"\"\"\n", + " data = {}\n", + " # Locations in block units\n", + " data['locations'] = [\n", + " (288, 149), (288, 129), (270, 133), (256, 141), (256, 157), (246, 157),\n", + " (236, 169), (228, 169), (228, 161), (220, 169), (212, 169), (204, 169),\n", + " (196, 169), (188, 169), (196, 161), (188, 145), (172, 145), (164, 145),\n", + " (156, 145), (148, 145), (140, 145), (148, 169), (164, 169), (172, 169),\n", + " (156, 169), (140, 169), (132, 169), (124, 169), (116, 161), (104, 153),\n", + " (104, 161), (104, 169), (90, 165), (80, 157), (64, 157), (64, 165),\n", + " (56, 169), (56, 161), (56, 153), (56, 145), (56, 137), (56, 129),\n", + " (56, 121), (40, 121), (40, 129), (40, 137), (40, 145), (40, 153),\n", + " (40, 161), (40, 169), (32, 169), (32, 161), (32, 153), (32, 145),\n", + " (32, 137), (32, 129), (32, 121), (32, 113), (40, 113), (56, 113),\n", + " (56, 105), (48, 99), (40, 99), (32, 97), (32, 89), (24, 89),\n", + " (16, 97), (16, 109), (8, 109), (8, 97), (8, 89), (8, 81),\n", + " (8, 73), (8, 65), (8, 57), (16, 57), (8, 49), (8, 41),\n", + " (24, 45), (32, 41), (32, 49), (32, 57), (32, 65), (32, 73),\n", + " (32, 81), (40, 83), (40, 73), (40, 63), (40, 51), (44, 43),\n", + " (44, 35), (44, 27), (32, 25), (24, 25), (16, 25), (16, 17),\n", + " (24, 17), (32, 17), (44, 11), (56, 9), (56, 17), (56, 25),\n", + " (56, 33), (56, 41), (64, 41), (72, 41), (72, 49), (56, 49),\n", + " (48, 51), (56, 57), (56, 65), (48, 63), (48, 73), (56, 73),\n", + " (56, 81), (48, 83), (56, 89), (56, 97), (104, 97), (104, 105),\n", + " (104, 113), (104, 121), (104, 129), (104, 137), (104, 145), (116, 145),\n", + " (124, 145), (132, 145), (132, 137), (140, 137), (148, 137), (156, 137),\n", + " (164, 137), (172, 125), (172, 117), (172, 109), (172, 101), (172, 93),\n", + " (172, 85), (180, 85), (180, 77), (180, 69), (180, 61), (180, 53),\n", + " (172, 53), (172, 61), (172, 69), (172, 77), (164, 81), (148, 85),\n", + " (124, 85), (124, 93), (124, 109), (124, 125), (124, 117), (124, 101),\n", + " (104, 89), (104, 81), (104, 73), (104, 65), (104, 49), (104, 41),\n", + " (104, 33), (104, 25), (104, 17), (92, 9), (80, 9), (72, 9),\n", + " (64, 21), (72, 25), (80, 25), (80, 25), (80, 41), (88, 49),\n", + " (104, 57), (124, 69), (124, 77), (132, 81), (140, 65), (132, 61),\n", + " (124, 61), (124, 53), (124, 45), (124, 37), (124, 29), (132, 21),\n", + " (124, 21), (120, 9), (128, 9), (136, 9), (148, 9), (162, 9),\n", + " (156, 25), (172, 21), (180, 21), (180, 29), (172, 29), (172, 37),\n", + " (172, 45), (180, 45), (180, 37), (188, 41), (196, 49), (204, 57),\n", + " (212, 65), (220, 73), (228, 69), (228, 77), (236, 77), (236, 69),\n", + " (236, 61), (228, 61), (228, 53), (236, 53), (236, 45), (228, 45),\n", + " (228, 37), (236, 37), (236, 29), (228, 29), (228, 21), (236, 21),\n", + " (252, 21), (260, 29), (260, 37), (260, 45), (260, 53), (260, 61),\n", + " (260, 69), (260, 77), (276, 77), (276, 69), (276, 61), (276, 53),\n", + " (284, 53), (284, 61), (284, 69), (284, 77), (284, 85), (284, 93),\n", + " (284, 101), (288, 109), (280, 109), (276, 101), (276, 93), (276, 85),\n", + " (268, 97), (260, 109), (252, 101), (260, 93), (260, 85), (236, 85),\n", + " (228, 85), (228, 93), (236, 93), (236, 101), (228, 101), (228, 109),\n", + " (228, 117), (228, 125), (220, 125), (212, 117), (204, 109), (196, 101),\n", + " (188, 93), (180, 93), (180, 101), (180, 109), (180, 117), (180, 125),\n", + " (196, 145), (204, 145), (212, 145), (220, 145), (228, 145), (236, 145),\n", + " (246, 141), (252, 125), (260, 129), (280, 133)\n", + " ] # yapf: disable\n", + " data['num_vehicles'] = 1\n", + " data['depot'] = 0\n", + " return data\n", + " # [END data_model]\n", + "\n", + "\n", + "# [START distance_callback]\n", + "def compute_euclidean_distance_matrix(locations):\n", + " \"\"\"Creates callback to return distance between points.\"\"\"\n", + " distances = {}\n", + " for from_counter, from_node in enumerate(locations):\n", + " distances[from_counter] = {}\n", + " for to_counter, to_node in enumerate(locations):\n", + " if from_counter == to_counter:\n", + " distances[from_counter][to_counter] = 0\n", + " else:\n", + " # Euclidean distance\n", + " distances[from_counter][to_counter] = (int(\n", + " math.hypot((from_node[0] - to_node[0]),\n", + " (from_node[1] - to_node[1]))))\n", + " return distances\n", + " # [END distance_callback]\n", + "\n", + "\n", + "# [START solution_printer]\n", + "def print_solution(manager, routing, assignment):\n", + " \"\"\"Prints assignment on console.\"\"\"\n", + " print('Objective: {}'.format(assignment.ObjectiveValue()))\n", + " index = routing.Start(0)\n", + " plan_output = 'Route:\\n'\n", + " route_distance = 0\n", + " while not routing.IsEnd(index):\n", + " plan_output += ' {} ->'.format(manager.IndexToNode(index))\n", + " previous_index = index\n", + " index = assignment.Value(routing.NextVar(index))\n", + " route_distance += routing.GetArcCostForVehicle(previous_index, index, 0)\n", + " plan_output += ' {}\\n'.format(manager.IndexToNode(index))\n", + " print(plan_output)\n", + " plan_output += 'Objective: {}m\\n'.format(route_distance)\n", + " # [END solution_printer]\n", + "\n", + "\n", + "\"\"\"Entry point of the program.\"\"\"\n", + "# Instantiate the data problem.\n", + "# [START data]\n", + "data = create_data_model()\n", + "# [END data]\n", + "\n", + "# Create the routing index manager.\n", + "# [START index_manager]\n", + "manager = pywrapcp.RoutingIndexManager(len(data['locations']),\n", + " data['num_vehicles'], data['depot'])\n", + "# [END index_manager]\n", + "\n", + "# Create Routing Model.\n", + "# [START routing_model]\n", + "routing = pywrapcp.RoutingModel(manager)\n", + "# [END routing_model]\n", + "\n", + "# [START transit_callback]\n", + "distance_matrix = compute_euclidean_distance_matrix(data['locations'])\n", + "\n", + "def distance_callback(from_index, to_index):\n", + " \"\"\"Returns the distance between the two nodes.\"\"\"\n", + " # Convert from routing variable Index to distance matrix NodeIndex.\n", + " from_node = manager.IndexToNode(from_index)\n", + " to_node = manager.IndexToNode(to_index)\n", + " return distance_matrix[from_node][to_node]\n", + "\n", + "transit_callback_index = routing.RegisterTransitCallback(distance_callback)\n", + "# [END transit_callback]\n", + "\n", + "# Define cost of each arc.\n", + "# [START arc_cost]\n", + "routing.SetArcCostEvaluatorOfAllVehicles(transit_callback_index)\n", + "# [END arc_cost]\n", + "\n", + "# Setting first solution heuristic.\n", + "# [START parameters]\n", + "search_parameters = pywrapcp.DefaultRoutingSearchParameters()\n", + "search_parameters.first_solution_strategy = (\n", + " routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC)\n", + "# [END parameters]\n", + "\n", + "# Solve the problem.\n", + "# [START solve]\n", + "assignment = routing.SolveWithParameters(search_parameters)\n", + "# [END solve]\n", + "\n", + "# Print solution on console.\n", + "# [START print_solution]\n", + "if assignment:\n", + " print_solution(manager, routing, assignment)\n", + "# [END print_solution]\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/constraint_solver/tsp_cities.ipynb b/examples/notebook/constraint_solver/tsp_cities.ipynb new file mode 100644 index 0000000000..e3fd95bb85 --- /dev/null +++ b/examples/notebook/constraint_solver/tsp_cities.ipynb @@ -0,0 +1,133 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010-2018 Google LLC\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "# [START program]\n", + "\"\"\"Simple travelling salesman problem between cities.\"\"\"\n", + "\n", + "# [START import]\n", + "from __future__ import print_function\n", + "from ortools.constraint_solver import routing_enums_pb2\n", + "from ortools.constraint_solver import pywrapcp\n", + "\n", + "# [END import]\n", + "\n", + "\n", + "# [START data_model]\n", + "def create_data_model():\n", + " \"\"\"Stores the data for the problem.\"\"\"\n", + " data = {}\n", + " data['distance_matrix'] = [\n", + " [0, 2451, 713, 1018, 1631, 1374, 2408, 213, 2571, 875, 1420, 2145, 1972],\n", + " [2451, 0, 1745, 1524, 831, 1240, 959, 2596, 403, 1589, 1374, 357, 579],\n", + " [713, 1745, 0, 355, 920, 803, 1737, 851, 1858, 262, 940, 1453, 1260],\n", + " [1018, 1524, 355, 0, 700, 862, 1395, 1123, 1584, 466, 1056, 1280, 987],\n", + " [1631, 831, 920, 700, 0, 663, 1021, 1769, 949, 796, 879, 586, 371],\n", + " [1374, 1240, 803, 862, 663, 0, 1681, 1551, 1765, 547, 225, 887, 999],\n", + " [2408, 959, 1737, 1395, 1021, 1681, 0, 2493, 678, 1724, 1891, 1114, 701],\n", + " [213, 2596, 851, 1123, 1769, 1551, 2493, 0, 2699, 1038, 1605, 2300, 2099],\n", + " [2571, 403, 1858, 1584, 949, 1765, 678, 2699, 0, 1744, 1645, 653, 600],\n", + " [875, 1589, 262, 466, 796, 547, 1724, 1038, 1744, 0, 679, 1272, 1162],\n", + " [1420, 1374, 940, 1056, 879, 225, 1891, 1605, 1645, 679, 0, 1017, 1200],\n", + " [2145, 357, 1453, 1280, 586, 887, 1114, 2300, 653, 1272, 1017, 0, 504],\n", + " [1972, 579, 1260, 987, 371, 999, 701, 2099, 600, 1162, 1200, 504, 0],\n", + " ] # yapf: disable\n", + " data['num_vehicles'] = 1\n", + " data['depot'] = 0\n", + " return data\n", + " # [END data_model]\n", + "\n", + "\n", + "# [START solution_printer]\n", + "def print_solution(manager, routing, assignment):\n", + " \"\"\"Prints assignment on console.\"\"\"\n", + " print('Objective: {} miles'.format(assignment.ObjectiveValue()))\n", + " index = routing.Start(0)\n", + " plan_output = 'Route for vehicle 0:\\n'\n", + " route_distance = 0\n", + " while not routing.IsEnd(index):\n", + " plan_output += ' {} ->'.format(manager.IndexToNode(index))\n", + " previous_index = index\n", + " index = assignment.Value(routing.NextVar(index))\n", + " route_distance += routing.GetArcCostForVehicle(previous_index, index, 0)\n", + " plan_output += ' {}\\n'.format(manager.IndexToNode(index))\n", + " print(plan_output)\n", + " plan_output += 'Route distance: {}miles\\n'.format(route_distance)\n", + " # [END solution_printer]\n", + "\n", + "\n", + "\"\"\"Entry point of the program.\"\"\"\n", + "# Instantiate the data problem.\n", + "# [START data]\n", + "data = create_data_model()\n", + "# [END data]\n", + "\n", + "# Create the routing index manager.\n", + "# [START index_manager]\n", + "manager = pywrapcp.RoutingIndexManager(len(data['distance_matrix']),\n", + " data['num_vehicles'], data['depot'])\n", + "# [END index_manager]\n", + "\n", + "# Create Routing Model.\n", + "# [START routing_model]\n", + "routing = pywrapcp.RoutingModel(manager)\n", + "\n", + "# [END routing_model]\n", + "\n", + "# [START transit_callback]\n", + "def distance_callback(from_index, to_index):\n", + " \"\"\"Returns the distance between the two nodes.\"\"\"\n", + " # Convert from routing variable Index to distance matrix NodeIndex.\n", + " from_node = manager.IndexToNode(from_index)\n", + " to_node = manager.IndexToNode(to_index)\n", + " return data['distance_matrix'][from_node][to_node]\n", + "\n", + "transit_callback_index = routing.RegisterTransitCallback(distance_callback)\n", + "# [END transit_callback]\n", + "\n", + "# Define cost of each arc.\n", + "# [START arc_cost]\n", + "routing.SetArcCostEvaluatorOfAllVehicles(transit_callback_index)\n", + "# [END arc_cost]\n", + "\n", + "# Setting first solution heuristic.\n", + "# [START parameters]\n", + "search_parameters = pywrapcp.DefaultRoutingSearchParameters()\n", + "search_parameters.first_solution_strategy = (\n", + " routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC)\n", + "# [END parameters]\n", + "\n", + "# Solve the problem.\n", + "# [START solve]\n", + "assignment = routing.SolveWithParameters(search_parameters)\n", + "# [END solve]\n", + "\n", + "# Print solution on console.\n", + "# [START print_solution]\n", + "if assignment:\n", + " print_solution(manager, routing, assignment)\n", + "# [END print_solution]\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/constraint_solver/tsp_distance_matrix.ipynb b/examples/notebook/constraint_solver/tsp_distance_matrix.ipynb new file mode 100644 index 0000000000..44edee9603 --- /dev/null +++ b/examples/notebook/constraint_solver/tsp_distance_matrix.ipynb @@ -0,0 +1,188 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010-2018 Google LLC\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "# [START program]\n", + "\"\"\"Simple Travelling Salesman Problem.\"\"\"\n", + "\n", + "# [START import]\n", + "from __future__ import print_function\n", + "from ortools.constraint_solver import routing_enums_pb2\n", + "from ortools.constraint_solver import pywrapcp\n", + "# [END import]\n", + "\n", + "\n", + "# [START data_model]\n", + "def create_data_model():\n", + " \"\"\"Stores the data for the problem.\"\"\"\n", + " data = {}\n", + " data['distance_matrix'] = [\n", + " [\n", + " 0, 548, 776, 696, 582, 274, 502, 194, 308, 194, 536, 502, 388, 354,\n", + " 468, 776, 662\n", + " ],\n", + " [\n", + " 548, 0, 684, 308, 194, 502, 730, 354, 696, 742, 1084, 594, 480, 674,\n", + " 1016, 868, 1210\n", + " ],\n", + " [\n", + " 776, 684, 0, 992, 878, 502, 274, 810, 468, 742, 400, 1278, 1164,\n", + " 1130, 788, 1552, 754\n", + " ],\n", + " [\n", + " 696, 308, 992, 0, 114, 650, 878, 502, 844, 890, 1232, 514, 628, 822,\n", + " 1164, 560, 1358\n", + " ],\n", + " [\n", + " 582, 194, 878, 114, 0, 536, 764, 388, 730, 776, 1118, 400, 514, 708,\n", + " 1050, 674, 1244\n", + " ],\n", + " [\n", + " 274, 502, 502, 650, 536, 0, 228, 308, 194, 240, 582, 776, 662, 628,\n", + " 514, 1050, 708\n", + " ],\n", + " [\n", + " 502, 730, 274, 878, 764, 228, 0, 536, 194, 468, 354, 1004, 890, 856,\n", + " 514, 1278, 480\n", + " ],\n", + " [\n", + " 194, 354, 810, 502, 388, 308, 536, 0, 342, 388, 730, 468, 354, 320,\n", + " 662, 742, 856\n", + " ],\n", + " [\n", + " 308, 696, 468, 844, 730, 194, 194, 342, 0, 274, 388, 810, 696, 662,\n", + " 320, 1084, 514\n", + " ],\n", + " [\n", + " 194, 742, 742, 890, 776, 240, 468, 388, 274, 0, 342, 536, 422, 388,\n", + " 274, 810, 468\n", + " ],\n", + " [\n", + " 536, 1084, 400, 1232, 1118, 582, 354, 730, 388, 342, 0, 878, 764,\n", + " 730, 388, 1152, 354\n", + " ],\n", + " [\n", + " 502, 594, 1278, 514, 400, 776, 1004, 468, 810, 536, 878, 0, 114,\n", + " 308, 650, 274, 844\n", + " ],\n", + " [\n", + " 388, 480, 1164, 628, 514, 662, 890, 354, 696, 422, 764, 114, 0, 194,\n", + " 536, 388, 730\n", + " ],\n", + " [\n", + " 354, 674, 1130, 822, 708, 628, 856, 320, 662, 388, 730, 308, 194, 0,\n", + " 342, 422, 536\n", + " ],\n", + " [\n", + " 468, 1016, 788, 1164, 1050, 514, 514, 662, 320, 274, 388, 650, 536,\n", + " 342, 0, 764, 194\n", + " ],\n", + " [\n", + " 776, 868, 1552, 560, 674, 1050, 1278, 742, 1084, 810, 1152, 274,\n", + " 388, 422, 764, 0, 798\n", + " ],\n", + " [\n", + " 662, 1210, 754, 1358, 1244, 708, 480, 856, 514, 468, 354, 844, 730,\n", + " 536, 194, 798, 0\n", + " ],\n", + " ]\n", + " data['num_vehicles'] = 1\n", + " data['depot'] = 0\n", + " return data\n", + " # [END data_model]\n", + "\n", + "\n", + "# [START solution_printer]\n", + "def print_solution(manager, routing, assignment):\n", + " \"\"\"Prints assignment on console.\"\"\"\n", + " print('Objective: {}'.format(assignment.ObjectiveValue()))\n", + " index = routing.Start(0)\n", + " plan_output = 'Route for vehicle 0:\\n'\n", + " route_distance = 0\n", + " while not routing.IsEnd(index):\n", + " plan_output += ' {} ->'.format(manager.IndexToNode(index))\n", + " previous_index = index\n", + " index = assignment.Value(routing.NextVar(index))\n", + " route_distance += routing.GetArcCostForVehicle(previous_index, index, 0)\n", + " plan_output += ' {}\\n'.format(manager.IndexToNode(index))\n", + " plan_output += 'Distance of the route: {}m\\n'.format(route_distance)\n", + " print(plan_output)\n", + " # [END solution_printer]\n", + "\n", + "\n", + "\"\"\"Entry point of the program.\"\"\"\n", + "# Instantiate the data problem.\n", + "# [START data]\n", + "data = create_data_model()\n", + "# [END data]\n", + "\n", + "# Create the routing index manager.\n", + "# [START index_manager]\n", + "manager = pywrapcp.RoutingIndexManager(len(data['distance_matrix']),\n", + " data['num_vehicles'], data['depot'])\n", + "# [END index_manager]\n", + "\n", + "# Create Routing Model.\n", + "# [START routing_model]\n", + "routing = pywrapcp.RoutingModel(manager)\n", + "\n", + "# [END routing_model]\n", + "\n", + "# Create and register a transit callback.\n", + "# [START transit_callback]\n", + "def distance_callback(from_index, to_index):\n", + " \"\"\"Returns the distance between the two nodes.\"\"\"\n", + " # Convert from routing variable Index to distance matrix NodeIndex.\n", + " from_node = manager.IndexToNode(from_index)\n", + " to_node = manager.IndexToNode(to_index)\n", + " return data['distance_matrix'][from_node][to_node]\n", + "\n", + "transit_callback_index = routing.RegisterTransitCallback(distance_callback)\n", + "# [END transit_callback]\n", + "\n", + "# Define cost of each arc.\n", + "# [START arc_cost]\n", + "routing.SetArcCostEvaluatorOfAllVehicles(transit_callback_index)\n", + "# [END arc_cost]\n", + "\n", + "# Setting first solution heuristic.\n", + "# [START parameters]\n", + "search_parameters = pywrapcp.DefaultRoutingSearchParameters()\n", + "search_parameters.first_solution_strategy = (\n", + " routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC)\n", + "# [END parameters]\n", + "\n", + "# Solve the problem.\n", + "# [START solve]\n", + "assignment = routing.SolveWithParameters(search_parameters)\n", + "# [END solve]\n", + "\n", + "# Print solution on console.\n", + "# [START print_solution]\n", + "if assignment:\n", + " print_solution(manager, routing, assignment)\n", + "# [END print_solution]\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/constraint_solver/vrp.ipynb b/examples/notebook/constraint_solver/vrp.ipynb new file mode 100644 index 0000000000..6bcf12cf0f --- /dev/null +++ b/examples/notebook/constraint_solver/vrp.ipynb @@ -0,0 +1,193 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010-2018 Google LLC\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "# [START program]\n", + "\"\"\"Simple Vehicles Routing Problem.\"\"\"\n", + "\n", + "# [START import]\n", + "from __future__ import print_function\n", + "from ortools.constraint_solver import routing_enums_pb2\n", + "from ortools.constraint_solver import pywrapcp\n", + "# [END import]\n", + "\n", + "\n", + "# [START data_model]\n", + "def create_data_model():\n", + " \"\"\"Stores the data for the problem.\"\"\"\n", + " data = {}\n", + " data['distance_matrix'] = [\n", + " [\n", + " 0, 548, 776, 696, 582, 274, 502, 194, 308, 194, 536, 502, 388, 354,\n", + " 468, 776, 662\n", + " ],\n", + " [\n", + " 548, 0, 684, 308, 194, 502, 730, 354, 696, 742, 1084, 594, 480, 674,\n", + " 1016, 868, 1210\n", + " ],\n", + " [\n", + " 776, 684, 0, 992, 878, 502, 274, 810, 468, 742, 400, 1278, 1164,\n", + " 1130, 788, 1552, 754\n", + " ],\n", + " [\n", + " 696, 308, 992, 0, 114, 650, 878, 502, 844, 890, 1232, 514, 628, 822,\n", + " 1164, 560, 1358\n", + " ],\n", + " [\n", + " 582, 194, 878, 114, 0, 536, 764, 388, 730, 776, 1118, 400, 514, 708,\n", + " 1050, 674, 1244\n", + " ],\n", + " [\n", + " 274, 502, 502, 650, 536, 0, 228, 308, 194, 240, 582, 776, 662, 628,\n", + " 514, 1050, 708\n", + " ],\n", + " [\n", + " 502, 730, 274, 878, 764, 228, 0, 536, 194, 468, 354, 1004, 890, 856,\n", + " 514, 1278, 480\n", + " ],\n", + " [\n", + " 194, 354, 810, 502, 388, 308, 536, 0, 342, 388, 730, 468, 354, 320,\n", + " 662, 742, 856\n", + " ],\n", + " [\n", + " 308, 696, 468, 844, 730, 194, 194, 342, 0, 274, 388, 810, 696, 662,\n", + " 320, 1084, 514\n", + " ],\n", + " [\n", + " 194, 742, 742, 890, 776, 240, 468, 388, 274, 0, 342, 536, 422, 388,\n", + " 274, 810, 468\n", + " ],\n", + " [\n", + " 536, 1084, 400, 1232, 1118, 582, 354, 730, 388, 342, 0, 878, 764,\n", + " 730, 388, 1152, 354\n", + " ],\n", + " [\n", + " 502, 594, 1278, 514, 400, 776, 1004, 468, 810, 536, 878, 0, 114,\n", + " 308, 650, 274, 844\n", + " ],\n", + " [\n", + " 388, 480, 1164, 628, 514, 662, 890, 354, 696, 422, 764, 114, 0, 194,\n", + " 536, 388, 730\n", + " ],\n", + " [\n", + " 354, 674, 1130, 822, 708, 628, 856, 320, 662, 388, 730, 308, 194, 0,\n", + " 342, 422, 536\n", + " ],\n", + " [\n", + " 468, 1016, 788, 1164, 1050, 514, 514, 662, 320, 274, 388, 650, 536,\n", + " 342, 0, 764, 194\n", + " ],\n", + " [\n", + " 776, 868, 1552, 560, 674, 1050, 1278, 742, 1084, 810, 1152, 274,\n", + " 388, 422, 764, 0, 798\n", + " ],\n", + " [\n", + " 662, 1210, 754, 1358, 1244, 708, 480, 856, 514, 468, 354, 844, 730,\n", + " 536, 194, 798, 0\n", + " ],\n", + " ]\n", + " data['num_vehicles'] = 4\n", + " data['depot'] = 0\n", + " return data\n", + " # [END data_model]\n", + "\n", + "\n", + "# [START solution_printer]\n", + "def print_solution(data, manager, routing, assignment):\n", + " \"\"\"Prints assignment on console.\"\"\"\n", + " print('Objective: {}'.format(assignment.ObjectiveValue()))\n", + " total_distance = 0\n", + " for vehicle_id in range(data['num_vehicles']):\n", + " index = routing.Start(vehicle_id)\n", + " plan_output = 'Route for vehicle {}:\\n'.format(vehicle_id)\n", + " route_distance = 0\n", + " while not routing.IsEnd(index):\n", + " plan_output += ' {} ->'.format(manager.IndexToNode(index))\n", + " previous_index = index\n", + " index = assignment.Value(routing.NextVar(index))\n", + " route_distance += routing.GetArcCostForVehicle(\n", + " previous_index, index, vehicle_id)\n", + " plan_output += ' {}\\n'.format(manager.IndexToNode(index))\n", + " plan_output += 'Distance of the route: {}m\\n'.format(route_distance)\n", + " print(plan_output)\n", + " total_distance += route_distance\n", + " print('Total Distance of all routes: {}m'.format(total_distance))\n", + " # [END solution_printer]\n", + "\n", + "\n", + "\"\"\"Entry point of the program.\"\"\"\n", + "# Instantiate the data problem.\n", + "# [START data]\n", + "data = create_data_model()\n", + "# [END data]\n", + "\n", + "# Create the routing index manager.\n", + "# [START index_manager]\n", + "manager = pywrapcp.RoutingIndexManager(len(data['distance_matrix']),\n", + " data['num_vehicles'], data['depot'])\n", + "# [END index_manager]\n", + "\n", + "# Create Routing Model.\n", + "# [START routing_model]\n", + "routing = pywrapcp.RoutingModel(manager)\n", + "\n", + "# [END routing_model]\n", + "\n", + "# Create and register a transit callback.\n", + "# [START transit_callback]\n", + "def distance_callback(from_index, to_index):\n", + " \"\"\"Returns the distance between the two nodes.\"\"\"\n", + " # Convert from routing variable Index to distance matrix NodeIndex.\n", + " from_node = manager.IndexToNode(from_index)\n", + " to_node = manager.IndexToNode(to_index)\n", + " return data['distance_matrix'][from_node][to_node]\n", + "\n", + "transit_callback_index = routing.RegisterTransitCallback(distance_callback)\n", + "# [END transit_callback]\n", + "\n", + "# Define cost of each arc.\n", + "# [START arc_cost]\n", + "routing.SetArcCostEvaluatorOfAllVehicles(transit_callback_index)\n", + "# [END arc_cost]\n", + "\n", + "# Setting first solution heuristic.\n", + "# [START parameters]\n", + "search_parameters = pywrapcp.DefaultRoutingSearchParameters()\n", + "search_parameters.first_solution_strategy = (\n", + " routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC)\n", + "# [END parameters]\n", + "\n", + "# Solve the problem.\n", + "# [START solve]\n", + "assignment = routing.SolveWithParameters(search_parameters)\n", + "# [END solve]\n", + "\n", + "# Print solution on console.\n", + "# [START print_solution]\n", + "if assignment:\n", + " print_solution(data, manager, routing, assignment)\n", + "# [END print_solution]\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/constraint_solver/vrp_capacity.ipynb b/examples/notebook/constraint_solver/vrp_capacity.ipynb new file mode 100644 index 0000000000..952c8a898d --- /dev/null +++ b/examples/notebook/constraint_solver/vrp_capacity.ipynb @@ -0,0 +1,223 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010-2018 Google LLC\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "# [START program]\n", + "\"\"\"Capacited Vehicles Routing Problem (CVRP).\"\"\"\n", + "\n", + "# [START import]\n", + "from __future__ import print_function\n", + "from ortools.constraint_solver import routing_enums_pb2\n", + "from ortools.constraint_solver import pywrapcp\n", + "# [END import]\n", + "\n", + "\n", + "# [START data_model]\n", + "def create_data_model():\n", + " \"\"\"Stores the data for the problem.\"\"\"\n", + " data = {}\n", + " data['distance_matrix'] = [\n", + " [\n", + " 0, 548, 776, 696, 582, 274, 502, 194, 308, 194, 536, 502, 388, 354,\n", + " 468, 776, 662\n", + " ],\n", + " [\n", + " 548, 0, 684, 308, 194, 502, 730, 354, 696, 742, 1084, 594, 480, 674,\n", + " 1016, 868, 1210\n", + " ],\n", + " [\n", + " 776, 684, 0, 992, 878, 502, 274, 810, 468, 742, 400, 1278, 1164,\n", + " 1130, 788, 1552, 754\n", + " ],\n", + " [\n", + " 696, 308, 992, 0, 114, 650, 878, 502, 844, 890, 1232, 514, 628, 822,\n", + " 1164, 560, 1358\n", + " ],\n", + " [\n", + " 582, 194, 878, 114, 0, 536, 764, 388, 730, 776, 1118, 400, 514, 708,\n", + " 1050, 674, 1244\n", + " ],\n", + " [\n", + " 274, 502, 502, 650, 536, 0, 228, 308, 194, 240, 582, 776, 662, 628,\n", + " 514, 1050, 708\n", + " ],\n", + " [\n", + " 502, 730, 274, 878, 764, 228, 0, 536, 194, 468, 354, 1004, 890, 856,\n", + " 514, 1278, 480\n", + " ],\n", + " [\n", + " 194, 354, 810, 502, 388, 308, 536, 0, 342, 388, 730, 468, 354, 320,\n", + " 662, 742, 856\n", + " ],\n", + " [\n", + " 308, 696, 468, 844, 730, 194, 194, 342, 0, 274, 388, 810, 696, 662,\n", + " 320, 1084, 514\n", + " ],\n", + " [\n", + " 194, 742, 742, 890, 776, 240, 468, 388, 274, 0, 342, 536, 422, 388,\n", + " 274, 810, 468\n", + " ],\n", + " [\n", + " 536, 1084, 400, 1232, 1118, 582, 354, 730, 388, 342, 0, 878, 764,\n", + " 730, 388, 1152, 354\n", + " ],\n", + " [\n", + " 502, 594, 1278, 514, 400, 776, 1004, 468, 810, 536, 878, 0, 114,\n", + " 308, 650, 274, 844\n", + " ],\n", + " [\n", + " 388, 480, 1164, 628, 514, 662, 890, 354, 696, 422, 764, 114, 0, 194,\n", + " 536, 388, 730\n", + " ],\n", + " [\n", + " 354, 674, 1130, 822, 708, 628, 856, 320, 662, 388, 730, 308, 194, 0,\n", + " 342, 422, 536\n", + " ],\n", + " [\n", + " 468, 1016, 788, 1164, 1050, 514, 514, 662, 320, 274, 388, 650, 536,\n", + " 342, 0, 764, 194\n", + " ],\n", + " [\n", + " 776, 868, 1552, 560, 674, 1050, 1278, 742, 1084, 810, 1152, 274,\n", + " 388, 422, 764, 0, 798\n", + " ],\n", + " [\n", + " 662, 1210, 754, 1358, 1244, 708, 480, 856, 514, 468, 354, 844, 730,\n", + " 536, 194, 798, 0\n", + " ],\n", + " ]\n", + " # [START demands_capacities]\n", + " data['demands'] = [0, 1, 1, 2, 4, 2, 4, 8, 8, 1, 2, 1, 2, 4, 4, 8, 8]\n", + " data['vehicle_capacities'] = [15, 15, 15, 15]\n", + " # [END demands_capacities]\n", + " data['num_vehicles'] = 4\n", + " data['depot'] = 0\n", + " return data\n", + " # [END data_model]\n", + "\n", + "\n", + "# [START solution_printer]\n", + "def print_solution(data, manager, routing, assignment):\n", + " \"\"\"Prints assignment on console.\"\"\"\n", + " total_distance = 0\n", + " total_load = 0\n", + " for vehicle_id in range(data['num_vehicles']):\n", + " index = routing.Start(vehicle_id)\n", + " plan_output = 'Route for vehicle {}:\\n'.format(vehicle_id)\n", + " route_distance = 0\n", + " route_load = 0\n", + " while not routing.IsEnd(index):\n", + " node_index = manager.IndexToNode(index)\n", + " route_load += data['demands'][node_index]\n", + " plan_output += ' {0} Load({1}) -> '.format(node_index, route_load)\n", + " previous_index = index\n", + " index = assignment.Value(routing.NextVar(index))\n", + " route_distance += routing.GetArcCostForVehicle(\n", + " previous_index, index, vehicle_id)\n", + " plan_output += ' {0} Load({1})\\n'.format(manager.IndexToNode(index),\n", + " route_load)\n", + " plan_output += 'Distance of the route: {}m\\n'.format(route_distance)\n", + " plan_output += 'Load of the route: {}\\n'.format(route_load)\n", + " print(plan_output)\n", + " total_distance += route_distance\n", + " total_load += route_load\n", + " print('Total distance of all routes: {}m'.format(total_distance))\n", + " print('Total load of all routes: {}'.format(total_load))\n", + " # [END solution_printer]\n", + "\n", + "\n", + "\"\"\"Solve the CVRP problem.\"\"\"\n", + "# Instantiate the data problem.\n", + "# [START data]\n", + "data = create_data_model()\n", + "# [END data]\n", + "\n", + "# Create the routing index manager.\n", + "# [START index_manager]\n", + "manager = pywrapcp.RoutingIndexManager(len(data['distance_matrix']),\n", + " data['num_vehicles'], data['depot'])\n", + "# [END index_manager]\n", + "\n", + "# Create Routing Model.\n", + "# [START routing_model]\n", + "routing = pywrapcp.RoutingModel(manager)\n", + "\n", + "# [END routing_model]\n", + "\n", + "# Create and register a transit callback.\n", + "# [START transit_callback]\n", + "def distance_callback(from_index, to_index):\n", + " \"\"\"Returns the distance between the two nodes.\"\"\"\n", + " # Convert from routing variable Index to distance matrix NodeIndex.\n", + " from_node = manager.IndexToNode(from_index)\n", + " to_node = manager.IndexToNode(to_index)\n", + " return data['distance_matrix'][from_node][to_node]\n", + "\n", + "transit_callback_index = routing.RegisterTransitCallback(distance_callback)\n", + "# [END transit_callback]\n", + "\n", + "# Define cost of each arc.\n", + "# [START arc_cost]\n", + "routing.SetArcCostEvaluatorOfAllVehicles(transit_callback_index)\n", + "\n", + "# [END arc_cost]\n", + "\n", + "# Add Capacity constraint.\n", + "# [START capacity_constraint]\n", + "def demand_callback(from_index):\n", + " \"\"\"Returns the demand of the node.\"\"\"\n", + " # Convert from routing variable Index to demands NodeIndex.\n", + " from_node = manager.IndexToNode(from_index)\n", + " return data['demands'][from_node]\n", + "\n", + "demand_callback_index = routing.RegisterUnaryTransitCallback(\n", + " demand_callback)\n", + "routing.AddDimensionWithVehicleCapacity(\n", + " demand_callback_index,\n", + " 0, # null capacity slack\n", + " data['vehicle_capacities'], # vehicle maximum capacities\n", + " True, # start cumul to zero\n", + " 'Capacity')\n", + "# [END capacity_constraint]\n", + "\n", + "# Setting first solution heuristic.\n", + "# [START parameters]\n", + "search_parameters = pywrapcp.DefaultRoutingSearchParameters()\n", + "search_parameters.first_solution_strategy = (\n", + " routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC)\n", + "# [END parameters]\n", + "\n", + "# Solve the problem.\n", + "# [START solve]\n", + "assignment = routing.SolveWithParameters(search_parameters)\n", + "# [END solve]\n", + "\n", + "# Print solution on console.\n", + "# [START print_solution]\n", + "if assignment:\n", + " print_solution(data, manager, routing, assignment)\n", + "# [END print_solution]\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/constraint_solver/vrp_drop_nodes.ipynb b/examples/notebook/constraint_solver/vrp_drop_nodes.ipynb new file mode 100644 index 0000000000..d5d9b46a4c --- /dev/null +++ b/examples/notebook/constraint_solver/vrp_drop_nodes.ipynb @@ -0,0 +1,236 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010-2018 Google LLC\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "# [START program]\n", + "\"\"\"Capacited Vehicles Routing Problem (CVRP).\"\"\"\n", + "\n", + "# [START import]\n", + "from __future__ import print_function\n", + "from ortools.constraint_solver import routing_enums_pb2\n", + "from ortools.constraint_solver import pywrapcp\n", + "# [END import]\n", + "\n", + "\n", + "# [START data_model]\n", + "def create_data_model():\n", + " \"\"\"Stores the data for the problem.\"\"\"\n", + " data = {}\n", + " data['distance_matrix'] = [\n", + " [\n", + " 0, 548, 776, 696, 582, 274, 502, 194, 308, 194, 536, 502, 388, 354,\n", + " 468, 776, 662\n", + " ],\n", + " [\n", + " 548, 0, 684, 308, 194, 502, 730, 354, 696, 742, 1084, 594, 480, 674,\n", + " 1016, 868, 1210\n", + " ],\n", + " [\n", + " 776, 684, 0, 992, 878, 502, 274, 810, 468, 742, 400, 1278, 1164,\n", + " 1130, 788, 1552, 754\n", + " ],\n", + " [\n", + " 696, 308, 992, 0, 114, 650, 878, 502, 844, 890, 1232, 514, 628, 822,\n", + " 1164, 560, 1358\n", + " ],\n", + " [\n", + " 582, 194, 878, 114, 0, 536, 764, 388, 730, 776, 1118, 400, 514, 708,\n", + " 1050, 674, 1244\n", + " ],\n", + " [\n", + " 274, 502, 502, 650, 536, 0, 228, 308, 194, 240, 582, 776, 662, 628,\n", + " 514, 1050, 708\n", + " ],\n", + " [\n", + " 502, 730, 274, 878, 764, 228, 0, 536, 194, 468, 354, 1004, 890, 856,\n", + " 514, 1278, 480\n", + " ],\n", + " [\n", + " 194, 354, 810, 502, 388, 308, 536, 0, 342, 388, 730, 468, 354, 320,\n", + " 662, 742, 856\n", + " ],\n", + " [\n", + " 308, 696, 468, 844, 730, 194, 194, 342, 0, 274, 388, 810, 696, 662,\n", + " 320, 1084, 514\n", + " ],\n", + " [\n", + " 194, 742, 742, 890, 776, 240, 468, 388, 274, 0, 342, 536, 422, 388,\n", + " 274, 810, 468\n", + " ],\n", + " [\n", + " 536, 1084, 400, 1232, 1118, 582, 354, 730, 388, 342, 0, 878, 764,\n", + " 730, 388, 1152, 354\n", + " ],\n", + " [\n", + " 502, 594, 1278, 514, 400, 776, 1004, 468, 810, 536, 878, 0, 114,\n", + " 308, 650, 274, 844\n", + " ],\n", + " [\n", + " 388, 480, 1164, 628, 514, 662, 890, 354, 696, 422, 764, 114, 0, 194,\n", + " 536, 388, 730\n", + " ],\n", + " [\n", + " 354, 674, 1130, 822, 708, 628, 856, 320, 662, 388, 730, 308, 194, 0,\n", + " 342, 422, 536\n", + " ],\n", + " [\n", + " 468, 1016, 788, 1164, 1050, 514, 514, 662, 320, 274, 388, 650, 536,\n", + " 342, 0, 764, 194\n", + " ],\n", + " [\n", + " 776, 868, 1552, 560, 674, 1050, 1278, 742, 1084, 810, 1152, 274,\n", + " 388, 422, 764, 0, 798\n", + " ],\n", + " [\n", + " 662, 1210, 754, 1358, 1244, 708, 480, 856, 514, 468, 354, 844, 730,\n", + " 536, 194, 798, 0\n", + " ],\n", + " ]\n", + " # [START demands_capacities]\n", + " data['demands'] = [0, 1, 1, 3, 6, 3, 6, 8, 8, 1, 2, 1, 2, 6, 6, 8, 8]\n", + " data['vehicle_capacities'] = [15, 15, 15, 15]\n", + " # [END demands_capacities]\n", + " data['num_vehicles'] = 4\n", + " data['depot'] = 0\n", + " return data\n", + " # [END data_model]\n", + "\n", + "\n", + "# [START solution_printer]\n", + "def print_solution(data, manager, routing, assignment):\n", + " \"\"\"Prints assignment on console.\"\"\"\n", + " # Display dropped nodes.\n", + " dropped_nodes = 'Dropped nodes:'\n", + " for node in range(routing.Size()):\n", + " if routing.IsStart(node) or routing.IsEnd(node):\n", + " continue\n", + " if assignment.Value(routing.NextVar(node)) == node:\n", + " dropped_nodes += ' {}'.format(manager.IndexToNode(node))\n", + " print(dropped_nodes)\n", + " # Display routes\n", + " total_distance = 0\n", + " total_load = 0\n", + " for vehicle_id in range(data['num_vehicles']):\n", + " index = routing.Start(vehicle_id)\n", + " plan_output = 'Route for vehicle {}:\\n'.format(vehicle_id)\n", + " route_distance = 0\n", + " route_load = 0\n", + " while not routing.IsEnd(index):\n", + " node_index = manager.IndexToNode(index)\n", + " route_load += data['demands'][node_index]\n", + " plan_output += ' {0} Load({1}) -> '.format(node_index, route_load)\n", + " previous_index = index\n", + " index = assignment.Value(routing.NextVar(index))\n", + " route_distance += routing.GetArcCostForVehicle(\n", + " previous_index, index, vehicle_id)\n", + " plan_output += ' {0} Load({1})\\n'.format(manager.IndexToNode(index),\n", + " route_load)\n", + " plan_output += 'Distance of the route: {}m\\n'.format(route_distance)\n", + " plan_output += 'Load of the route: {}\\n'.format(route_load)\n", + " print(plan_output)\n", + " total_distance += route_distance\n", + " total_load += route_load\n", + " print('Total Distance of all routes: {}m'.format(total_distance))\n", + " print('Total Load of all routes: {}'.format(total_load))\n", + " # [END solution_printer]\n", + "\n", + "\n", + "\"\"\"Solve the CVRP problem.\"\"\"\n", + "# Instantiate the data problem.\n", + "# [START data]\n", + "data = create_data_model()\n", + "# [END data]\n", + "\n", + "# Create the routing index manager.\n", + "# [START index_manager]\n", + "manager = pywrapcp.RoutingIndexManager(len(data['distance_matrix']),\n", + " data['num_vehicles'], data['depot'])\n", + "# [END index_manager]\n", + "\n", + "# Create Routing Model.\n", + "# [START routing_model]\n", + "routing = pywrapcp.RoutingModel(manager)\n", + "\n", + "# [END routing_model]\n", + "\n", + "# Create and register a transit callback.\n", + "# [START transit_callback]\n", + "def distance_callback(from_index, to_index):\n", + " \"\"\"Returns the distance between the two nodes.\"\"\"\n", + " # Convert from routing variable Index to distance matrix NodeIndex.\n", + " from_node = manager.IndexToNode(from_index)\n", + " to_node = manager.IndexToNode(to_index)\n", + " return data['distance_matrix'][from_node][to_node]\n", + "\n", + "transit_callback_index = routing.RegisterTransitCallback(distance_callback)\n", + "# [END transit_callback]\n", + "\n", + "# Define cost of each arc.\n", + "# [START arc_cost]\n", + "routing.SetArcCostEvaluatorOfAllVehicles(transit_callback_index)\n", + "\n", + "# [END arc_cost]\n", + "\n", + "# Add Capacity constraint.\n", + "# [START capacity_constraint]\n", + "def demand_callback(from_index):\n", + " \"\"\"Returns the demand of the node.\"\"\"\n", + " # Convert from routing variable Index to demands NodeIndex.\n", + " from_node = manager.IndexToNode(from_index)\n", + " return data['demands'][from_node]\n", + "\n", + "demand_callback_index = routing.RegisterUnaryTransitCallback(\n", + " demand_callback)\n", + "routing.AddDimensionWithVehicleCapacity(\n", + " demand_callback_index,\n", + " 0, # null capacity slack\n", + " data['vehicle_capacities'], # vehicle maximum capacities\n", + " True, # start cumul to zero\n", + " 'Capacity')\n", + "# Allow to drop nodes.\n", + "penalty = 1000\n", + "for node in range(1, len(data['distance_matrix'])):\n", + " routing.AddDisjunction([manager.NodeToIndex(node)], penalty)\n", + "# [END capacity_constraint]\n", + "\n", + "# Setting first solution heuristic.\n", + "# [START parameters]\n", + "search_parameters = pywrapcp.DefaultRoutingSearchParameters()\n", + "search_parameters.first_solution_strategy = (\n", + " routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC)\n", + "# [END parameters]\n", + "\n", + "# Solve the problem.\n", + "# [START solve]\n", + "assignment = routing.SolveWithParameters(search_parameters)\n", + "# [END solve]\n", + "\n", + "# Print solution on console.\n", + "# [START print_solution]\n", + "if assignment:\n", + " print_solution(data, manager, routing, assignment)\n", + "# [END print_solution]\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/constraint_solver/vrp_global_span.ipynb b/examples/notebook/constraint_solver/vrp_global_span.ipynb new file mode 100644 index 0000000000..02e4cfb8ab --- /dev/null +++ b/examples/notebook/constraint_solver/vrp_global_span.ipynb @@ -0,0 +1,207 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010-2018 Google LLC\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "# [START program]\n", + "\"\"\"Vehicles Routing Problem (VRP).\"\"\"\n", + "\n", + "# [START import]\n", + "from __future__ import print_function\n", + "from ortools.constraint_solver import routing_enums_pb2\n", + "from ortools.constraint_solver import pywrapcp\n", + "# [END import]\n", + "\n", + "\n", + "# [START data_model]\n", + "def create_data_model():\n", + " \"\"\"Stores the data for the problem.\"\"\"\n", + " data = {}\n", + " data['distance_matrix'] = [\n", + " [\n", + " 0, 548, 776, 696, 582, 274, 502, 194, 308, 194, 536, 502, 388, 354,\n", + " 468, 776, 662\n", + " ],\n", + " [\n", + " 548, 0, 684, 308, 194, 502, 730, 354, 696, 742, 1084, 594, 480, 674,\n", + " 1016, 868, 1210\n", + " ],\n", + " [\n", + " 776, 684, 0, 992, 878, 502, 274, 810, 468, 742, 400, 1278, 1164,\n", + " 1130, 788, 1552, 754\n", + " ],\n", + " [\n", + " 696, 308, 992, 0, 114, 650, 878, 502, 844, 890, 1232, 514, 628, 822,\n", + " 1164, 560, 1358\n", + " ],\n", + " [\n", + " 582, 194, 878, 114, 0, 536, 764, 388, 730, 776, 1118, 400, 514, 708,\n", + " 1050, 674, 1244\n", + " ],\n", + " [\n", + " 274, 502, 502, 650, 536, 0, 228, 308, 194, 240, 582, 776, 662, 628,\n", + " 514, 1050, 708\n", + " ],\n", + " [\n", + " 502, 730, 274, 878, 764, 228, 0, 536, 194, 468, 354, 1004, 890, 856,\n", + " 514, 1278, 480\n", + " ],\n", + " [\n", + " 194, 354, 810, 502, 388, 308, 536, 0, 342, 388, 730, 468, 354, 320,\n", + " 662, 742, 856\n", + " ],\n", + " [\n", + " 308, 696, 468, 844, 730, 194, 194, 342, 0, 274, 388, 810, 696, 662,\n", + " 320, 1084, 514\n", + " ],\n", + " [\n", + " 194, 742, 742, 890, 776, 240, 468, 388, 274, 0, 342, 536, 422, 388,\n", + " 274, 810, 468\n", + " ],\n", + " [\n", + " 536, 1084, 400, 1232, 1118, 582, 354, 730, 388, 342, 0, 878, 764,\n", + " 730, 388, 1152, 354\n", + " ],\n", + " [\n", + " 502, 594, 1278, 514, 400, 776, 1004, 468, 810, 536, 878, 0, 114,\n", + " 308, 650, 274, 844\n", + " ],\n", + " [\n", + " 388, 480, 1164, 628, 514, 662, 890, 354, 696, 422, 764, 114, 0, 194,\n", + " 536, 388, 730\n", + " ],\n", + " [\n", + " 354, 674, 1130, 822, 708, 628, 856, 320, 662, 388, 730, 308, 194, 0,\n", + " 342, 422, 536\n", + " ],\n", + " [\n", + " 468, 1016, 788, 1164, 1050, 514, 514, 662, 320, 274, 388, 650, 536,\n", + " 342, 0, 764, 194\n", + " ],\n", + " [\n", + " 776, 868, 1552, 560, 674, 1050, 1278, 742, 1084, 810, 1152, 274,\n", + " 388, 422, 764, 0, 798\n", + " ],\n", + " [\n", + " 662, 1210, 754, 1358, 1244, 708, 480, 856, 514, 468, 354, 844, 730,\n", + " 536, 194, 798, 0\n", + " ],\n", + " ]\n", + " data['num_vehicles'] = 4\n", + " data['depot'] = 0\n", + " return data\n", + " # [END data_model]\n", + "\n", + "\n", + "# [START solution_printer]\n", + "def print_solution(data, manager, routing, solution):\n", + " \"\"\"Prints solution on console.\"\"\"\n", + " max_route_distance = 0\n", + " for vehicle_id in range(data['num_vehicles']):\n", + " index = routing.Start(vehicle_id)\n", + " plan_output = 'Route for vehicle {}:\\n'.format(vehicle_id)\n", + " route_distance = 0\n", + " while not routing.IsEnd(index):\n", + " plan_output += ' {} -> '.format(manager.IndexToNode(index))\n", + " previous_index = index\n", + " index = solution.Value(routing.NextVar(index))\n", + " route_distance += routing.GetArcCostForVehicle(\n", + " previous_index, index, vehicle_id)\n", + " plan_output += '{}\\n'.format(manager.IndexToNode(index))\n", + " plan_output += 'Distance of the route: {}m\\n'.format(route_distance)\n", + " print(plan_output)\n", + " max_route_distance = max(route_distance, max_route_distance)\n", + " print('Maximum of the route distances: {}m'.format(max_route_distance))\n", + "\n", + "\n", + "# [END solution_printer]\n", + "\n", + "\n", + "\"\"\"Solve the CVRP problem.\"\"\"\n", + "# Instantiate the data problem.\n", + "# [START data]\n", + "data = create_data_model()\n", + "# [END data]\n", + "\n", + "# Create the routing index manager.\n", + "# [START index_manager]\n", + "manager = pywrapcp.RoutingIndexManager(len(data['distance_matrix']),\n", + " data['num_vehicles'], data['depot'])\n", + "# [END index_manager]\n", + "\n", + "# Create Routing Model.\n", + "# [START routing_model]\n", + "routing = pywrapcp.RoutingModel(manager)\n", + "\n", + "# [END routing_model]\n", + "\n", + "# Create and register a transit callback.\n", + "# [START transit_callback]\n", + "def distance_callback(from_index, to_index):\n", + " \"\"\"Returns the distance between the two nodes.\"\"\"\n", + " # Convert from routing variable Index to distance matrix NodeIndex.\n", + " from_node = manager.IndexToNode(from_index)\n", + " to_node = manager.IndexToNode(to_index)\n", + " return data['distance_matrix'][from_node][to_node]\n", + "\n", + "transit_callback_index = routing.RegisterTransitCallback(distance_callback)\n", + "# [END transit_callback]\n", + "\n", + "# Define cost of each arc.\n", + "# [START arc_cost]\n", + "routing.SetArcCostEvaluatorOfAllVehicles(transit_callback_index)\n", + "# [END arc_cost]\n", + "\n", + "# Add Distance constraint.\n", + "# [START distance_constraint]\n", + "dimension_name = 'Distance'\n", + "routing.AddDimension(\n", + " transit_callback_index,\n", + " 0, # no slack\n", + " 3000, # vehicle maximum travel distance\n", + " True, # start cumul to zero\n", + " dimension_name)\n", + "distance_dimension = routing.GetDimensionOrDie(dimension_name)\n", + "distance_dimension.SetGlobalSpanCostCoefficient(100)\n", + "# [END distance_constraint]\n", + "\n", + "# Setting first solution heuristic.\n", + "# [START parameters]\n", + "search_parameters = pywrapcp.DefaultRoutingSearchParameters()\n", + "search_parameters.first_solution_strategy = (\n", + " routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC)\n", + "# [END parameters]\n", + "\n", + "# Solve the problem.\n", + "# [START solve]\n", + "solution = routing.SolveWithParameters(search_parameters)\n", + "# [END solve]\n", + "\n", + "# Print solution on console.\n", + "# [START print_solution]\n", + "if solution:\n", + " print_solution(data, manager, routing, solution)\n", + "# [END print_solution]\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/constraint_solver/vrp_initial_routes.ipynb b/examples/notebook/constraint_solver/vrp_initial_routes.ipynb new file mode 100644 index 0000000000..c92acc9fd3 --- /dev/null +++ b/examples/notebook/constraint_solver/vrp_initial_routes.ipynb @@ -0,0 +1,223 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010-2018 Google LLC\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "# [START program]\n", + "\"\"\"Vehicles Routing Problem (VRP).\"\"\"\n", + "\n", + "# [START import]\n", + "from __future__ import print_function\n", + "from ortools.constraint_solver import pywrapcp\n", + "from ortools.constraint_solver import pywrapcp\n", + "\n", + "# [END import]\n", + "\n", + "\n", + "# [START data_model]\n", + "def create_data_model():\n", + " \"\"\"Stores the data for the problem.\"\"\"\n", + " data = {}\n", + " data['distance_matrix'] = [\n", + " [\n", + " 0, 548, 776, 696, 582, 274, 502, 194, 308, 194, 536, 502, 388, 354,\n", + " 468, 776, 662\n", + " ],\n", + " [\n", + " 548, 0, 684, 308, 194, 502, 730, 354, 696, 742, 1084, 594, 480, 674,\n", + " 1016, 868, 1210\n", + " ],\n", + " [\n", + " 776, 684, 0, 992, 878, 502, 274, 810, 468, 742, 400, 1278, 1164,\n", + " 1130, 788, 1552, 754\n", + " ],\n", + " [\n", + " 696, 308, 992, 0, 114, 650, 878, 502, 844, 890, 1232, 514, 628, 822,\n", + " 1164, 560, 1358\n", + " ],\n", + " [\n", + " 582, 194, 878, 114, 0, 536, 764, 388, 730, 776, 1118, 400, 514, 708,\n", + " 1050, 674, 1244\n", + " ],\n", + " [\n", + " 274, 502, 502, 650, 536, 0, 228, 308, 194, 240, 582, 776, 662, 628,\n", + " 514, 1050, 708\n", + " ],\n", + " [\n", + " 502, 730, 274, 878, 764, 228, 0, 536, 194, 468, 354, 1004, 890, 856,\n", + " 514, 1278, 480\n", + " ],\n", + " [\n", + " 194, 354, 810, 502, 388, 308, 536, 0, 342, 388, 730, 468, 354, 320,\n", + " 662, 742, 856\n", + " ],\n", + " [\n", + " 308, 696, 468, 844, 730, 194, 194, 342, 0, 274, 388, 810, 696, 662,\n", + " 320, 1084, 514\n", + " ],\n", + " [\n", + " 194, 742, 742, 890, 776, 240, 468, 388, 274, 0, 342, 536, 422, 388,\n", + " 274, 810, 468\n", + " ],\n", + " [\n", + " 536, 1084, 400, 1232, 1118, 582, 354, 730, 388, 342, 0, 878, 764,\n", + " 730, 388, 1152, 354\n", + " ],\n", + " [\n", + " 502, 594, 1278, 514, 400, 776, 1004, 468, 810, 536, 878, 0, 114,\n", + " 308, 650, 274, 844\n", + " ],\n", + " [\n", + " 388, 480, 1164, 628, 514, 662, 890, 354, 696, 422, 764, 114, 0, 194,\n", + " 536, 388, 730\n", + " ],\n", + " [\n", + " 354, 674, 1130, 822, 708, 628, 856, 320, 662, 388, 730, 308, 194, 0,\n", + " 342, 422, 536\n", + " ],\n", + " [\n", + " 468, 1016, 788, 1164, 1050, 514, 514, 662, 320, 274, 388, 650, 536,\n", + " 342, 0, 764, 194\n", + " ],\n", + " [\n", + " 776, 868, 1552, 560, 674, 1050, 1278, 742, 1084, 810, 1152, 274,\n", + " 388, 422, 764, 0, 798\n", + " ],\n", + " [\n", + " 662, 1210, 754, 1358, 1244, 708, 480, 856, 514, 468, 354, 844, 730,\n", + " 536, 194, 798, 0\n", + " ],\n", + " ]\n", + " # [START initial_routes]\n", + " data['initial_routes'] = [\n", + " [8, 16, 14, 13, 12, 11],\n", + " [3, 4, 9, 10],\n", + " [15, 1],\n", + " [7, 5, 2, 6],\n", + " ]\n", + " # [END initial_routes]\n", + " data['num_vehicles'] = 4\n", + " data['depot'] = 0\n", + " return data\n", + " # [END data_model]\n", + "\n", + "\n", + "# [START solution_printer]\n", + "def print_solution(data, manager, routing, solution):\n", + " \"\"\"Prints solution on console.\"\"\"\n", + " max_route_distance = 0\n", + " for vehicle_id in range(data['num_vehicles']):\n", + " index = routing.Start(vehicle_id)\n", + " plan_output = 'Route for vehicle {}:\\n'.format(vehicle_id)\n", + " route_distance = 0\n", + " while not routing.IsEnd(index):\n", + " plan_output += ' {} -> '.format(manager.IndexToNode(index))\n", + " previous_index = index\n", + " index = solution.Value(routing.NextVar(index))\n", + " route_distance += routing.GetArcCostForVehicle(\n", + " previous_index, index, vehicle_id)\n", + " plan_output += '{}\\n'.format(manager.IndexToNode(index))\n", + " plan_output += 'Distance of the route: {}m\\n'.format(route_distance)\n", + " print(plan_output)\n", + " max_route_distance = max(route_distance, max_route_distance)\n", + " print('Maximum of the route distances: {}m'.format(max_route_distance))\n", + "\n", + "\n", + "# [END solution_printer]\n", + "\n", + "\n", + "\"\"\"Solve the CVRP problem.\"\"\"\n", + "# Instantiate the data problem.\n", + "# [START data]\n", + "data = create_data_model()\n", + "# [END data]\n", + "\n", + "# Create the routing index manager.\n", + "# [START index_manager]\n", + "manager = pywrapcp.RoutingIndexManager(len(data['distance_matrix']),\n", + " data['num_vehicles'], data['depot'])\n", + "# [END index_manager]\n", + "\n", + "# Create Routing Model.\n", + "# [START routing_model]\n", + "routing = pywrapcp.RoutingModel(manager)\n", + "\n", + "# [END routing_model]\n", + "\n", + "# Create and register a transit callback.\n", + "# [START transit_callback]\n", + "def distance_callback(from_index, to_index):\n", + " \"\"\"Returns the distance between the two nodes.\"\"\"\n", + " # Convert from routing variable Index to distance matrix NodeIndex.\n", + " from_node = manager.IndexToNode(from_index)\n", + " to_node = manager.IndexToNode(to_index)\n", + " return data['distance_matrix'][from_node][to_node]\n", + "\n", + "transit_callback_index = routing.RegisterTransitCallback(distance_callback)\n", + "# [END transit_callback]\n", + "\n", + "# Define cost of each arc.\n", + "# [START arc_cost]\n", + "routing.SetArcCostEvaluatorOfAllVehicles(transit_callback_index)\n", + "# [END arc_cost]\n", + "\n", + "# Add Distance constraint.\n", + "# [START distance_constraint]\n", + "dimension_name = 'Distance'\n", + "routing.AddDimension(\n", + " transit_callback_index,\n", + " 0, # no slack\n", + " 3000, # vehicle maximum travel distance\n", + " True, # start cumul to zero\n", + " dimension_name)\n", + "distance_dimension = routing.GetDimensionOrDie(dimension_name)\n", + "distance_dimension.SetGlobalSpanCostCoefficient(100)\n", + "# [END distance_constraint]\n", + "\n", + "# [START print_initial_solution]\n", + "initial_solution = routing.ReadAssignmentFromRoutes(data['initial_routes'],\n", + " True)\n", + "print('Initial solution:')\n", + "print_solution(data, manager, routing, initial_solution)\n", + "# [END print_initial_solution]\n", + "\n", + "# Set default search parameters.\n", + "# [START parameters]\n", + "search_parameters = pywrapcp.DefaultRoutingSearchParameters()\n", + "# [END parameters]\n", + "\n", + "# Solve the problem.\n", + "# [START solve]\n", + "solution = routing.SolveFromAssignmentWithParameters(\n", + " initial_solution, search_parameters)\n", + "# [END solve]\n", + "\n", + "# Print solution on console.\n", + "# [START print_solution]\n", + "if solution:\n", + " print('Solution after search:')\n", + " print_solution(data, manager, routing, solution)\n", + "# [END print_solution]\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/constraint_solver/vrp_pickup_delivery.ipynb b/examples/notebook/constraint_solver/vrp_pickup_delivery.ipynb new file mode 100644 index 0000000000..cdfa7c8fd8 --- /dev/null +++ b/examples/notebook/constraint_solver/vrp_pickup_delivery.ipynb @@ -0,0 +1,227 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010-2018 Google LLC\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "# [START program]\n", + "\"\"\"Simple Pickup Delivery Problem (PDP).\"\"\"\n", + "\n", + "# [START import]\n", + "from __future__ import print_function\n", + "from ortools.constraint_solver import routing_enums_pb2\n", + "from ortools.constraint_solver import pywrapcp\n", + "# [END import]\n", + "\n", + "\n", + "# [START data_model]\n", + "def create_data_model():\n", + " \"\"\"Stores the data for the problem.\"\"\"\n", + " data = {}\n", + " data['distance_matrix'] = [\n", + " [\n", + " 0, 548, 776, 696, 582, 274, 502, 194, 308, 194, 536, 502, 388, 354,\n", + " 468, 776, 662\n", + " ],\n", + " [\n", + " 548, 0, 684, 308, 194, 502, 730, 354, 696, 742, 1084, 594, 480, 674,\n", + " 1016, 868, 1210\n", + " ],\n", + " [\n", + " 776, 684, 0, 992, 878, 502, 274, 810, 468, 742, 400, 1278, 1164,\n", + " 1130, 788, 1552, 754\n", + " ],\n", + " [\n", + " 696, 308, 992, 0, 114, 650, 878, 502, 844, 890, 1232, 514, 628, 822,\n", + " 1164, 560, 1358\n", + " ],\n", + " [\n", + " 582, 194, 878, 114, 0, 536, 764, 388, 730, 776, 1118, 400, 514, 708,\n", + " 1050, 674, 1244\n", + " ],\n", + " [\n", + " 274, 502, 502, 650, 536, 0, 228, 308, 194, 240, 582, 776, 662, 628,\n", + " 514, 1050, 708\n", + " ],\n", + " [\n", + " 502, 730, 274, 878, 764, 228, 0, 536, 194, 468, 354, 1004, 890, 856,\n", + " 514, 1278, 480\n", + " ],\n", + " [\n", + " 194, 354, 810, 502, 388, 308, 536, 0, 342, 388, 730, 468, 354, 320,\n", + " 662, 742, 856\n", + " ],\n", + " [\n", + " 308, 696, 468, 844, 730, 194, 194, 342, 0, 274, 388, 810, 696, 662,\n", + " 320, 1084, 514\n", + " ],\n", + " [\n", + " 194, 742, 742, 890, 776, 240, 468, 388, 274, 0, 342, 536, 422, 388,\n", + " 274, 810, 468\n", + " ],\n", + " [\n", + " 536, 1084, 400, 1232, 1118, 582, 354, 730, 388, 342, 0, 878, 764,\n", + " 730, 388, 1152, 354\n", + " ],\n", + " [\n", + " 502, 594, 1278, 514, 400, 776, 1004, 468, 810, 536, 878, 0, 114,\n", + " 308, 650, 274, 844\n", + " ],\n", + " [\n", + " 388, 480, 1164, 628, 514, 662, 890, 354, 696, 422, 764, 114, 0, 194,\n", + " 536, 388, 730\n", + " ],\n", + " [\n", + " 354, 674, 1130, 822, 708, 628, 856, 320, 662, 388, 730, 308, 194, 0,\n", + " 342, 422, 536\n", + " ],\n", + " [\n", + " 468, 1016, 788, 1164, 1050, 514, 514, 662, 320, 274, 388, 650, 536,\n", + " 342, 0, 764, 194\n", + " ],\n", + " [\n", + " 776, 868, 1552, 560, 674, 1050, 1278, 742, 1084, 810, 1152, 274,\n", + " 388, 422, 764, 0, 798\n", + " ],\n", + " [\n", + " 662, 1210, 754, 1358, 1244, 708, 480, 856, 514, 468, 354, 844, 730,\n", + " 536, 194, 798, 0\n", + " ],\n", + " ]\n", + " # [START pickups_deliveries]\n", + " data['pickups_deliveries'] = [\n", + " [1, 6],\n", + " [2, 10],\n", + " [4, 3],\n", + " [5, 9],\n", + " [7, 8],\n", + " [15, 11],\n", + " [13, 12],\n", + " [16, 14],\n", + " ]\n", + " # [END pickups_deliveries]\n", + " data['num_vehicles'] = 4\n", + " data['depot'] = 0\n", + " return data\n", + " # [END data_model]\n", + "\n", + "\n", + "# [START solution_printer]\n", + "def print_solution(data, manager, routing, assignment):\n", + " \"\"\"Prints assignment on console.\"\"\"\n", + " total_distance = 0\n", + " for vehicle_id in range(data['num_vehicles']):\n", + " index = routing.Start(vehicle_id)\n", + " plan_output = 'Route for vehicle {}:\\n'.format(vehicle_id)\n", + " route_distance = 0\n", + " while not routing.IsEnd(index):\n", + " plan_output += ' {} -> '.format(manager.IndexToNode(index))\n", + " previous_index = index\n", + " index = assignment.Value(routing.NextVar(index))\n", + " route_distance += routing.GetArcCostForVehicle(\n", + " previous_index, index, vehicle_id)\n", + " plan_output += '{}\\n'.format(manager.IndexToNode(index))\n", + " plan_output += 'Distance of the route: {}m\\n'.format(route_distance)\n", + " print(plan_output)\n", + " total_distance += route_distance\n", + " print('Total Distance of all routes: {}m'.format(total_distance))\n", + " # [END solution_printer]\n", + "\n", + "\n", + "\"\"\"Entry point of the program.\"\"\"\n", + "# Instantiate the data problem.\n", + "# [START data]\n", + "data = create_data_model()\n", + "# [END data]\n", + "\n", + "# Create the routing index manager.\n", + "# [START index_manager]\n", + "manager = pywrapcp.RoutingIndexManager(len(data['distance_matrix']),\n", + " data['num_vehicles'], data['depot'])\n", + "# [END index_manager]\n", + "\n", + "# Create Routing Model.\n", + "# [START routing_model]\n", + "routing = pywrapcp.RoutingModel(manager)\n", + "\n", + "# [END routing_model]\n", + "\n", + "# Define cost of each arc.\n", + "# [START arc_cost]\n", + "def distance_callback(from_index, to_index):\n", + " \"\"\"Returns the manhattan distance between the two nodes.\"\"\"\n", + " # Convert from routing variable Index to distance matrix NodeIndex.\n", + " from_node = manager.IndexToNode(from_index)\n", + " to_node = manager.IndexToNode(to_index)\n", + " return data['distance_matrix'][from_node][to_node]\n", + "\n", + "transit_callback_index = routing.RegisterTransitCallback(distance_callback)\n", + "routing.SetArcCostEvaluatorOfAllVehicles(transit_callback_index)\n", + "# [END arc_cost]\n", + "\n", + "# Add Distance constraint.\n", + "# [START distance_constraint]\n", + "dimension_name = 'Distance'\n", + "routing.AddDimension(\n", + " transit_callback_index,\n", + " 0, # no slack\n", + " 3000, # vehicle maximum travel distance\n", + " True, # start cumul to zero\n", + " dimension_name)\n", + "distance_dimension = routing.GetDimensionOrDie(dimension_name)\n", + "distance_dimension.SetGlobalSpanCostCoefficient(100)\n", + "# [END distance_constraint]\n", + "\n", + "# Define Transportation Requests.\n", + "# [START pickup_delivery_constraint]\n", + "for request in data['pickups_deliveries']:\n", + " pickup_index = manager.NodeToIndex(request[0])\n", + " delivery_index = manager.NodeToIndex(request[1])\n", + " routing.AddPickupAndDelivery(pickup_index, delivery_index)\n", + " routing.solver().Add(\n", + " routing.VehicleVar(pickup_index) == routing.VehicleVar(\n", + " delivery_index))\n", + " routing.solver().Add(\n", + " distance_dimension.CumulVar(pickup_index) <=\n", + " distance_dimension.CumulVar(delivery_index))\n", + "# [END pickup_delivery_constraint]\n", + "\n", + "# Setting first solution heuristic.\n", + "# [START parameters]\n", + "search_parameters = pywrapcp.DefaultRoutingSearchParameters()\n", + "search_parameters.first_solution_strategy = (\n", + " routing_enums_pb2.FirstSolutionStrategy.PARALLEL_CHEAPEST_INSERTION)\n", + "# [END parameters]\n", + "\n", + "# Solve the problem.\n", + "# [START solve]\n", + "assignment = routing.SolveWithParameters(search_parameters)\n", + "# [END solve]\n", + "\n", + "# Print solution on console.\n", + "# [START print_solution]\n", + "if assignment:\n", + " print_solution(data, manager, routing, assignment)\n", + "# [END print_solution]\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/constraint_solver/vrp_pickup_delivery_fifo.ipynb b/examples/notebook/constraint_solver/vrp_pickup_delivery_fifo.ipynb new file mode 100644 index 0000000000..2d4e70bb8e --- /dev/null +++ b/examples/notebook/constraint_solver/vrp_pickup_delivery_fifo.ipynb @@ -0,0 +1,229 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010-2018 Google LLC\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "# [START program]\n", + "\"\"\"Simple Pickup Delivery Problem (PDP).\"\"\"\n", + "\n", + "# [START import]\n", + "from __future__ import print_function\n", + "from ortools.constraint_solver import routing_enums_pb2\n", + "from ortools.constraint_solver import pywrapcp\n", + "# [END import]\n", + "\n", + "\n", + "# [START data_model]\n", + "def create_data_model():\n", + " \"\"\"Stores the data for the problem.\"\"\"\n", + " data = {}\n", + " data['distance_matrix'] = [\n", + " [\n", + " 0, 548, 776, 696, 582, 274, 502, 194, 308, 194, 536, 502, 388, 354,\n", + " 468, 776, 662\n", + " ],\n", + " [\n", + " 548, 0, 684, 308, 194, 502, 730, 354, 696, 742, 1084, 594, 480, 674,\n", + " 1016, 868, 1210\n", + " ],\n", + " [\n", + " 776, 684, 0, 992, 878, 502, 274, 810, 468, 742, 400, 1278, 1164,\n", + " 1130, 788, 1552, 754\n", + " ],\n", + " [\n", + " 696, 308, 992, 0, 114, 650, 878, 502, 844, 890, 1232, 514, 628, 822,\n", + " 1164, 560, 1358\n", + " ],\n", + " [\n", + " 582, 194, 878, 114, 0, 536, 764, 388, 730, 776, 1118, 400, 514, 708,\n", + " 1050, 674, 1244\n", + " ],\n", + " [\n", + " 274, 502, 502, 650, 536, 0, 228, 308, 194, 240, 582, 776, 662, 628,\n", + " 514, 1050, 708\n", + " ],\n", + " [\n", + " 502, 730, 274, 878, 764, 228, 0, 536, 194, 468, 354, 1004, 890, 856,\n", + " 514, 1278, 480\n", + " ],\n", + " [\n", + " 194, 354, 810, 502, 388, 308, 536, 0, 342, 388, 730, 468, 354, 320,\n", + " 662, 742, 856\n", + " ],\n", + " [\n", + " 308, 696, 468, 844, 730, 194, 194, 342, 0, 274, 388, 810, 696, 662,\n", + " 320, 1084, 514\n", + " ],\n", + " [\n", + " 194, 742, 742, 890, 776, 240, 468, 388, 274, 0, 342, 536, 422, 388,\n", + " 274, 810, 468\n", + " ],\n", + " [\n", + " 536, 1084, 400, 1232, 1118, 582, 354, 730, 388, 342, 0, 878, 764,\n", + " 730, 388, 1152, 354\n", + " ],\n", + " [\n", + " 502, 594, 1278, 514, 400, 776, 1004, 468, 810, 536, 878, 0, 114,\n", + " 308, 650, 274, 844\n", + " ],\n", + " [\n", + " 388, 480, 1164, 628, 514, 662, 890, 354, 696, 422, 764, 114, 0, 194,\n", + " 536, 388, 730\n", + " ],\n", + " [\n", + " 354, 674, 1130, 822, 708, 628, 856, 320, 662, 388, 730, 308, 194, 0,\n", + " 342, 422, 536\n", + " ],\n", + " [\n", + " 468, 1016, 788, 1164, 1050, 514, 514, 662, 320, 274, 388, 650, 536,\n", + " 342, 0, 764, 194\n", + " ],\n", + " [\n", + " 776, 868, 1552, 560, 674, 1050, 1278, 742, 1084, 810, 1152, 274,\n", + " 388, 422, 764, 0, 798\n", + " ],\n", + " [\n", + " 662, 1210, 754, 1358, 1244, 708, 480, 856, 514, 468, 354, 844, 730,\n", + " 536, 194, 798, 0\n", + " ],\n", + " ]\n", + " # [START pickups_deliveries]\n", + " data['pickups_deliveries'] = [\n", + " [1, 6],\n", + " [2, 10],\n", + " [4, 3],\n", + " [5, 9],\n", + " [7, 8],\n", + " [15, 11],\n", + " [13, 12],\n", + " [16, 14],\n", + " ]\n", + " # [END pickups_deliveries]\n", + " data['num_vehicles'] = 4\n", + " data['depot'] = 0\n", + " return data\n", + " # [END data_model]\n", + "\n", + "\n", + "# [START solution_printer]\n", + "def print_solution(data, manager, routing, assignment):\n", + " \"\"\"Prints assignment on console.\"\"\"\n", + " total_distance = 0\n", + " for vehicle_id in range(data['num_vehicles']):\n", + " index = routing.Start(vehicle_id)\n", + " plan_output = 'Route for vehicle {}:\\n'.format(vehicle_id)\n", + " route_distance = 0\n", + " while not routing.IsEnd(index):\n", + " plan_output += ' {} -> '.format(manager.IndexToNode(index))\n", + " previous_index = index\n", + " index = assignment.Value(routing.NextVar(index))\n", + " route_distance += routing.GetArcCostForVehicle(\n", + " previous_index, index, vehicle_id)\n", + " plan_output += '{}\\n'.format(manager.IndexToNode(index))\n", + " plan_output += 'Distance of the route: {}m\\n'.format(route_distance)\n", + " print(plan_output)\n", + " total_distance += route_distance\n", + " print('Total Distance of all routes: {}m'.format(total_distance))\n", + " # [END solution_printer]\n", + "\n", + "\n", + "\"\"\"Entry point of the program.\"\"\"\n", + "# Instantiate the data problem.\n", + "# [START data]\n", + "data = create_data_model()\n", + "# [END data]\n", + "\n", + "# Create the routing index manager.\n", + "# [START index_manager]\n", + "manager = pywrapcp.RoutingIndexManager(len(data['distance_matrix']),\n", + " data['num_vehicles'], data['depot'])\n", + "# [END index_manager]\n", + "\n", + "# Create Routing Model.\n", + "# [START routing_model]\n", + "routing = pywrapcp.RoutingModel(manager)\n", + "\n", + "# [END routing_model]\n", + "\n", + "# Define cost of each arc.\n", + "# [START arc_cost]\n", + "def distance_callback(from_index, to_index):\n", + " \"\"\"Returns the manhattan distance between the two nodes.\"\"\"\n", + " # Convert from routing variable Index to distance matrix NodeIndex.\n", + " from_node = manager.IndexToNode(from_index)\n", + " to_node = manager.IndexToNode(to_index)\n", + " return data['distance_matrix'][from_node][to_node]\n", + "\n", + "transit_callback_index = routing.RegisterTransitCallback(distance_callback)\n", + "routing.SetArcCostEvaluatorOfAllVehicles(transit_callback_index)\n", + "# [END arc_cost]\n", + "\n", + "# Add Distance constraint.\n", + "# [START distance_constraint]\n", + "dimension_name = 'Distance'\n", + "routing.AddDimension(\n", + " transit_callback_index,\n", + " 0, # no slack\n", + " 3000, # vehicle maximum travel distance\n", + " True, # start cumul to zero\n", + " dimension_name)\n", + "distance_dimension = routing.GetDimensionOrDie(dimension_name)\n", + "distance_dimension.SetGlobalSpanCostCoefficient(100)\n", + "# [END distance_constraint]\n", + "\n", + "# Define Transportation Requests.\n", + "# [START pickup_delivery_constraint]\n", + "for request in data['pickups_deliveries']:\n", + " pickup_index = manager.NodeToIndex(request[0])\n", + " delivery_index = manager.NodeToIndex(request[1])\n", + " routing.AddPickupAndDelivery(pickup_index, delivery_index)\n", + " routing.solver().Add(\n", + " routing.VehicleVar(pickup_index) == routing.VehicleVar(\n", + " delivery_index))\n", + " routing.solver().Add(\n", + " distance_dimension.CumulVar(pickup_index) <=\n", + " distance_dimension.CumulVar(delivery_index))\n", + "routing.SetPickupAndDeliveryPolicyOfAllVehicles(\n", + " pywrapcp.RoutingModel.PICKUP_AND_DELIVERY_FIFO)\n", + "# [END pickup_delivery_constraint]\n", + "\n", + "# Setting first solution heuristic.\n", + "# [START parameters]\n", + "search_parameters = pywrapcp.DefaultRoutingSearchParameters()\n", + "search_parameters.first_solution_strategy = (\n", + " routing_enums_pb2.FirstSolutionStrategy.PARALLEL_CHEAPEST_INSERTION)\n", + "# [END parameters]\n", + "\n", + "# Solve the problem.\n", + "# [START solve]\n", + "assignment = routing.SolveWithParameters(search_parameters)\n", + "# [END solve]\n", + "\n", + "# Print solution on console.\n", + "# [START print_solution]\n", + "if assignment:\n", + " print_solution(data, manager, routing, assignment)\n", + "# [END print_solution]\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/constraint_solver/vrp_pickup_delivery_lifo.ipynb b/examples/notebook/constraint_solver/vrp_pickup_delivery_lifo.ipynb new file mode 100644 index 0000000000..0557697901 --- /dev/null +++ b/examples/notebook/constraint_solver/vrp_pickup_delivery_lifo.ipynb @@ -0,0 +1,229 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010-2018 Google LLC\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "# [START program]\n", + "\"\"\"Simple Pickup Delivery Problem (PDP).\"\"\"\n", + "\n", + "# [START import]\n", + "from __future__ import print_function\n", + "from ortools.constraint_solver import routing_enums_pb2\n", + "from ortools.constraint_solver import pywrapcp\n", + "# [END import]\n", + "\n", + "\n", + "# [START data_model]\n", + "def create_data_model():\n", + " \"\"\"Stores the data for the problem.\"\"\"\n", + " data = {}\n", + " data['distance_matrix'] = [\n", + " [\n", + " 0, 548, 776, 696, 582, 274, 502, 194, 308, 194, 536, 502, 388, 354,\n", + " 468, 776, 662\n", + " ],\n", + " [\n", + " 548, 0, 684, 308, 194, 502, 730, 354, 696, 742, 1084, 594, 480, 674,\n", + " 1016, 868, 1210\n", + " ],\n", + " [\n", + " 776, 684, 0, 992, 878, 502, 274, 810, 468, 742, 400, 1278, 1164,\n", + " 1130, 788, 1552, 754\n", + " ],\n", + " [\n", + " 696, 308, 992, 0, 114, 650, 878, 502, 844, 890, 1232, 514, 628, 822,\n", + " 1164, 560, 1358\n", + " ],\n", + " [\n", + " 582, 194, 878, 114, 0, 536, 764, 388, 730, 776, 1118, 400, 514, 708,\n", + " 1050, 674, 1244\n", + " ],\n", + " [\n", + " 274, 502, 502, 650, 536, 0, 228, 308, 194, 240, 582, 776, 662, 628,\n", + " 514, 1050, 708\n", + " ],\n", + " [\n", + " 502, 730, 274, 878, 764, 228, 0, 536, 194, 468, 354, 1004, 890, 856,\n", + " 514, 1278, 480\n", + " ],\n", + " [\n", + " 194, 354, 810, 502, 388, 308, 536, 0, 342, 388, 730, 468, 354, 320,\n", + " 662, 742, 856\n", + " ],\n", + " [\n", + " 308, 696, 468, 844, 730, 194, 194, 342, 0, 274, 388, 810, 696, 662,\n", + " 320, 1084, 514\n", + " ],\n", + " [\n", + " 194, 742, 742, 890, 776, 240, 468, 388, 274, 0, 342, 536, 422, 388,\n", + " 274, 810, 468\n", + " ],\n", + " [\n", + " 536, 1084, 400, 1232, 1118, 582, 354, 730, 388, 342, 0, 878, 764,\n", + " 730, 388, 1152, 354\n", + " ],\n", + " [\n", + " 502, 594, 1278, 514, 400, 776, 1004, 468, 810, 536, 878, 0, 114,\n", + " 308, 650, 274, 844\n", + " ],\n", + " [\n", + " 388, 480, 1164, 628, 514, 662, 890, 354, 696, 422, 764, 114, 0, 194,\n", + " 536, 388, 730\n", + " ],\n", + " [\n", + " 354, 674, 1130, 822, 708, 628, 856, 320, 662, 388, 730, 308, 194, 0,\n", + " 342, 422, 536\n", + " ],\n", + " [\n", + " 468, 1016, 788, 1164, 1050, 514, 514, 662, 320, 274, 388, 650, 536,\n", + " 342, 0, 764, 194\n", + " ],\n", + " [\n", + " 776, 868, 1552, 560, 674, 1050, 1278, 742, 1084, 810, 1152, 274,\n", + " 388, 422, 764, 0, 798\n", + " ],\n", + " [\n", + " 662, 1210, 754, 1358, 1244, 708, 480, 856, 514, 468, 354, 844, 730,\n", + " 536, 194, 798, 0\n", + " ],\n", + " ]\n", + " # [START pickups_deliveries]\n", + " data['pickups_deliveries'] = [\n", + " [1, 6],\n", + " [2, 10],\n", + " [4, 3],\n", + " [5, 9],\n", + " [7, 8],\n", + " [15, 11],\n", + " [13, 12],\n", + " [16, 14],\n", + " ]\n", + " # [END pickups_deliveries]\n", + " data['num_vehicles'] = 4\n", + " data['depot'] = 0\n", + " return data\n", + " # [END data_model]\n", + "\n", + "\n", + "# [START solution_printer]\n", + "def print_solution(data, manager, routing, assignment):\n", + " \"\"\"Prints assignment on console.\"\"\"\n", + " total_distance = 0\n", + " for vehicle_id in range(data['num_vehicles']):\n", + " index = routing.Start(vehicle_id)\n", + " plan_output = 'Route for vehicle {}:\\n'.format(vehicle_id)\n", + " route_distance = 0\n", + " while not routing.IsEnd(index):\n", + " plan_output += ' {} -> '.format(manager.IndexToNode(index))\n", + " previous_index = index\n", + " index = assignment.Value(routing.NextVar(index))\n", + " route_distance += routing.GetArcCostForVehicle(\n", + " previous_index, index, vehicle_id)\n", + " plan_output += '{}\\n'.format(manager.IndexToNode(index))\n", + " plan_output += 'Distance of the route: {}m\\n'.format(route_distance)\n", + " print(plan_output)\n", + " total_distance += route_distance\n", + " print('Total Distance of all routes: {}m'.format(total_distance))\n", + " # [END solution_printer]\n", + "\n", + "\n", + "\"\"\"Entry point of the program.\"\"\"\n", + "# Instantiate the data problem.\n", + "# [START data]\n", + "data = create_data_model()\n", + "# [END data]\n", + "\n", + "# Create the routing index manager.\n", + "# [START index_manager]\n", + "manager = pywrapcp.RoutingIndexManager(len(data['distance_matrix']),\n", + " data['num_vehicles'], data['depot'])\n", + "# [END index_manager]\n", + "\n", + "# Create Routing Model.\n", + "# [START routing_model]\n", + "routing = pywrapcp.RoutingModel(manager)\n", + "\n", + "# [END routing_model]\n", + "\n", + "# Define cost of each arc.\n", + "# [START arc_cost]\n", + "def distance_callback(from_index, to_index):\n", + " \"\"\"Returns the manhattan distance between the two nodes.\"\"\"\n", + " # Convert from routing variable Index to distance matrix NodeIndex.\n", + " from_node = manager.IndexToNode(from_index)\n", + " to_node = manager.IndexToNode(to_index)\n", + " return data['distance_matrix'][from_node][to_node]\n", + "\n", + "transit_callback_index = routing.RegisterTransitCallback(distance_callback)\n", + "routing.SetArcCostEvaluatorOfAllVehicles(transit_callback_index)\n", + "# [END arc_cost]\n", + "\n", + "# Add Distance constraint.\n", + "# [START distance_constraint]\n", + "dimension_name = 'Distance'\n", + "routing.AddDimension(\n", + " transit_callback_index,\n", + " 0, # no slack\n", + " 3000, # vehicle maximum travel distance\n", + " True, # start cumul to zero\n", + " dimension_name)\n", + "distance_dimension = routing.GetDimensionOrDie(dimension_name)\n", + "distance_dimension.SetGlobalSpanCostCoefficient(100)\n", + "# [END distance_constraint]\n", + "\n", + "# Define Transportation Requests.\n", + "# [START pickup_delivery_constraint]\n", + "for request in data['pickups_deliveries']:\n", + " pickup_index = manager.NodeToIndex(request[0])\n", + " delivery_index = manager.NodeToIndex(request[1])\n", + " routing.AddPickupAndDelivery(pickup_index, delivery_index)\n", + " routing.solver().Add(\n", + " routing.VehicleVar(pickup_index) == routing.VehicleVar(\n", + " delivery_index))\n", + " routing.solver().Add(\n", + " distance_dimension.CumulVar(pickup_index) <=\n", + " distance_dimension.CumulVar(delivery_index))\n", + "routing.SetPickupAndDeliveryPolicyOfAllVehicles(\n", + " pywrapcp.RoutingModel.PICKUP_AND_DELIVERY_LIFO)\n", + "# [END pickup_delivery_constraint]\n", + "\n", + "# Setting first solution heuristic.\n", + "# [START parameters]\n", + "search_parameters = pywrapcp.DefaultRoutingSearchParameters()\n", + "search_parameters.first_solution_strategy = (\n", + " routing_enums_pb2.FirstSolutionStrategy.PARALLEL_CHEAPEST_INSERTION)\n", + "# [END parameters]\n", + "\n", + "# Solve the problem.\n", + "# [START solve]\n", + "assignment = routing.SolveWithParameters(search_parameters)\n", + "# [END solve]\n", + "\n", + "# Print solution on console.\n", + "# [START print_solution]\n", + "if assignment:\n", + " print_solution(data, manager, routing, assignment)\n", + "# [END print_solution]\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/constraint_solver/vrp_resources.ipynb b/examples/notebook/constraint_solver/vrp_resources.ipynb new file mode 100644 index 0000000000..3f56ab4dfe --- /dev/null +++ b/examples/notebook/constraint_solver/vrp_resources.ipynb @@ -0,0 +1,227 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010-2018 Google LLC\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "# [START program]\n", + "\"\"\"Vehicles Routing Problem (VRP) with Resource Constraints.\"\"\"\n", + "\n", + "# [START import]\n", + "from __future__ import print_function\n", + "from ortools.constraint_solver import routing_enums_pb2\n", + "from ortools.constraint_solver import pywrapcp\n", + "# [END import]\n", + "\n", + "\n", + "# [START data_model]\n", + "def create_data_model():\n", + " \"\"\"Stores the data for the problem.\"\"\"\n", + " data = {}\n", + " data['time_matrix'] = [\n", + " [0, 6, 9, 8, 7, 3, 6, 2, 3, 2, 6, 6, 4, 4, 5, 9, 7],\n", + " [6, 0, 8, 3, 2, 6, 8, 4, 8, 8, 13, 7, 5, 8, 12, 10, 14],\n", + " [9, 8, 0, 11, 10, 6, 3, 9, 5, 8, 4, 15, 14, 13, 9, 18, 9],\n", + " [8, 3, 11, 0, 1, 7, 10, 6, 10, 10, 14, 6, 7, 9, 14, 6, 16],\n", + " [7, 2, 10, 1, 0, 6, 9, 4, 8, 9, 13, 4, 6, 8, 12, 8, 14],\n", + " [3, 6, 6, 7, 6, 0, 2, 3, 2, 2, 7, 9, 7, 7, 6, 12, 8],\n", + " [6, 8, 3, 10, 9, 2, 0, 6, 2, 5, 4, 12, 10, 10, 6, 15, 5],\n", + " [2, 4, 9, 6, 4, 3, 6, 0, 4, 4, 8, 5, 4, 3, 7, 8, 10],\n", + " [3, 8, 5, 10, 8, 2, 2, 4, 0, 3, 4, 9, 8, 7, 3, 13, 6],\n", + " [2, 8, 8, 10, 9, 2, 5, 4, 3, 0, 4, 6, 5, 4, 3, 9, 5],\n", + " [6, 13, 4, 14, 13, 7, 4, 8, 4, 4, 0, 10, 9, 8, 4, 13, 4],\n", + " [6, 7, 15, 6, 4, 9, 12, 5, 9, 6, 10, 0, 1, 3, 7, 3, 10],\n", + " [4, 5, 14, 7, 6, 7, 10, 4, 8, 5, 9, 1, 0, 2, 6, 4, 8],\n", + " [4, 8, 13, 9, 8, 7, 10, 3, 7, 4, 8, 3, 2, 0, 4, 5, 6],\n", + " [5, 12, 9, 14, 12, 6, 6, 7, 3, 3, 4, 7, 6, 4, 0, 9, 2],\n", + " [9, 10, 18, 6, 8, 12, 15, 8, 13, 9, 13, 3, 4, 5, 9, 0, 9],\n", + " [7, 14, 9, 16, 14, 8, 5, 10, 6, 5, 4, 10, 8, 6, 2, 9, 0],\n", + " ]\n", + " data['time_windows'] = [\n", + " (0, 5), # depot\n", + " (7, 12), # 1\n", + " (10, 15), # 2\n", + " (5, 14), # 3\n", + " (5, 13), # 4\n", + " (0, 5), # 5\n", + " (5, 10), # 6\n", + " (0, 10), # 7\n", + " (5, 10), # 8\n", + " (0, 5), # 9\n", + " (10, 16), # 10\n", + " (10, 15), # 11\n", + " (0, 5), # 12\n", + " (5, 10), # 13\n", + " (7, 12), # 14\n", + " (10, 15), # 15\n", + " (5, 15), # 16\n", + " ]\n", + " data['num_vehicles'] = 4\n", + " # [START resources_data]\n", + " data['vehicle_load_time'] = 5\n", + " data['vehicle_unload_time'] = 5\n", + " data['depot_capacity'] = 2\n", + " # [END resources_data]\n", + " data['depot'] = 0\n", + " return data\n", + " # [END data_model]\n", + "\n", + "\n", + "# [START solution_printer]\n", + "def print_solution(data, manager, routing, assignment):\n", + " \"\"\"Prints assignment on console.\"\"\"\n", + " time_dimension = routing.GetDimensionOrDie('Time')\n", + " total_time = 0\n", + " for vehicle_id in range(data['num_vehicles']):\n", + " index = routing.Start(vehicle_id)\n", + " plan_output = 'Route for vehicle {}:\\n'.format(vehicle_id)\n", + " while not routing.IsEnd(index):\n", + " time_var = time_dimension.CumulVar(index)\n", + " plan_output += '{0} Time({1},{2}) -> '.format(\n", + " manager.IndexToNode(index), assignment.Min(time_var),\n", + " assignment.Max(time_var))\n", + " index = assignment.Value(routing.NextVar(index))\n", + " time_var = time_dimension.CumulVar(index)\n", + " plan_output += '{0} Time({1},{2})\\n'.format(manager.IndexToNode(index),\n", + " assignment.Min(time_var),\n", + " assignment.Max(time_var))\n", + " plan_output += 'Time of the route: {}min\\n'.format(\n", + " assignment.Min(time_var))\n", + " print(plan_output)\n", + " total_time += assignment.Min(time_var)\n", + " print('Total time of all routes: {}min'.format(total_time))\n", + " # [END solution_printer]\n", + "\n", + "\n", + "\"\"\"Solve the VRP with time windows.\"\"\"\n", + "# Instantiate the data problem.\n", + "# [START data]\n", + "data = create_data_model()\n", + "# [END data]\n", + "\n", + "# Create the routing index manager.\n", + "# [START index_manager]\n", + "manager = pywrapcp.RoutingIndexManager(len(data['time_matrix']),\n", + " data['num_vehicles'], data['depot'])\n", + "# [END index_manager]\n", + "\n", + "# Create Routing Model.\n", + "# [START routing_model]\n", + "routing = pywrapcp.RoutingModel(manager)\n", + "\n", + "# [END routing_model]\n", + "\n", + "# Create and register a transit callback.\n", + "# [START transit_callback]\n", + "def time_callback(from_index, to_index):\n", + " \"\"\"Returns the travel time between the two nodes.\"\"\"\n", + " # Convert from routing variable Index to time matrix NodeIndex.\n", + " from_node = manager.IndexToNode(from_index)\n", + " to_node = manager.IndexToNode(to_index)\n", + " return data['time_matrix'][from_node][to_node]\n", + "\n", + "transit_callback_index = routing.RegisterTransitCallback(time_callback)\n", + "# [END transit_callback]\n", + "\n", + "# Define cost of each arc.\n", + "# [START arc_cost]\n", + "routing.SetArcCostEvaluatorOfAllVehicles(transit_callback_index)\n", + "# [END arc_cost]\n", + "\n", + "# Add Time Windows constraint.\n", + "# [START time_windows_constraint]\n", + "time = 'Time'\n", + "routing.AddDimension(\n", + " transit_callback_index,\n", + " 60, # allow waiting time\n", + " 60, # maximum time per vehicle\n", + " False, # Don't force start cumul to zero.\n", + " time)\n", + "time_dimension = routing.GetDimensionOrDie(time)\n", + "# Add time window constraints for each location except depot.\n", + "for location_idx, time_window in enumerate(data['time_windows']):\n", + " if location_idx == 0:\n", + " continue\n", + " index = manager.NodeToIndex(location_idx)\n", + " time_dimension.CumulVar(index).SetRange(time_window[0], time_window[1])\n", + "# Add time window constraints for each vehicle start node.\n", + "for vehicle_id in range(data['num_vehicles']):\n", + " index = routing.Start(vehicle_id)\n", + " time_dimension.CumulVar(index).SetRange(data['time_windows'][0][0],\n", + " data['time_windows'][0][1])\n", + "# [END time_windows_constraint]\n", + "\n", + "# Add resource constraints at the depot.\n", + "# [START depot_load_time]\n", + "solver = routing.solver()\n", + "intervals = []\n", + "for i in range(data['num_vehicles']):\n", + " # Add time windows at start of routes\n", + " intervals.append(\n", + " solver.FixedDurationIntervalVar(\n", + " time_dimension.CumulVar(routing.Start(i)),\n", + " data['vehicle_load_time'], 'depot_interval'))\n", + " # Add time windows at end of routes.\n", + " intervals.append(\n", + " solver.FixedDurationIntervalVar(\n", + " time_dimension.CumulVar(routing.End(i)),\n", + " data['vehicle_unload_time'], 'depot_interval'))\n", + "# [END depot_load_time]\n", + "\n", + "# [START depot_capacity]\n", + "depot_usage = [1 for i in range(len(intervals))]\n", + "solver.Add(\n", + " solver.Cumulative(intervals, depot_usage, data['depot_capacity'],\n", + " 'depot'))\n", + "# [END depot_capacity]\n", + "\n", + "# Instantiate route start and end times to produce feasible times.\n", + "# [START depot_start_end_times]\n", + "for i in range(data['num_vehicles']):\n", + " routing.AddVariableMinimizedByFinalizer(\n", + " time_dimension.CumulVar(routing.Start(i)))\n", + " routing.AddVariableMinimizedByFinalizer(\n", + " time_dimension.CumulVar(routing.End(i)))\n", + "# [END depot_start_end_times]\n", + "\n", + "# Setting first solution heuristic.\n", + "# [START parameters]\n", + "search_parameters = pywrapcp.DefaultRoutingSearchParameters()\n", + "search_parameters.first_solution_strategy = (\n", + " routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC)\n", + "# [END parameters]\n", + "\n", + "# Solve the problem.\n", + "# [START solve]\n", + "assignment = routing.SolveWithParameters(search_parameters)\n", + "# [END solve]\n", + "\n", + "# Print solution on console.\n", + "# [START print_solution]\n", + "if assignment:\n", + " print_solution(data, manager, routing, assignment)\n", + "# [END print_solution]\n", + "else:\n", + " print('No solution found !')\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/constraint_solver/vrp_starts_ends.ipynb b/examples/notebook/constraint_solver/vrp_starts_ends.ipynb new file mode 100644 index 0000000000..1f7b90e4b2 --- /dev/null +++ b/examples/notebook/constraint_solver/vrp_starts_ends.ipynb @@ -0,0 +1,209 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010-2018 Google LLC\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "# [START program]\n", + "\"\"\"Simple Vehicles Routing Problem.\"\"\"\n", + "\n", + "# [START import]\n", + "from __future__ import print_function\n", + "from ortools.constraint_solver import routing_enums_pb2\n", + "from ortools.constraint_solver import pywrapcp\n", + "# [END import]\n", + "\n", + "\n", + "# [START data_model]\n", + "def create_data_model():\n", + " \"\"\"Stores the data for the problem.\"\"\"\n", + " data = {}\n", + " data['distance_matrix'] = [\n", + " [\n", + " 0, 548, 776, 696, 582, 274, 502, 194, 308, 194, 536, 502, 388, 354,\n", + " 468, 776, 662\n", + " ],\n", + " [\n", + " 548, 0, 684, 308, 194, 502, 730, 354, 696, 742, 1084, 594, 480, 674,\n", + " 1016, 868, 1210\n", + " ],\n", + " [\n", + " 776, 684, 0, 992, 878, 502, 274, 810, 468, 742, 400, 1278, 1164,\n", + " 1130, 788, 1552, 754\n", + " ],\n", + " [\n", + " 696, 308, 992, 0, 114, 650, 878, 502, 844, 890, 1232, 514, 628, 822,\n", + " 1164, 560, 1358\n", + " ],\n", + " [\n", + " 582, 194, 878, 114, 0, 536, 764, 388, 730, 776, 1118, 400, 514, 708,\n", + " 1050, 674, 1244\n", + " ],\n", + " [\n", + " 274, 502, 502, 650, 536, 0, 228, 308, 194, 240, 582, 776, 662, 628,\n", + " 514, 1050, 708\n", + " ],\n", + " [\n", + " 502, 730, 274, 878, 764, 228, 0, 536, 194, 468, 354, 1004, 890, 856,\n", + " 514, 1278, 480\n", + " ],\n", + " [\n", + " 194, 354, 810, 502, 388, 308, 536, 0, 342, 388, 730, 468, 354, 320,\n", + " 662, 742, 856\n", + " ],\n", + " [\n", + " 308, 696, 468, 844, 730, 194, 194, 342, 0, 274, 388, 810, 696, 662,\n", + " 320, 1084, 514\n", + " ],\n", + " [\n", + " 194, 742, 742, 890, 776, 240, 468, 388, 274, 0, 342, 536, 422, 388,\n", + " 274, 810, 468\n", + " ],\n", + " [\n", + " 536, 1084, 400, 1232, 1118, 582, 354, 730, 388, 342, 0, 878, 764,\n", + " 730, 388, 1152, 354\n", + " ],\n", + " [\n", + " 502, 594, 1278, 514, 400, 776, 1004, 468, 810, 536, 878, 0, 114,\n", + " 308, 650, 274, 844\n", + " ],\n", + " [\n", + " 388, 480, 1164, 628, 514, 662, 890, 354, 696, 422, 764, 114, 0, 194,\n", + " 536, 388, 730\n", + " ],\n", + " [\n", + " 354, 674, 1130, 822, 708, 628, 856, 320, 662, 388, 730, 308, 194, 0,\n", + " 342, 422, 536\n", + " ],\n", + " [\n", + " 468, 1016, 788, 1164, 1050, 514, 514, 662, 320, 274, 388, 650, 536,\n", + " 342, 0, 764, 194\n", + " ],\n", + " [\n", + " 776, 868, 1552, 560, 674, 1050, 1278, 742, 1084, 810, 1152, 274,\n", + " 388, 422, 764, 0, 798\n", + " ],\n", + " [\n", + " 662, 1210, 754, 1358, 1244, 708, 480, 856, 514, 468, 354, 844, 730,\n", + " 536, 194, 798, 0\n", + " ],\n", + " ]\n", + " data['num_vehicles'] = 4\n", + " # [START starts_ends]\n", + " data['starts'] = [1, 2, 15, 16]\n", + " data['ends'] = [0, 0, 0, 0]\n", + " # [END starts_ends]\n", + " return data\n", + " # [END data_model]\n", + "\n", + "\n", + "# [START solution_printer]\n", + "def print_solution(data, manager, routing, solution):\n", + " \"\"\"Prints solution on console.\"\"\"\n", + " max_route_distance = 0\n", + " for vehicle_id in range(data['num_vehicles']):\n", + " index = routing.Start(vehicle_id)\n", + " plan_output = 'Route for vehicle {}:\\n'.format(vehicle_id)\n", + " route_distance = 0\n", + " while not routing.IsEnd(index):\n", + " plan_output += ' {} -> '.format(manager.IndexToNode(index))\n", + " previous_index = index\n", + " index = solution.Value(routing.NextVar(index))\n", + " route_distance += routing.GetArcCostForVehicle(\n", + " previous_index, index, vehicle_id)\n", + " plan_output += '{}\\n'.format(manager.IndexToNode(index))\n", + " plan_output += 'Distance of the route: {}m\\n'.format(route_distance)\n", + " print(plan_output)\n", + " max_route_distance = max(route_distance, max_route_distance)\n", + " print('Maximum of the route distances: {}m'.format(max_route_distance))\n", + " # [END solution_printer]\n", + "\n", + "\n", + "\"\"\"Entry point of the program.\"\"\"\n", + "# Instantiate the data problem.\n", + "# [START data]\n", + "data = create_data_model()\n", + "# [END data]\n", + "\n", + "# Create the routing index manager.\n", + "# [START index_manager]\n", + "manager = pywrapcp.RoutingIndexManager(len(data['distance_matrix']),\n", + " data['num_vehicles'], data['starts'],\n", + " data['ends'])\n", + "# [END index_manager]\n", + "\n", + "# Create Routing Model.\n", + "# [START routing_model]\n", + "routing = pywrapcp.RoutingModel(manager)\n", + "\n", + "# [END routing_model]\n", + "\n", + "# Create and register a transit callback.\n", + "# [START transit_callback]\n", + "def distance_callback(from_index, to_index):\n", + " \"\"\"Returns the distance between the two nodes.\"\"\"\n", + " # Convert from routing variable Index to distance matrix NodeIndex.\n", + " from_node = manager.IndexToNode(from_index)\n", + " to_node = manager.IndexToNode(to_index)\n", + " return data['distance_matrix'][from_node][to_node]\n", + "\n", + "transit_callback_index = routing.RegisterTransitCallback(distance_callback)\n", + "# [END transit_callback]\n", + "\n", + "# Define cost of each arc.\n", + "# [START arc_cost]\n", + "routing.SetArcCostEvaluatorOfAllVehicles(transit_callback_index)\n", + "# [END arc_cost]\n", + "\n", + "# Add Distance constraint.\n", + "# [START distance_constraint]\n", + "dimension_name = 'Distance'\n", + "routing.AddDimension(\n", + " transit_callback_index,\n", + " 0, # no slack\n", + " 2000, # vehicle maximum travel distance\n", + " True, # start cumul to zero\n", + " dimension_name)\n", + "distance_dimension = routing.GetDimensionOrDie(dimension_name)\n", + "distance_dimension.SetGlobalSpanCostCoefficient(100)\n", + "# [END distance_constraint]\n", + "\n", + "# Setting first solution heuristic.\n", + "# [START parameters]\n", + "search_parameters = pywrapcp.DefaultRoutingSearchParameters()\n", + "search_parameters.first_solution_strategy = (\n", + " routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC)\n", + "# [END parameters]\n", + "\n", + "# Solve the problem.\n", + "# [START solve]\n", + "solution = routing.SolveWithParameters(search_parameters)\n", + "# [END solve]\n", + "\n", + "# Print solution on console.\n", + "# [START print_solution]\n", + "if solution:\n", + " print_solution(data, manager, routing, solution)\n", + "# [END print_solution]\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/constraint_solver/vrp_time_windows.ipynb b/examples/notebook/constraint_solver/vrp_time_windows.ipynb new file mode 100644 index 0000000000..ac99bb354b --- /dev/null +++ b/examples/notebook/constraint_solver/vrp_time_windows.ipynb @@ -0,0 +1,196 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010-2018 Google LLC\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "# [START program]\n", + "\"\"\"Vehicles Routing Problem (VRP) with Time Windows.\"\"\"\n", + "\n", + "# [START import]\n", + "from __future__ import print_function\n", + "from ortools.constraint_solver import routing_enums_pb2\n", + "from ortools.constraint_solver import pywrapcp\n", + "# [END import]\n", + "\n", + "\n", + "# [START data_model]\n", + "def create_data_model():\n", + " \"\"\"Stores the data for the problem.\"\"\"\n", + " data = {}\n", + " data['time_matrix'] = [\n", + " [0, 6, 9, 8, 7, 3, 6, 2, 3, 2, 6, 6, 4, 4, 5, 9, 7],\n", + " [6, 0, 8, 3, 2, 6, 8, 4, 8, 8, 13, 7, 5, 8, 12, 10, 14],\n", + " [9, 8, 0, 11, 10, 6, 3, 9, 5, 8, 4, 15, 14, 13, 9, 18, 9],\n", + " [8, 3, 11, 0, 1, 7, 10, 6, 10, 10, 14, 6, 7, 9, 14, 6, 16],\n", + " [7, 2, 10, 1, 0, 6, 9, 4, 8, 9, 13, 4, 6, 8, 12, 8, 14],\n", + " [3, 6, 6, 7, 6, 0, 2, 3, 2, 2, 7, 9, 7, 7, 6, 12, 8],\n", + " [6, 8, 3, 10, 9, 2, 0, 6, 2, 5, 4, 12, 10, 10, 6, 15, 5],\n", + " [2, 4, 9, 6, 4, 3, 6, 0, 4, 4, 8, 5, 4, 3, 7, 8, 10],\n", + " [3, 8, 5, 10, 8, 2, 2, 4, 0, 3, 4, 9, 8, 7, 3, 13, 6],\n", + " [2, 8, 8, 10, 9, 2, 5, 4, 3, 0, 4, 6, 5, 4, 3, 9, 5],\n", + " [6, 13, 4, 14, 13, 7, 4, 8, 4, 4, 0, 10, 9, 8, 4, 13, 4],\n", + " [6, 7, 15, 6, 4, 9, 12, 5, 9, 6, 10, 0, 1, 3, 7, 3, 10],\n", + " [4, 5, 14, 7, 6, 7, 10, 4, 8, 5, 9, 1, 0, 2, 6, 4, 8],\n", + " [4, 8, 13, 9, 8, 7, 10, 3, 7, 4, 8, 3, 2, 0, 4, 5, 6],\n", + " [5, 12, 9, 14, 12, 6, 6, 7, 3, 3, 4, 7, 6, 4, 0, 9, 2],\n", + " [9, 10, 18, 6, 8, 12, 15, 8, 13, 9, 13, 3, 4, 5, 9, 0, 9],\n", + " [7, 14, 9, 16, 14, 8, 5, 10, 6, 5, 4, 10, 8, 6, 2, 9, 0],\n", + " ]\n", + " data['time_windows'] = [\n", + " (0, 5), # depot\n", + " (7, 12), # 1\n", + " (10, 15), # 2\n", + " (5, 14), # 3\n", + " (5, 13), # 4\n", + " (0, 5), # 5\n", + " (5, 10), # 6\n", + " (0, 10), # 7\n", + " (5, 10), # 8\n", + " (0, 5), # 9\n", + " (10, 16), # 10\n", + " (10, 15), # 11\n", + " (0, 5), # 12\n", + " (5, 10), # 13\n", + " (7, 12), # 14\n", + " (10, 15), # 15\n", + " (5, 15), # 16\n", + " ]\n", + " data['num_vehicles'] = 4\n", + " data['depot'] = 0\n", + " return data\n", + " # [END data_model]\n", + "\n", + "\n", + "# [START solution_printer]\n", + "def print_solution(data, manager, routing, assignment):\n", + " \"\"\"Prints assignment on console.\"\"\"\n", + " time_dimension = routing.GetDimensionOrDie('Time')\n", + " total_time = 0\n", + " for vehicle_id in range(data['num_vehicles']):\n", + " index = routing.Start(vehicle_id)\n", + " plan_output = 'Route for vehicle {}:\\n'.format(vehicle_id)\n", + " while not routing.IsEnd(index):\n", + " time_var = time_dimension.CumulVar(index)\n", + " plan_output += '{0} Time({1},{2}) -> '.format(\n", + " manager.IndexToNode(index), assignment.Min(time_var),\n", + " assignment.Max(time_var))\n", + " index = assignment.Value(routing.NextVar(index))\n", + " time_var = time_dimension.CumulVar(index)\n", + " plan_output += '{0} Time({1},{2})\\n'.format(manager.IndexToNode(index),\n", + " assignment.Min(time_var),\n", + " assignment.Max(time_var))\n", + " plan_output += 'Time of the route: {}min\\n'.format(\n", + " assignment.Min(time_var))\n", + " print(plan_output)\n", + " total_time += assignment.Min(time_var)\n", + " print('Total time of all routes: {}min'.format(total_time))\n", + " # [END solution_printer]\n", + "\n", + "\n", + "\"\"\"Solve the VRP with time windows.\"\"\"\n", + "# Instantiate the data problem.\n", + "# [START data]\n", + "data = create_data_model()\n", + "# [END data]\n", + "\n", + "# Create the routing index manager.\n", + "# [START index_manager]\n", + "manager = pywrapcp.RoutingIndexManager(len(data['time_matrix']),\n", + " data['num_vehicles'], data['depot'])\n", + "# [END index_manager]\n", + "\n", + "# Create Routing Model.\n", + "# [START routing_model]\n", + "routing = pywrapcp.RoutingModel(manager)\n", + "\n", + "# [END routing_model]\n", + "\n", + "# Create and register a transit callback.\n", + "# [START transit_callback]\n", + "def time_callback(from_index, to_index):\n", + " \"\"\"Returns the travel time between the two nodes.\"\"\"\n", + " # Convert from routing variable Index to time matrix NodeIndex.\n", + " from_node = manager.IndexToNode(from_index)\n", + " to_node = manager.IndexToNode(to_index)\n", + " return data['time_matrix'][from_node][to_node]\n", + "\n", + "transit_callback_index = routing.RegisterTransitCallback(time_callback)\n", + "# [END transit_callback]\n", + "\n", + "# Define cost of each arc.\n", + "# [START arc_cost]\n", + "routing.SetArcCostEvaluatorOfAllVehicles(transit_callback_index)\n", + "# [END arc_cost]\n", + "\n", + "# Add Time Windows constraint.\n", + "# [START time_windows_constraint]\n", + "time = 'Time'\n", + "routing.AddDimension(\n", + " transit_callback_index,\n", + " 30, # allow waiting time\n", + " 30, # maximum time per vehicle\n", + " False, # Don't force start cumul to zero.\n", + " time)\n", + "time_dimension = routing.GetDimensionOrDie(time)\n", + "# Add time window constraints for each location except depot.\n", + "for location_idx, time_window in enumerate(data['time_windows']):\n", + " if location_idx == 0:\n", + " continue\n", + " index = manager.NodeToIndex(location_idx)\n", + " time_dimension.CumulVar(index).SetRange(time_window[0], time_window[1])\n", + "# Add time window constraints for each vehicle start node.\n", + "for vehicle_id in range(data['num_vehicles']):\n", + " index = routing.Start(vehicle_id)\n", + " time_dimension.CumulVar(index).SetRange(data['time_windows'][0][0],\n", + " data['time_windows'][0][1])\n", + "# [END time_windows_constraint]\n", + "\n", + "# Instantiate route start and end times to produce feasible times.\n", + "# [START depot_start_end_times]\n", + "for i in range(data['num_vehicles']):\n", + " routing.AddVariableMinimizedByFinalizer(\n", + " time_dimension.CumulVar(routing.Start(i)))\n", + " routing.AddVariableMinimizedByFinalizer(\n", + " time_dimension.CumulVar(routing.End(i)))\n", + "# [END depot_start_end_times]\n", + "\n", + "# Setting first solution heuristic.\n", + "# [START parameters]\n", + "search_parameters = pywrapcp.DefaultRoutingSearchParameters()\n", + "search_parameters.first_solution_strategy = (\n", + " routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC)\n", + "# [END parameters]\n", + "\n", + "# Solve the problem.\n", + "# [START solve]\n", + "assignment = routing.SolveWithParameters(search_parameters)\n", + "# [END solve]\n", + "\n", + "# Print solution on console.\n", + "# [START print_solution]\n", + "if assignment:\n", + " print_solution(data, manager, routing, assignment)\n", + "# [END print_solution]\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/constraint_solver/vrp_with_time_limit.ipynb b/examples/notebook/constraint_solver/vrp_with_time_limit.ipynb new file mode 100644 index 0000000000..6b99b15919 --- /dev/null +++ b/examples/notebook/constraint_solver/vrp_with_time_limit.ipynb @@ -0,0 +1,130 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010-2018 Google LLC\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "# [START program]\n", + "\"\"\"Vehicles Routing Problem (VRP).\"\"\"\n", + "\n", + "# [START import]\n", + "from __future__ import print_function\n", + "from ortools.constraint_solver import routing_enums_pb2\n", + "from ortools.constraint_solver import pywrapcp\n", + "# [END import]\n", + "\n", + "# [START solution_printer]\n", + "def print_solution(manager, routing, solution):\n", + " \"\"\"Prints solution on console.\"\"\"\n", + " max_route_distance = 0\n", + " for vehicle_id in range(manager.GetNumberOfVehicles()):\n", + " index = routing.Start(vehicle_id)\n", + " plan_output = 'Route for vehicle {}:\\n'.format(vehicle_id)\n", + " route_distance = 0\n", + " while not routing.IsEnd(index):\n", + " plan_output += ' {} -> '.format(manager.IndexToNode(index))\n", + " previous_index = index\n", + " index = solution.Value(routing.NextVar(index))\n", + " route_distance += routing.GetArcCostForVehicle(\n", + " previous_index, index, vehicle_id)\n", + " plan_output += '{}\\n'.format(manager.IndexToNode(index))\n", + " plan_output += 'Distance of the route: {}m\\n'.format(route_distance)\n", + " print(plan_output)\n", + " max_route_distance = max(route_distance, max_route_distance)\n", + " print('Maximum of the route distances: {}m'.format(max_route_distance))\n", + "# [END solution_printer]\n", + "\n", + "\n", + "\"\"\"Solve the CVRP problem.\"\"\"\n", + "# Instantiate the data problem.\n", + "# [START data]\n", + "num_locations = 20;\n", + "num_vehicles = 5;\n", + "depot = 0;\n", + "# [END data]\n", + "\n", + "# Create the routing index manager.\n", + "# [START index_manager]\n", + "manager = pywrapcp.RoutingIndexManager(\n", + " num_locations, num_vehicles, depot)\n", + "# [END index_manager]\n", + "\n", + "# Create Routing Model.\n", + "# [START routing_model]\n", + "routing = pywrapcp.RoutingModel(manager)\n", + "\n", + "# [END routing_model]\n", + "\n", + "# Create and register a transit callback.\n", + "# [START transit_callback]\n", + "def distance_callback(from_index, to_index):\n", + " \"\"\"Returns the distance between the two nodes.\"\"\"\n", + " # Convert from routing variable Index to distance matrix NodeIndex.\n", + " from_node = manager.IndexToNode(from_index)\n", + " to_node = manager.IndexToNode(to_index)\n", + " return 1\n", + "\n", + "transit_callback_index = routing.RegisterTransitCallback(distance_callback)\n", + "# [END transit_callback]\n", + "\n", + "# Define cost of each arc.\n", + "# [START arc_cost]\n", + "routing.SetArcCostEvaluatorOfAllVehicles(transit_callback_index)\n", + "# [END arc_cost]\n", + "\n", + "# Add Distance constraint.\n", + "# [START distance_constraint]\n", + "dimension_name = 'Distance'\n", + "routing.AddDimension(\n", + " transit_callback_index,\n", + " 0, # no slack\n", + " 3000, # vehicle maximum travel distance\n", + " True, # start cumul to zero\n", + " dimension_name)\n", + "distance_dimension = routing.GetDimensionOrDie(dimension_name)\n", + "distance_dimension.SetGlobalSpanCostCoefficient(100)\n", + "# [END distance_constraint]\n", + "\n", + "# Setting first solution heuristic.\n", + "# [START parameters]\n", + "search_parameters = pywrapcp.DefaultRoutingSearchParameters()\n", + "search_parameters.first_solution_strategy = (\n", + " routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC)\n", + "search_parameters.local_search_metaheuristic = (\n", + " routing_enums_pb2.LocalSearchMetaheuristic.GUIDED_LOCAL_SEARCH)\n", + "search_parameters.log_search = True\n", + "search_parameters.time_limit.FromSeconds(10)\n", + "# [END parameters]\n", + "\n", + "# Solve the problem.\n", + "# [START solve]\n", + "solution = routing.SolveWithParameters(search_parameters)\n", + "# [END solve]\n", + "\n", + "# Print solution on console.\n", + "# [START print_solution]\n", + "if solution:\n", + " print_solution(manager, routing, solution)\n", + "# [END print_solution]\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/constraint_solver/vrpgs.ipynb b/examples/notebook/constraint_solver/vrpgs.ipynb new file mode 100644 index 0000000000..fafac349d7 --- /dev/null +++ b/examples/notebook/constraint_solver/vrpgs.ipynb @@ -0,0 +1,174 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "#!/usr/bin/env python\n", + "# This Python file uses the following encoding: utf-8\n", + "# Copyright 2015 Tin Arm Engineering AB\n", + "# Copyright 2018 Google LLC\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"Vehicle Routing Problem (VRP).\n", + "\n", + " This is a sample using the routing library python wrapper to solve a VRP\n", + " problem.\n", + " A description of the problem can be found here:\n", + " http://en.wikipedia.org/wiki/Vehicle_routing_problem.\n", + "\n", + " Distances are in meters.\n", + "\"\"\"\n", + "\n", + "from __future__ import print_function\n", + "\n", + "from functools import partial\n", + "from six.moves import xrange\n", + "\n", + "from ortools.constraint_solver import pywrapcp\n", + "from ortools.constraint_solver import routing_enums_pb2\n", + "\n", + "\n", + "###########################\n", + "# Problem Data Definition #\n", + "###########################\n", + "def create_data_model():\n", + " \"\"\"Stores the data for the problem\"\"\"\n", + " data = {}\n", + " # Locations in block unit\n", + " _locations = \\\n", + " [(4, 4), # depot\n", + " (2, 0), (8, 0), # locations to visit\n", + " (0, 1), (1, 1),\n", + " (5, 2), (7, 2),\n", + " (3, 3), (6, 3),\n", + " (5, 5), (8, 5),\n", + " (1, 6), (2, 6),\n", + " (3, 7), (6, 7),\n", + " (0, 8), (7, 8)]\n", + " # Compute locations in meters using the block dimension defined as follow\n", + " # Manhattan average block: 750ft x 264ft -> 228m x 80m\n", + " # here we use: 114m x 80m city block\n", + " # src: https://nyti.ms/2GDoRIe 'NY Times: Know Your distance'\n", + " data['locations'] = [(l[0] * 114, l[1] * 80) for l in _locations]\n", + " data['num_locations'] = len(data['locations'])\n", + " data['num_vehicles'] = 4\n", + " data['depot'] = 0\n", + " return data\n", + "\n", + "\n", + "#######################\n", + "# Problem Constraints #\n", + "#######################\n", + "def manhattan_distance(position_1, position_2):\n", + " \"\"\"Computes the Manhattan distance between two points\"\"\"\n", + " return (\n", + " abs(position_1[0] - position_2[0]) + abs(position_1[1] - position_2[1]))\n", + "\n", + "\n", + "def create_distance_evaluator(data):\n", + " \"\"\"Creates callback to return distance between points.\"\"\"\n", + " _distances = {}\n", + " # precompute distance between location to have distance callback in O(1)\n", + " for from_node in xrange(data['num_locations']):\n", + " _distances[from_node] = {}\n", + " for to_node in xrange(data['num_locations']):\n", + " if from_node == to_node:\n", + " _distances[from_node][to_node] = 0\n", + " else:\n", + " _distances[from_node][to_node] = (manhattan_distance(\n", + " data['locations'][from_node], data['locations'][to_node]))\n", + "\n", + " def distance_evaluator(manager, from_node, to_node):\n", + " \"\"\"Returns the manhattan distance between the two nodes\"\"\"\n", + " return _distances[manager.IndexToNode(from_node)][manager.IndexToNode(\n", + " to_node)]\n", + "\n", + " return distance_evaluator\n", + "\n", + "\n", + "def add_distance_dimension(routing, distance_evaluator_index):\n", + " \"\"\"Add Global Span constraint\"\"\"\n", + " distance = 'Distance'\n", + " routing.AddDimension(\n", + " distance_evaluator_index,\n", + " 0, # null slack\n", + " 3000, # maximum distance per vehicle\n", + " True, # start cumul to zero\n", + " distance)\n", + " distance_dimension = routing.GetDimensionOrDie(distance)\n", + " # Try to minimize the max distance among vehicles.\n", + " # /!\\ It doesn't mean the standard deviation is minimized\n", + " distance_dimension.SetGlobalSpanCostCoefficient(100)\n", + "\n", + "\n", + "###########\n", + "# Printer #\n", + "###########\n", + "def print_solution(data, routing, manager, assignment): # pylint:disable=too-many-locals\n", + " \"\"\"Prints assignment on console\"\"\"\n", + " print('Objective: {}'.format(assignment.ObjectiveValue()))\n", + " total_distance = 0\n", + " for vehicle_id in xrange(data['num_vehicles']):\n", + " index = routing.Start(vehicle_id)\n", + " plan_output = 'Route for vehicle {}:\\n'.format(vehicle_id)\n", + " distance = 0\n", + " while not routing.IsEnd(index):\n", + " plan_output += ' {} ->'.format(manager.IndexToNode(index))\n", + " previous_index = index\n", + " index = assignment.Value(routing.NextVar(index))\n", + " distance += routing.GetArcCostForVehicle(previous_index, index,\n", + " vehicle_id)\n", + " plan_output += ' {}\\n'.format(manager.IndexToNode(index))\n", + " plan_output += 'Distance of the route: {}m\\n'.format(distance)\n", + " print(plan_output)\n", + " total_distance += distance\n", + " print('Total Distance of all routes: {}m'.format(total_distance))\n", + "\n", + "\n", + "########\n", + "# Main #\n", + "########\n", + "\"\"\"Entry point of the program\"\"\"\n", + "# Instantiate the data problem.\n", + "data = create_data_model()\n", + "\n", + "# Create the routing index manager\n", + "manager = pywrapcp.RoutingIndexManager(data['num_locations'],\n", + " data['num_vehicles'], data['depot'])\n", + "\n", + "# Create Routing Model\n", + "routing = pywrapcp.RoutingModel(manager)\n", + "\n", + "# Define weight of each edge\n", + "distance_evaluator_index = routing.RegisterTransitCallback(\n", + " partial(create_distance_evaluator(data), manager))\n", + "routing.SetArcCostEvaluatorOfAllVehicles(distance_evaluator_index)\n", + "add_distance_dimension(routing, distance_evaluator_index)\n", + "\n", + "# Setting first solution heuristic (cheapest addition).\n", + "search_parameters = pywrapcp.DefaultRoutingSearchParameters()\n", + "search_parameters.first_solution_strategy = (\n", + " routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC) # pylint: disable=no-member\n", + "# Solve the problem.\n", + "assignment = routing.SolveWithParameters(search_parameters)\n", + "print_solution(data, routing, manager, assignment)\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/contrib/3_jugs_mip.ipynb b/examples/notebook/contrib/3_jugs_mip.ipynb new file mode 100644 index 0000000000..e201ade60b --- /dev/null +++ b/examples/notebook/contrib/3_jugs_mip.ipynb @@ -0,0 +1,176 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2011 Hakan Kjellerstrand hakank@gmail.com\n", + "#\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"\n", + "\n", + " 3 jugs problem using MIP in Google or-tools.\n", + "\n", + " A.k.a. water jugs problem.\n", + "\n", + " Problem from Taha 'Introduction to Operations Research',\n", + " page 245f .\n", + "\n", + " Compare with the CP model:\n", + " http://www.hakank.org/google_or_tools/3_jugs_regular\n", + "\n", + " This model was created by Hakan Kjellerstrand (hakank@gmail.com)\n", + " Also see my other Google CP Solver models:\n", + " http://www.hakank.org/google_or_tools/\n", + "\"\"\"\n", + "from __future__ import print_function\n", + "import sys\n", + "from ortools.linear_solver import pywraplp\n", + "\n", + "\n", + "\n", + "# Create the solver.\n", + "\n", + "print('Solver: ', sol)\n", + "\n", + "# using GLPK\n", + "if sol == 'GLPK':\n", + " solver = pywraplp.Solver('CoinsGridGLPK',\n", + " pywraplp.Solver.GLPK_MIXED_INTEGER_PROGRAMMING)\n", + "else:\n", + " # Using CBC\n", + " solver = pywraplp.Solver('CoinsGridCBC',\n", + " pywraplp.Solver.CBC_MIXED_INTEGER_PROGRAMMING)\n", + "\n", + "#\n", + "# data\n", + "#\n", + "n = 15\n", + "start = 0 # start node\n", + "end = 14 # end node\n", + "M = 999 # a large number\n", + "\n", + "nodes = [\n", + " '8,0,0', # start\n", + " '5,0,3',\n", + " '5,3,0',\n", + " '2,3,3',\n", + " '2,5,1',\n", + " '7,0,1',\n", + " '7,1,0',\n", + " '4,1,3',\n", + " '3,5,0',\n", + " '3,2,3',\n", + " '6,2,0',\n", + " '6,0,2',\n", + " '1,5,2',\n", + " '1,4,3',\n", + " '4,4,0' # goal!\n", + "]\n", + "\n", + "# distance\n", + "d = [[M, 1, M, M, M, M, M, M, 1, M, M, M, M, M, M],\n", + " [M, M, 1, M, M, M, M, M, M, M, M, M, M, M, M],\n", + " [M, M, M, 1, M, M, M, M, 1, M, M, M, M, M, M],\n", + " [M, M, M, M, 1, M, M, M, M, M, M, M, M, M, M],\n", + " [M, M, M, M, M, 1, M, M, 1, M, M, M, M, M, M],\n", + " [M, M, M, M, M, M, 1, M, M, M, M, M, M, M, M],\n", + " [M, M, M, M, M, M, M, 1, 1, M, M, M, M, M, M],\n", + " [M, M, M, M, M, M, M, M, M, M, M, M, M, M, 1],\n", + " [M, M, M, M, M, M, M, M, M, 1, M, M, M, M, M],\n", + " [M, 1, M, M, M, M, M, M, M, M, 1, M, M, M, M],\n", + " [M, M, M, M, M, M, M, M, M, M, M, 1, M, M, M],\n", + " [M, 1, M, M, M, M, M, M, M, M, M, M, 1, M, M],\n", + " [M, M, M, M, M, M, M, M, M, M, M, M, M, 1, M],\n", + " [M, 1, M, M, M, M, M, M, M, M, M, M, M, M, 1],\n", + " [M, M, M, M, M, M, M, M, M, M, M, M, M, M, M]]\n", + "\n", + "#\n", + "# variables\n", + "#\n", + "\n", + "# requirements (right hand statement)\n", + "rhs = [solver.IntVar(-1, 1, 'rhs[%i]' % i) for i in range(n)]\n", + "\n", + "x = {}\n", + "for i in range(n):\n", + " for j in range(n):\n", + " x[i, j] = solver.IntVar(0, 1, 'x[%i,%i]' % (i, j))\n", + "\n", + "out_flow = [solver.IntVar(0, 1, 'out_flow[%i]' % i) for i in range(n)]\n", + "in_flow = [solver.IntVar(0, 1, 'in_flow[%i]' % i) for i in range(n)]\n", + "\n", + "# length of path, to be minimized\n", + "z = solver.Sum(\n", + " [d[i][j] * x[i, j] for i in range(n) for j in range(n) if d[i][j] < M])\n", + "\n", + "#\n", + "# constraints\n", + "#\n", + "\n", + "for i in range(n):\n", + " if i == start:\n", + " solver.Add(rhs[i] == 1)\n", + " elif i == end:\n", + " solver.Add(rhs[i] == -1)\n", + " else:\n", + " solver.Add(rhs[i] == 0)\n", + "\n", + "# outflow constraint\n", + "for i in range(n):\n", + " solver.Add(\n", + " out_flow[i] == solver.Sum([x[i, j] for j in range(n) if d[i][j] < M]))\n", + "\n", + "# inflow constraint\n", + "for j in range(n):\n", + " solver.Add(\n", + " in_flow[j] == solver.Sum([x[i, j] for i in range(n) if d[i][j] < M]))\n", + "\n", + "# inflow = outflow\n", + "for i in range(n):\n", + " solver.Add(out_flow[i] - in_flow[i] == rhs[i])\n", + "\n", + "# objective\n", + "objective = solver.Minimize(z)\n", + "\n", + "#\n", + "# solution and search\n", + "#\n", + "solver.Solve()\n", + "\n", + "print()\n", + "print('z: ', int(solver.Objective().Value()))\n", + "\n", + "t = start\n", + "while t != end:\n", + " print(nodes[t], '->', end=' ')\n", + " for j in range(n):\n", + " if x[t, j].SolutionValue() == 1:\n", + " print(nodes[j])\n", + " t = j\n", + " break\n", + "\n", + "print()\n", + "print('walltime :', solver.WallTime(), 'ms')\n", + "if sol == 'CBC':\n", + " print('iterations:', solver.Iterations())\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/contrib/3_jugs_regular.ipynb b/examples/notebook/contrib/3_jugs_regular.ipynb new file mode 100644 index 0000000000..b1dddb3fe2 --- /dev/null +++ b/examples/notebook/contrib/3_jugs_regular.ipynb @@ -0,0 +1,269 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010 Hakan Kjellerstrand hakank@gmail.com\n", + "#\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"\n", + "\n", + " 3 jugs problem using regular constraint in Google CP Solver.\n", + "\n", + " A.k.a. water jugs problem.\n", + "\n", + " Problem from Taha 'Introduction to Operations Research',\n", + " page 245f .\n", + "\n", + " For more info about the problem, see:\n", + " http://mathworld.wolfram.com/ThreeJugProblem.html\n", + "\n", + " This model use a regular constraint for handling the\n", + " transitions between the states. Instead of minimizing\n", + " the cost in a cost matrix (as shortest path problem),\n", + " we here call the model with increasing length of the\n", + " sequence array (x).\n", + "\n", + " Compare with other models that use MIP/CP approach,\n", + " as a shortest path problem:\n", + " * Comet: http://www.hakank.org/comet/3_jugs.co\n", + " * Comet: http://www.hakank.org/comet/water_buckets1.co\n", + " * MiniZinc: http://www.hakank.org/minizinc/3_jugs.mzn\n", + " * MiniZinc: http://www.hakank.org/minizinc/3_jugs2.mzn\n", + " * SICStus: http://www.hakank.org/sicstus/3_jugs.pl\n", + " * ECLiPSe: http://www.hakank.org/eclipse/3_jugs.ecl\n", + " * ECLiPSe: http://www.hakank.org/eclipse/3_jugs2.ecl\n", + " * Gecode: http://www.hakank.org/gecode/3_jugs2.cpp\n", + "\n", + "\n", + " This model was created by Hakan Kjellerstrand (hakank@gmail.com)\n", + " Also see my other Google CP Solver models:\n", + " http://www.hakank.org/google_or_tools/\n", + "\n", + "\"\"\"\n", + "\n", + "from __future__ import print_function\n", + "from ortools.constraint_solver import pywrapcp\n", + "from collections import defaultdict\n", + "\n", + "#\n", + "# Global constraint regular\n", + "#\n", + "# This is a translation of MiniZinc's regular constraint (defined in\n", + "# lib/zinc/globals.mzn), via the Comet code refered above.\n", + "# All comments are from the MiniZinc code.\n", + "# '''\n", + "# The sequence of values in array 'x' (which must all be in the range 1..S)\n", + "# is accepted by the DFA of 'Q' states with input 1..S and transition\n", + "# function 'd' (which maps (1..Q, 1..S) -> 0..Q)) and initial state 'q0'\n", + "# (which must be in 1..Q) and accepting states 'F' (which all must be in\n", + "# 1..Q). We reserve state 0 to be an always failing state.\n", + "# '''\n", + "#\n", + "# x : IntVar array\n", + "# Q : number of states\n", + "# S : input_max\n", + "# d : transition matrix\n", + "# q0: initial state\n", + "# F : accepting states\n", + "\n", + "\n", + "def regular(x, Q, S, d, q0, F):\n", + "\n", + " solver = x[0].solver()\n", + "\n", + " assert Q > 0, 'regular: \"Q\" must be greater than zero'\n", + " assert S > 0, 'regular: \"S\" must be greater than zero'\n", + "\n", + " # d2 is the same as d, except we add one extra transition for\n", + " # each possible input; each extra transition is from state zero\n", + " # to state zero. This allows us to continue even if we hit a\n", + " # non-accepted input.\n", + "\n", + " # Comet: int d2[0..Q, 1..S]\n", + " d2 = []\n", + " for i in range(Q + 1):\n", + " row = []\n", + " for j in range(S):\n", + " if i == 0:\n", + " row.append(0)\n", + " else:\n", + " row.append(d[i - 1][j])\n", + " d2.append(row)\n", + "\n", + " d2_flatten = [d2[i][j] for i in range(Q + 1) for j in range(S)]\n", + "\n", + " # If x has index set m..n, then a[m-1] holds the initial state\n", + " # (q0), and a[i+1] holds the state we're in after processing\n", + " # x[i]. If a[n] is in F, then we succeed (ie. accept the\n", + " # string).\n", + " x_range = list(range(0, len(x)))\n", + " m = 0\n", + " n = len(x)\n", + "\n", + " a = [solver.IntVar(0, Q + 1, 'a[%i]' % i) for i in range(m, n + 1)]\n", + "\n", + " # Check that the final state is in F\n", + " solver.Add(solver.MemberCt(a[-1], F))\n", + " # First state is q0\n", + " solver.Add(a[m] == q0)\n", + " for i in x_range:\n", + " solver.Add(x[i] >= 1)\n", + " solver.Add(x[i] <= S)\n", + "\n", + " # Determine a[i+1]: a[i+1] == d2[a[i], x[i]]\n", + " solver.Add(\n", + " a[i + 1] == solver.Element(d2_flatten, ((a[i]) * S) + (x[i] - 1)))\n", + "\n", + "\n", + "\n", + "# Create the solver.\n", + "solver = pywrapcp.Solver('3 jugs problem using regular constraint')\n", + "\n", + "#\n", + "# data\n", + "#\n", + "\n", + "# the DFA (for regular)\n", + "n_states = 14\n", + "input_max = 15\n", + "initial_state = 1 # 0 is for the failing state\n", + "accepting_states = [15]\n", + "\n", + "##\n", + "# Manually crafted DFA\n", + "# (from the adjacency matrix used in the other models)\n", + "##\n", + "# transition_fn = [\n", + "# # 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5\n", + "# [0, 2, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 0, 0, 0], # 1\n", + "# [0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], # 2\n", + "# [0, 0, 0, 4, 0, 0, 0, 0, 9, 0, 0, 0, 0, 0, 0], # 3\n", + "# [0, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], # 4\n", + "# [0, 0, 0, 0, 0, 6, 0, 0, 9, 0, 0, 0, 0, 0, 0], # 5\n", + "# [0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0], # 6\n", + "# [0, 0, 0, 0, 0, 0, 0, 8, 9, 0, 0, 0, 0, 0, 0], # 7\n", + "# [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15], # 8\n", + "# [0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 0, 0, 0, 0, 0], # 9\n", + "# [0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 11, 0, 0, 0, 0], # 10\n", + "# [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 0, 0, 0], # 11\n", + "# [0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 13, 0, 0], # 12\n", + "# [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 14, 0], # 13\n", + "# [0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15], # 14\n", + "# # 15\n", + "# ]\n", + "\n", + "#\n", + "# However, the DFA is easy to create from adjacency lists.\n", + "#\n", + "states = [\n", + " [2, 9], # state 1\n", + " [3], # state 2\n", + " [4, 9], # state 3\n", + " [5], # state 4\n", + " [6, 9], # state 5\n", + " [7], # state 6\n", + " [8, 9], # state 7\n", + " [15], # state 8\n", + " [10], # state 9\n", + " [11], # state 10\n", + " [12], # state 11\n", + " [13], # state 12\n", + " [14], # state 13\n", + " [15] # state 14\n", + "]\n", + "\n", + "transition_fn = []\n", + "for i in range(n_states):\n", + " row = []\n", + " for j in range(1, input_max + 1):\n", + " if j in states[i]:\n", + " row.append(j)\n", + " else:\n", + " row.append(0)\n", + " transition_fn.append(row)\n", + "\n", + "#\n", + "# The name of the nodes, for printing\n", + "# the solution.\n", + "#\n", + "nodes = [\n", + " '8,0,0', # 1 start\n", + " '5,0,3', # 2\n", + " '5,3,0', # 3\n", + " '2,3,3', # 4\n", + " '2,5,1', # 5\n", + " '7,0,1', # 6\n", + " '7,1,0', # 7\n", + " '4,1,3', # 8\n", + " '3,5,0', # 9\n", + " '3,2,3', # 10\n", + " '6,2,0', # 11\n", + " '6,0,2', # 12\n", + " '1,5,2', # 13\n", + " '1,4,3', # 14\n", + " '4,4,0' # 15 goal\n", + "]\n", + "\n", + "#\n", + "# declare variables\n", + "#\n", + "x = [solver.IntVar(1, input_max, 'x[%i]' % i) for i in range(n)]\n", + "\n", + "#\n", + "# constraints\n", + "#\n", + "regular(x, n_states, input_max, transition_fn, initial_state,\n", + " accepting_states)\n", + "\n", + "#\n", + "# solution and search\n", + "#\n", + "db = solver.Phase(x, solver.INT_VAR_DEFAULT, solver.INT_VALUE_DEFAULT)\n", + "\n", + "solver.NewSearch(db)\n", + "\n", + "num_solutions = 0\n", + "x_val = []\n", + "while solver.NextSolution():\n", + " num_solutions += 1\n", + " x_val = [1] + [x[i].Value() for i in range(n)]\n", + " print('x:', x_val)\n", + " for i in range(1, n + 1):\n", + " print('%s -> %s' % (nodes[x_val[i - 1] - 1], nodes[x_val[i] - 1]))\n", + "\n", + "solver.EndSearch()\n", + "\n", + "if num_solutions > 0:\n", + " print()\n", + " print('num_solutions:', num_solutions)\n", + " print('failures:', solver.Failures())\n", + " print('branches:', solver.Branches())\n", + " print('WallTime:', solver.WallTime(), 'ms')\n", + "\n", + "# return the solution (or an empty array)\n", + "return x_val\n", + "\n", + "\n", + "# Search for a minimum solution by increasing\n", + "# the length of the state array." + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/contrib/a_round_of_golf.ipynb b/examples/notebook/contrib/a_round_of_golf.ipynb new file mode 100644 index 0000000000..a30fe4a0e7 --- /dev/null +++ b/examples/notebook/contrib/a_round_of_golf.ipynb @@ -0,0 +1,178 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010 Hakan Kjellerstrand hakank@gmail.com\n", + "#\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"\n", + "\n", + " A Round of Golf puzzle (Dell Logic Puzzles) in Google CP Solver.\n", + "\n", + " From http://brownbuffalo.sourceforge.net/RoundOfGolfClues.html\n", + " '''\n", + " Title: A Round of Golf\n", + " Author: Ellen K. Rodehorst\n", + " Publication: Dell Favorite Logic Problems\n", + " Issue: Summer, 2000\n", + " Puzzle #: 9\n", + " Stars: 1\n", + "\n", + " When the Sunny Hills Country Club golf course isn't in use by club members,\n", + " of course, it's open to the club's employees. Recently, Jack and three other\n", + " workers at the golf course got together on their day off to play a round of\n", + " eighteen holes of golf.\n", + " Afterward, all four, including Mr. Green, went to the clubhouse to total\n", + " their scorecards. Each man works at a different job (one is a short-order\n", + " cook), and each shot a different score in the game. No one scored below\n", + " 70 or above 85 strokes. From the clues below, can you discover each man's\n", + " full name, job and golf score?\n", + "\n", + " 1. Bill, who is not the maintenance man, plays golf often and had the lowest\n", + " score of the foursome.\n", + " 2. Mr. Clubb, who isn't Paul, hit several balls into the woods and scored ten\n", + " strokes more than the pro-shop clerk.\n", + " 3. In some order, Frank and the caddy scored four and seven more strokes than\n", + " Mr. Sands.\n", + " 4. Mr. Carter thought his score of 78 was one of his better games, even\n", + " though Frank's score was lower.\n", + " 5. None of the four scored exactly 81 strokes.\n", + "\n", + " Determine: First Name - Last Name - Job - Score\n", + " '''\n", + "\n", + " Compare with the F1 model:\n", + " http://www.f1compiler.com/samples/A 20Round 20of 20Golf.f1.html\n", + "\n", + "\n", + " Compare with the following models:\n", + " * MiniZinc: http://www.hakank.org/minizinc/a_round_of_golf.mzn\n", + " * Comet : http://www.hakank.org/comet/a_round_of_golf.co\n", + " * ECLiPSe : http://www.hakank.org/eclipse/a_round_of_golf.ecl\n", + " * Gecode : http://hakank.org/gecode/a_round_of_golf.cpp\n", + " * SICStus : http://hakank.org/sicstus/a_round_of_golf.pl\n", + "\n", + " This model was created by Hakan Kjellerstrand (hakank@gmail.com)\n", + " Also see my other Google CP Solver models:\n", + " http://www.hakank.org/google_or_tools/\n", + "\n", + "\"\"\"\n", + "\n", + "from __future__ import print_function\n", + "\n", + "from ortools.constraint_solver import pywrapcp\n", + "\n", + "\n", + "\n", + "# Create the solver.\n", + "solver = pywrapcp.Solver(\"All interval\")\n", + "\n", + "#\n", + "# data\n", + "#\n", + "n = 4\n", + "[Jack, Bill, Paul, Frank] = [i for i in range(n)]\n", + "\n", + "#\n", + "# declare variables\n", + "#\n", + "last_name = [solver.IntVar(0, n - 1, \"last_name[%i]\" % i) for i in range(n)]\n", + "[Green, Clubb, Sands, Carter] = last_name\n", + "\n", + "job = [solver.IntVar(0, n - 1, \"job[%i]\" % i) for i in range(n)]\n", + "[cook, maintenance_man, clerk, caddy] = job\n", + "\n", + "score = [solver.IntVar(70, 85, \"score[%i]\" % i) for i in range(n)]\n", + "\n", + "#\n", + "# constraints\n", + "#\n", + "solver.Add(solver.AllDifferent(last_name))\n", + "solver.Add(solver.AllDifferent(job))\n", + "solver.Add(solver.AllDifferent(score))\n", + "\n", + "# 1. Bill, who is not the maintenance man, plays golf often and had\n", + "# the lowest score of the foursome.\n", + "solver.Add(Bill != maintenance_man)\n", + "solver.Add(score[Bill] < score[Jack])\n", + "solver.Add(score[Bill] < score[Paul])\n", + "solver.Add(score[Bill] < score[Frank])\n", + "\n", + "# 2. Mr. Clubb, who isn't Paul, hit several balls into the woods and\n", + "# scored ten strokes more than the pro-shop clerk.\n", + "solver.Add(Clubb != Paul)\n", + "solver.Add(solver.Element(score, Clubb) == solver.Element(score, clerk) + 10)\n", + "\n", + "# 3. In some order, Frank and the caddy scored four and seven more\n", + "# strokes than Mr. Sands.\n", + "solver.Add(Frank != caddy)\n", + "solver.Add(Frank != Sands)\n", + "solver.Add(caddy != Sands)\n", + "\n", + "b3_a_1 = solver.IsEqualVar(solver.Element(score, Sands) + 4, score[Frank])\n", + "b3_a_2 = solver.IsEqualVar(\n", + " solver.Element(score, caddy),\n", + " solver.Element(score, Sands) + 7)\n", + "\n", + "b3_b_1 = solver.IsEqualVar(solver.Element(score, Sands) + 7, score[Frank])\n", + "b3_b_2 = solver.IsEqualVar(\n", + " solver.Element(score, caddy),\n", + " solver.Element(score, Sands) + 4)\n", + "\n", + "solver.Add((b3_a_1 * b3_a_2) + (b3_b_1 * b3_b_2) == 1)\n", + "\n", + "# 4. Mr. Carter thought his score of 78 was one of his better games,\n", + "# even though Frank's score was lower.\n", + "solver.Add(Frank != Carter)\n", + "solver.Add(solver.Element(score, Carter) == 78)\n", + "solver.Add(score[Frank] < solver.Element(score, Carter))\n", + "\n", + "# 5. None of the four scored exactly 81 strokes.\n", + "[solver.Add(score[i] != 81) for i in range(n)]\n", + "\n", + "#\n", + "# solution and search\n", + "#\n", + "solution = solver.Assignment()\n", + "solution.Add(last_name)\n", + "solution.Add(job)\n", + "solution.Add(score)\n", + "\n", + "db = solver.Phase(last_name + job + score, solver.CHOOSE_FIRST_UNBOUND,\n", + " solver.INT_VALUE_DEFAULT)\n", + "\n", + "solver.NewSearch(db)\n", + "num_solutions = 0\n", + "while solver.NextSolution():\n", + " print(\"last_name:\", [last_name[i].Value() for i in range(n)])\n", + " print(\"job :\", [job[i].Value() for i in range(n)])\n", + " print(\"score :\", [score[i].Value() for i in range(n)])\n", + " num_solutions += 1\n", + " print()\n", + "\n", + "print(\"num_solutions:\", num_solutions)\n", + "print(\"failures:\", solver.Failures())\n", + "print(\"branches:\", solver.Branches())\n", + "print(\"WallTime:\", solver.WallTime())\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/contrib/all_interval.ipynb b/examples/notebook/contrib/all_interval.ipynb new file mode 100644 index 0000000000..05eea7b49d --- /dev/null +++ b/examples/notebook/contrib/all_interval.ipynb @@ -0,0 +1,125 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010 Hakan Kjellerstrand hakank@gmail.com\n", + "#\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"\n", + "\n", + " All interval problem in Google CP Solver.\n", + "\n", + " CSPLib problem number 7\n", + " http://www.cs.st-andrews.ac.uk/~ianm/CSPLib/prob/prob007/index.html\n", + " '''\n", + " Given the twelve standard pitch-classes (c, c , d, ...), represented by\n", + " numbers 0,1,...,11, find a series in which each pitch-class occurs exactly\n", + " once and in which the musical intervals between neighbouring notes cover\n", + " the full set of intervals from the minor second (1 semitone) to the major\n", + " seventh (11 semitones). That is, for each of the intervals, there is a\n", + " pair of neigbhouring pitch-classes in the series, between which this\n", + " interval appears. The problem of finding such a series can be easily\n", + " formulated as an instance of a more general arithmetic problem on Z_n,\n", + " the set of integer residues modulo n. Given n in N, find a vector\n", + " s = (s_1, ..., s_n), such that (i) s is a permutation of\n", + " Z_n = {0,1,...,n-1}; and (ii) the interval vector\n", + " v = (|s_2-s_1|, |s_3-s_2|, ... |s_n-s_{n-1}|) is a permutation of\n", + " Z_n-{0} = {1,2,...,n-1}. A vector v satisfying these conditions is\n", + " called an all-interval series of size n; the problem of finding such\n", + " a series is the all-interval series problem of size n. We may also be\n", + " interested in finding all possible series of a given size.\n", + " '''\n", + "\n", + " Compare with the following models:\n", + " * MiniZinc: http://www.hakank.org/minizinc/all_interval.mzn\n", + " * Comet : http://www.hakank.org/comet/all_interval.co\n", + " * Gecode/R: http://www.hakank.org/gecode_r/all_interval.rb\n", + " * ECLiPSe : http://www.hakank.org/eclipse/all_interval.ecl\n", + " * SICStus : http://www.hakank.org/sicstus/all_interval.pl\n", + "\n", + "\n", + " This model was created by Hakan Kjellerstrand (hakank@gmail.com)\n", + " Also see my other Google CP Solver models:\n", + " http://www.hakank.org/google_or_tools/\n", + "\n", + "\"\"\"\n", + "\n", + "from __future__ import print_function\n", + "\n", + "import sys\n", + "\n", + "from ortools.constraint_solver import pywrapcp\n", + "\n", + "\n", + "\n", + "# Create the solver.\n", + "solver = pywrapcp.Solver(\"All interval\")\n", + "\n", + "#\n", + "# data\n", + "#\n", + "print(\"n:\", n)\n", + "\n", + "#\n", + "# declare variables\n", + "#\n", + "x = [solver.IntVar(1, n, \"x[%i]\" % i) for i in range(n)]\n", + "diffs = [solver.IntVar(1, n - 1, \"diffs[%i]\" % i) for i in range(n - 1)]\n", + "\n", + "#\n", + "# constraints\n", + "#\n", + "solver.Add(solver.AllDifferent(x))\n", + "solver.Add(solver.AllDifferent(diffs))\n", + "\n", + "for k in range(n - 1):\n", + " solver.Add(diffs[k] == abs(x[k + 1] - x[k]))\n", + "\n", + "# symmetry breaking\n", + "solver.Add(x[0] < x[n - 1])\n", + "solver.Add(diffs[0] < diffs[1])\n", + "\n", + "#\n", + "# solution and search\n", + "#\n", + "solution = solver.Assignment()\n", + "solution.Add(x)\n", + "solution.Add(diffs)\n", + "\n", + "db = solver.Phase(x, solver.CHOOSE_FIRST_UNBOUND, solver.ASSIGN_MIN_VALUE)\n", + "\n", + "solver.NewSearch(db)\n", + "num_solutions = 0\n", + "while solver.NextSolution():\n", + " print(\"x:\", [x[i].Value() for i in range(n)])\n", + " print(\"diffs:\", [diffs[i].Value() for i in range(n - 1)])\n", + " num_solutions += 1\n", + " print()\n", + "\n", + "print(\"num_solutions:\", num_solutions)\n", + "print(\"failures:\", solver.Failures())\n", + "print(\"branches:\", solver.Branches())\n", + "print(\"WallTime:\", solver.WallTime())\n", + "\n", + "n = 12\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/contrib/alldifferent_except_0.ipynb b/examples/notebook/contrib/alldifferent_except_0.ipynb new file mode 100644 index 0000000000..3ab62ac503 --- /dev/null +++ b/examples/notebook/contrib/alldifferent_except_0.ipynb @@ -0,0 +1,137 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010 Hakan Kjellerstrand hakank@gmail.com\n", + "#\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"\n", + "\n", + " All different except 0 Google CP Solver.\n", + "\n", + " Decomposition of global constraint alldifferent_except_0.\n", + "\n", + " From Global constraint catalogue:\n", + " http://www.emn.fr/x-info/sdemasse/gccat/Calldifferent_except_0.html\n", + " '''\n", + " Enforce all variables of the collection VARIABLES to take distinct\n", + " values, except those variables that are assigned to 0.\n", + "\n", + " Example\n", + " (<5, 0, 1, 9, 0, 3>)\n", + "\n", + " The alldifferent_except_0 constraint holds since all the values\n", + " (that are different from 0) 5, 1, 9 and 3 are distinct.\n", + " '''\n", + "\n", + " Compare with the following models:\n", + " * Comet: http://hakank.org/comet/alldifferent_except_0.co\n", + " * ECLiPSe: http://hakank.org/eclipse/alldifferent_except_0.ecl\n", + " * Tailor/Essence': http://hakank.org/tailor/alldifferent_except_0.eprime\n", + " * Gecode: http://hakank.org/gecode/alldifferent_except_0.cpp\n", + " * Gecode/R: http://hakank.org/gecode_r/all_different_except_0.rb\n", + " * MiniZinc: http://hakank.org/minizinc/alldifferent_except_0.mzn\n", + " * SICStus_ http://hakank.org/sicstus/alldifferent_except_0.pl\n", + " * Choco: http://hakank.org/choco/AllDifferentExcept0_test.java\n", + " * JaCoP: http://hakank.org/JaCoP/AllDifferentExcept0_test.java\n", + " * Zinc: http://hakank.org/minizinc/alldifferent_except_0.zinc\n", + "\n", + " This model was created by Hakan Kjellerstrand (hakank@gmail.com)\n", + " Also see my other Google CP Solver models:\n", + " http://www.hakank.org/google_or_tools/\n", + "\n", + "\"\"\"\n", + "\n", + "from __future__ import print_function\n", + "\n", + "from ortools.constraint_solver import pywrapcp\n", + "\n", + "#\n", + "# Decomposition of alldifferent_except_0\n", + "# Thanks to Laurent Perron (Google) for\n", + "# suggestions of improvements.\n", + "#\n", + "\n", + "\n", + "def alldifferent_except_0(solver, a):\n", + " n = len(a)\n", + " for i in range(n):\n", + " for j in range(i):\n", + " solver.Add((a[i] != 0) * (a[j] != 0) <= (a[i] != a[j]))\n", + "\n", + "\n", + "# more compact version:\n", + "\n", + "\n", + "def alldifferent_except_0_b(solver, a):\n", + " n = len(a)\n", + " [\n", + " solver.Add((a[i] != 0) * (a[j] != 0) <= (a[i] != a[j]))\n", + " for i in range(n)\n", + " for j in range(i)\n", + " ]\n", + "\n", + "\n", + "# Create the solver.\n", + "solver = pywrapcp.Solver(\"Alldifferent except 0\")\n", + "\n", + "# data\n", + "n = 7\n", + "\n", + "# declare variables\n", + "x = [solver.IntVar(0, n - 1, \"x%i\" % i) for i in range(n)]\n", + "# Number of zeros.\n", + "z = solver.Sum([x[i] == 0 for i in range(n)]).VarWithName(\"z\")\n", + "\n", + "#\n", + "# constraints\n", + "#\n", + "alldifferent_except_0(solver, x)\n", + "\n", + "# we require 2 0's\n", + "solver.Add(z == 2)\n", + "\n", + "#\n", + "# solution and search\n", + "#\n", + "solution = solver.Assignment()\n", + "solution.Add([x[i] for i in range(n)])\n", + "solution.Add(z)\n", + "\n", + "collector = solver.AllSolutionCollector(solution)\n", + "solver.Solve(\n", + " solver.Phase([x[i] for i in range(n)], solver.CHOOSE_FIRST_UNBOUND,\n", + " solver.ASSIGN_MIN_VALUE), [collector])\n", + "\n", + "num_solutions = collector.SolutionCount()\n", + "for s in range(num_solutions):\n", + " print(\"x:\", [collector.Value(s, x[i]) for i in range(n)])\n", + " print(\"z:\", collector.Value(s, z))\n", + " print()\n", + "\n", + "print(\"num_solutions:\", num_solutions)\n", + "print(\"failures:\", solver.Failures())\n", + "print(\"branches:\", solver.Branches())\n", + "print(\"WallTime:\", solver.WallTime())\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/contrib/alphametic.ipynb b/examples/notebook/contrib/alphametic.ipynb new file mode 100644 index 0000000000..e2be2ecc07 --- /dev/null +++ b/examples/notebook/contrib/alphametic.ipynb @@ -0,0 +1,166 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010 Hakan Kjellerstrand hakank@gmail.com\n", + "#\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"\n", + "\n", + " Generic alphametic solver in Google CP Solver.\n", + "\n", + " This is a generic alphametic solver.\n", + "\n", + " Usage:\n", + " python alphametic.py\n", + " -> solves SEND+MORE=MONEY in base 10\n", + "\n", + " python alphametic.py 'SEND+MOST=MONEY' 11\n", + " -> solver SEND+MOST=MONEY in base 11\n", + "\n", + " python alphametic.py TEST \n", + " -> solve some test problems in base \n", + " (defined in test_problems())\n", + "\n", + " Assumptions:\n", + " - we only solves problems of the form\n", + " NUMBER<1>+NUMBER<2>...+NUMBER = NUMBER\n", + " i.e. the last number is the sum\n", + " - the only nonletter characters are: +, =, \\d (which are splitted upon)\n", + "\n", + "\n", + " Compare with the following model:\n", + " * Zinc: http://www.hakank.org/minizinc/alphametic.zinc\n", + "\n", + " This model was created by Hakan Kjellerstrand (hakank@gmail.com)\n", + " Also see my other Google CP Solver models:\n", + " http://www.hakank.org/google_or_tools/\n", + "\n", + "\"\"\"\n", + "from __future__ import print_function\n", + "import sys\n", + "import re\n", + "\n", + "from ortools.constraint_solver import pywrapcp\n", + "\n", + "\n", + "\n", + "# Create the solver.\n", + "solver = pywrapcp.Solver(\"Send most money\")\n", + "\n", + "# data\n", + "print(\"\\nproblem:\", problem_str)\n", + "\n", + "# convert to array.\n", + "problem = re.split(\"[\\s+=]\", problem_str)\n", + "\n", + "p_len = len(problem)\n", + "print(\"base:\", base)\n", + "\n", + "# create the lookup table: list of (digit : ix)\n", + "a = sorted(set(\"\".join(problem)))\n", + "n = len(a)\n", + "lookup = dict(list(zip(a, list(range(n)))))\n", + "\n", + "# length of each number\n", + "lens = list(map(len, problem))\n", + "\n", + "#\n", + "# declare variables\n", + "#\n", + "\n", + "# the digits\n", + "x = [solver.IntVar(0, base - 1, \"x[%i]\" % i) for i in range(n)]\n", + "# the sums of each number (e.g. the three numbers SEND, MORE, MONEY)\n", + "sums = [solver.IntVar(1, 10**(lens[i]) - 1) for i in range(p_len)]\n", + "\n", + "#\n", + "# constraints\n", + "#\n", + "solver.Add(solver.AllDifferent(x))\n", + "\n", + "ix = 0\n", + "for prob in problem:\n", + " this_len = len(prob)\n", + "\n", + " # sum all the digits with proper exponents to a number\n", + " solver.Add(\n", + " sums[ix] == solver.Sum([(base**i) * x[lookup[prob[this_len - i - 1]]]\n", + " for i in range(this_len)[::-1]]))\n", + " # leading digits must be > 0\n", + " solver.Add(x[lookup[prob[0]]] > 0)\n", + " ix += 1\n", + "\n", + "# the last number is the sum of the previous numbers\n", + "solver.Add(solver.Sum([sums[i] for i in range(p_len - 1)]) == sums[-1])\n", + "\n", + "#\n", + "# solution and search\n", + "#\n", + "solution = solver.Assignment()\n", + "solution.Add(x)\n", + "solution.Add(sums)\n", + "\n", + "db = solver.Phase(x, solver.CHOOSE_FIRST_UNBOUND, solver.ASSIGN_MIN_VALUE)\n", + "\n", + "solver.NewSearch(db)\n", + "\n", + "num_solutions = 0\n", + "while solver.NextSolution():\n", + " num_solutions += 1\n", + " print(\"\\nsolution #%i\" % num_solutions)\n", + " for i in range(n):\n", + " print(a[i], \"=\", x[i].Value())\n", + " print()\n", + " for prob in problem:\n", + " for p in prob:\n", + " print(p, end=\" \")\n", + " print()\n", + " print()\n", + " for prob in problem:\n", + " for p in prob:\n", + " print(x[lookup[p]].Value(), end=\" \")\n", + " print()\n", + "\n", + " print(\"sums:\", [sums[i].Value() for i in range(p_len)])\n", + " print()\n", + "\n", + "print(\"\\nnum_solutions:\", num_solutions)\n", + "print(\"failures:\", solver.Failures())\n", + "print(\"branches:\", solver.Branches())\n", + "print(\"WallTime:\", solver.WallTime())\n", + "\n", + "def test_problems(base=10):\n", + " problems = [\n", + " \"SEND+MORE=MONEY\", \"SEND+MOST=MONEY\", \"VINGT+CINQ+CINQ=TRENTE\",\n", + " \"EIN+EIN+EIN+EIN=VIER\", \"DONALD+GERALD=ROBERT\",\n", + " \"SATURN+URANUS+NEPTUNE+PLUTO+PLANETS\", \"WRONG+WRONG=RIGHT\"\n", + " ]\n", + "\n", + " for p in problems:\n", + " main(p, base)\n", + "\n", + "\n", + "problem = \"SEND+MORE=MONEY\"\n", + "base = 10\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/contrib/assignment.ipynb b/examples/notebook/contrib/assignment.ipynb new file mode 100644 index 0000000000..ccb6778674 --- /dev/null +++ b/examples/notebook/contrib/assignment.ipynb @@ -0,0 +1,135 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010 Hakan Kjellerstrand hakank@gmail.com\n", + "#\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"\n", + "\n", + " Assignment problem in Google CP Solver.\n", + "\n", + " Winston 'Operations Research', Assignment Problems, page 393f\n", + " (generalized version with added test column)\n", + "\n", + " Compare with the following models:\n", + " * Comet : http://www.hakank.org/comet/assignment.co\n", + " * ECLiPSE : http://www.hakank.org/eclipse/assignment.ecl\n", + " * Gecode : http://www.hakank.org/gecode/assignment.cpp\n", + " * MiniZinc: http://www.hakank.org/minizinc/assignment.mzn\n", + " * Tailor/Essence': http://www.hakank.org/tailor/assignment.eprime\n", + " * SICStus: http://hakank.org/sicstus/assignment.pl\n", + "\n", + " This model was created by Hakan Kjellerstrand (hakank@gmail.com)\n", + " Also see my other Google CP Solver models:\n", + " http://www.hakank.org/google_or_tools/\n", + "\"\"\"\n", + "from __future__ import print_function\n", + "from ortools.constraint_solver import pywrapcp\n", + "\n", + "\n", + "# Create the solver.\n", + "solver = pywrapcp.Solver(\"n-queens\")\n", + "\n", + "#\n", + "# data\n", + "#\n", + "\n", + "# declare variables\n", + "total_cost = solver.IntVar(0, 100, \"total_cost\")\n", + "x = []\n", + "for i in range(rows):\n", + " t = []\n", + " for j in range(cols):\n", + " t.append(solver.IntVar(0, 1, \"x[%i,%i]\" % (i, j)))\n", + " x.append(t)\n", + "x_flat = [x[i][j] for i in range(rows) for j in range(cols)]\n", + "\n", + "#\n", + "# constraints\n", + "#\n", + "\n", + "# total_cost\n", + "solver.Add(total_cost == solver.Sum(\n", + " [solver.ScalProd(x_row, cost_row) for (x_row, cost_row) in zip(x, cost)]))\n", + "\n", + "# exacly one assignment per row, all rows must be assigned\n", + "[\n", + " solver.Add(solver.Sum([x[row][j]\n", + " for j in range(cols)]) == 1)\n", + " for row in range(rows)\n", + "]\n", + "\n", + "# zero or one assignments per column\n", + "[\n", + " solver.Add(solver.Sum([x[i][col]\n", + " for i in range(rows)]) <= 1)\n", + " for col in range(cols)\n", + "]\n", + "\n", + "objective = solver.Minimize(total_cost, 1)\n", + "\n", + "#\n", + "# solution and search\n", + "#\n", + "solution = solver.Assignment()\n", + "solution.Add(x_flat)\n", + "solution.Add(total_cost)\n", + "\n", + "# db: DecisionBuilder\n", + "db = solver.Phase(x_flat, solver.INT_VAR_SIMPLE, solver.ASSIGN_MIN_VALUE)\n", + "\n", + "solver.NewSearch(db, [objective])\n", + "num_solutions = 0\n", + "while solver.NextSolution():\n", + " print(\"total_cost:\", total_cost.Value())\n", + " for i in range(rows):\n", + " for j in range(cols):\n", + " print(x[i][j].Value(), end=\" \")\n", + " print()\n", + " print()\n", + "\n", + " for i in range(rows):\n", + " print(\"Task:\", i, end=\" \")\n", + " for j in range(cols):\n", + " if x[i][j].Value() == 1:\n", + " print(\" is done by \", j)\n", + " print()\n", + "\n", + " num_solutions += 1\n", + "solver.EndSearch()\n", + "\n", + "print()\n", + "print(\"num_solutions:\", num_solutions)\n", + "print(\"failures:\", solver.Failures())\n", + "print(\"branches:\", solver.Branches())\n", + "print(\"WallTime:\", solver.WallTime())\n", + "\n", + "\n", + "# Problem instance\n", + "# hakank: I added the fifth column to make it more\n", + "# interestingrows = 4\n", + "cols = 5\n", + "cost = [[14, 5, 8, 7, 15], [2, 12, 6, 5, 3], [7, 8, 3, 9, 7], [2, 4, 6, 10, 1]]\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/contrib/assignment6_mip.ipynb b/examples/notebook/contrib/assignment6_mip.ipynb new file mode 100644 index 0000000000..ae7e36aa63 --- /dev/null +++ b/examples/notebook/contrib/assignment6_mip.ipynb @@ -0,0 +1,162 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2011 Hakan Kjellerstrand hakank@gmail.com\n", + "#\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"\n", + "\n", + " Assignment problem using MIP in Google or-tools.\n", + "\n", + " From GLPK:s example assign.mod:\n", + " '''\n", + " The assignment problem is one of the fundamental combinatorial\n", + " optimization problems.\n", + "\n", + " In its most general form, the problem is as follows:\n", + "\n", + " There are a number of agents and a number of tasks. Any agent can be\n", + " assigned to perform any task, incurring some cost that may vary\n", + " depending on the agent-task assignment. It is required to perform all\n", + " tasks by assigning exactly one agent to each task in such a way that\n", + " the total cost of the assignment is minimized.\n", + "\n", + " (From Wikipedia, the free encyclopedia.)\n", + " '''\n", + "\n", + " Compare with the Comet model:\n", + " http://www.hakank.org/comet/assignment6.co\n", + "\n", + "\n", + " This model was created by Hakan Kjellerstrand (hakank@gmail.com)\n", + " Also see my other Google CP Solver models:\n", + " http://www.hakank.org/google_or_tools/\n", + "\"\"\"\n", + "from __future__ import print_function\n", + "import sys\n", + "from ortools.linear_solver import pywraplp\n", + "\n", + "\n", + "\n", + "# Create the solver.\n", + "print('Solver: ', sol)\n", + "\n", + "# using GLPK\n", + "if sol == 'GLPK':\n", + " solver = pywraplp.Solver('CoinsGridGLPK',\n", + " pywraplp.Solver.GLPK_MIXED_INTEGER_PROGRAMMING)\n", + "else:\n", + " # Using CBC\n", + " solver = pywraplp.Solver('CoinsGridCBC',\n", + " pywraplp.Solver.CBC_MIXED_INTEGER_PROGRAMMING)\n", + "\n", + "#\n", + "# data\n", + "#\n", + "\n", + "# number of agents\n", + "m = 8\n", + "\n", + "# number of tasks\n", + "n = 8\n", + "\n", + "# set of agents\n", + "I = list(range(m))\n", + "\n", + "# set of tasks\n", + "J = list(range(n))\n", + "\n", + "# cost of allocating task j to agent i\n", + "# \"\"\"\n", + "# These data correspond to an example from [Christofides].\n", + "#\n", + "# Optimal solution is 76\n", + "# \"\"\"\n", + "c = [[13, 21, 20, 12, 8, 26, 22, 11], [12, 36, 25, 41, 40, 11, 4, 8],\n", + " [35, 32, 13, 36, 26, 21, 13, 37], [34, 54, 7, 8, 12, 22, 11, 40],\n", + " [21, 6, 45, 18, 24, 34, 12, 48], [42, 19, 39, 15, 14, 16, 28, 46],\n", + " [16, 34, 38, 3, 34, 40, 22, 24], [26, 20, 5, 17, 45, 31, 37, 43]]\n", + "\n", + "#\n", + "# variables\n", + "#\n", + "\n", + "# For the output: the assignment as task number.\n", + "assigned = [solver.IntVar(0, 10000, 'assigned[%i]' % j) for j in J]\n", + "\n", + "costs = [solver.IntVar(0, 10000, 'costs[%i]' % i) for i in I]\n", + "\n", + "x = {}\n", + "for i in range(n):\n", + " for j in range(n):\n", + " x[i, j] = solver.IntVar(0, 1, 'x[%i,%i]' % (i, j))\n", + "\n", + "# total cost, to be minimized\n", + "z = solver.Sum([c[i][j] * x[i, j] for i in I for j in J])\n", + "\n", + "#\n", + "# constraints\n", + "#\n", + "# each agent can perform at most one task\n", + "for i in I:\n", + " solver.Add(solver.Sum([x[i, j] for j in J]) <= 1)\n", + "\n", + "# each task must be assigned exactly to one agent\n", + "for j in J:\n", + " solver.Add(solver.Sum([x[i, j] for i in I]) == 1)\n", + "\n", + "# to which task and what cost is person i assigned (for output in MiniZinc)\n", + "for i in I:\n", + " solver.Add(assigned[i] == solver.Sum([j * x[i, j] for j in J]))\n", + " solver.Add(costs[i] == solver.Sum([c[i][j] * x[i, j] for j in J]))\n", + "\n", + "# objective\n", + "objective = solver.Minimize(z)\n", + "\n", + "#\n", + "# solution and search\n", + "#\n", + "solver.Solve()\n", + "\n", + "print()\n", + "print('z: ', int(solver.Objective().Value()))\n", + "\n", + "print('Assigned')\n", + "for j in J:\n", + " print(int(assigned[j].SolutionValue()), end=' ')\n", + "print()\n", + "\n", + "print('Matrix:')\n", + "for i in I:\n", + " for j in J:\n", + " print(int(x[i, j].SolutionValue()), end=' ')\n", + " print()\n", + "print()\n", + "\n", + "print()\n", + "print('walltime :', solver.WallTime(), 'ms')\n", + "if sol == 'CBC':\n", + " print('iterations:', solver.Iterations())\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/contrib/bacp.ipynb b/examples/notebook/contrib/bacp.ipynb new file mode 100644 index 0000000000..0e35db5c6a --- /dev/null +++ b/examples/notebook/contrib/bacp.ipynb @@ -0,0 +1,101 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010 Pierre Schaus pschaus@gmail.com\n", + "#\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\n", + "import argparse\n", + "from ortools.constraint_solver import pywrapcp\n", + "\n", + "parser = argparse.ArgumentParser()\n", + "\n", + "parser.add_argument(\n", + " '--data', default='examples/data/bacp/bacp12.txt', help='path to data file')\n", + "\n", + "#----------------helper for binpacking posting----------------\n", + "\n", + "\n", + "def BinPacking(solver, binvars, weights, loadvars):\n", + " \"\"\"post the load constraint on bins.\n", + "\n", + " constraints forall j: loadvars[j] == sum_i (binvars[i] == j) * weights[i])\n", + " \"\"\"\n", + " pack = solver.Pack(binvars, len(loadvars))\n", + " pack.AddWeightedSumEqualVarDimension(weights, loadvars)\n", + " solver.Add(pack)\n", + " solver.Add(solver.SumEquality(loadvars, sum(weights)))\n", + "\n", + "\n", + "#------------------------------data reading-------------------\n", + "\n", + "\n", + "def ReadData(filename):\n", + " \"\"\"Read data from .\"\"\"\n", + " f = open(filename)\n", + " nb_courses, nb_periods, min_credit, max_credit, nb_prereqs =\\\n", + " [int(nb) for nb in f.readline().split()]\n", + " credits = [int(nb) for nb in f.readline().split()]\n", + " prereq = [int(nb) for nb in f.readline().split()]\n", + " prereq = [(prereq[i * 2], prereq[i * 2 + 1]) for i in range(nb_prereqs)]\n", + " return (credits, nb_periods, prereq)\n", + "\n", + "\n", + "#------------------solver and variable declaration-------------\n", + "\n", + "credits, nb_periods, prereq = ReadData(args.data)\n", + "nb_courses = len(credits)\n", + "\n", + "solver = pywrapcp.Solver('Balanced Academic Curriculum Problem')\n", + "\n", + "x = [\n", + " solver.IntVar(0, nb_periods - 1, 'x' + str(i)) for i in range(nb_courses)\n", + "]\n", + "load_vars = [\n", + " solver.IntVar(0, sum(credits), 'load_vars' + str(i))\n", + " for i in range(nb_periods)\n", + "]\n", + "\n", + "#-------------------post of the constraints--------------\n", + "\n", + "# Bin Packing.\n", + "BinPacking(solver, x, credits, load_vars)\n", + "# Add dependencies.\n", + "for i, j in prereq:\n", + " solver.Add(x[i] < x[j])\n", + "\n", + "#----------------Objective-------------------------------\n", + "\n", + "objective_var = solver.Max(load_vars)\n", + "objective = solver.Minimize(objective_var, 1)\n", + "\n", + "#------------start the search and optimization-----------\n", + "\n", + "db = solver.Phase(x, solver.CHOOSE_MIN_SIZE_LOWEST_MIN,\n", + " solver.INT_VALUE_DEFAULT)\n", + "\n", + "search_log = solver.SearchLog(100000, objective_var)\n", + "solver.Solve(db, [objective, search_log])\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/contrib/blending.ipynb b/examples/notebook/contrib/blending.ipynb new file mode 100644 index 0000000000..4d6549786a --- /dev/null +++ b/examples/notebook/contrib/blending.ipynb @@ -0,0 +1,152 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2011 Hakan Kjellerstrand hakank@gmail.com\n", + "#\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"\n", + "\n", + " Blending problem in Google or-tools.\n", + "\n", + " From the OPL model blending.mod.\n", + "\n", + " This model was created by Hakan Kjellerstrand (hakank@gmail.com)\n", + " Also see my other Google CP Solver models:\n", + " http://www.hakank.org/google_or_tools/\n", + "\"\"\"\n", + "from __future__ import print_function\n", + "import sys\n", + "from ortools.linear_solver import pywraplp\n", + "\n", + "\n", + "\n", + "# Create the solver.\n", + "\n", + "print('Solver: ', sol)\n", + "\n", + "# using GLPK\n", + "if sol == 'GLPK':\n", + " solver = pywraplp.Solver('CoinsGridGLPK',\n", + " pywraplp.Solver.GLPK_MIXED_INTEGER_PROGRAMMING)\n", + "else:\n", + " # Using CBC\n", + " solver = pywraplp.Solver('CoinsGridCBC',\n", + " pywraplp.Solver.CBC_MIXED_INTEGER_PROGRAMMING)\n", + "\n", + "#\n", + "# data\n", + "#\n", + "NbMetals = 3\n", + "NbRaw = 2\n", + "NbScrap = 2\n", + "NbIngo = 1\n", + "Metals = list(range(NbMetals))\n", + "Raws = list(range(NbRaw))\n", + "Scraps = list(range(NbScrap))\n", + "Ingos = list(range(NbIngo))\n", + "\n", + "CostMetal = [22, 10, 13]\n", + "CostRaw = [6, 5]\n", + "CostScrap = [7, 8]\n", + "CostIngo = [9]\n", + "Low = [0.05, 0.30, 0.60]\n", + "Up = [0.10, 0.40, 0.80]\n", + "PercRaw = [[0.20, 0.01], [0.05, 0], [0.05, 0.30]]\n", + "PercScrap = [[0, 0.01], [0.60, 0], [0.40, 0.70]]\n", + "PercIngo = [[0.10], [0.45], [0.45]]\n", + "Alloy = 71\n", + "\n", + "#\n", + "# variables\n", + "#\n", + "p = [solver.NumVar(0, solver.Infinity(), 'p[%i]' % i) for i in Metals]\n", + "r = [solver.NumVar(0, solver.Infinity(), 'r[%i]' % i) for i in Raws]\n", + "s = [solver.NumVar(0, solver.Infinity(), 's[%i]' % i) for i in Scraps]\n", + "ii = [solver.IntVar(0, solver.Infinity(), 'ii[%i]' % i) for i in Ingos]\n", + "metal = [\n", + " solver.NumVar(Low[j] * Alloy, Up[j] * Alloy, 'metal[%i]' % j)\n", + " for j in Metals\n", + "]\n", + "\n", + "z = solver.NumVar(0, solver.Infinity(), 'z')\n", + "\n", + "#\n", + "# constraints\n", + "#\n", + "\n", + "solver.Add(z == solver.Sum([CostMetal[i] * p[i] for i in Metals]) +\n", + " solver.Sum([CostRaw[i] * r[i] for i in Raws]) +\n", + " solver.Sum([CostScrap[i] * s[i] for i in Scraps]) +\n", + " solver.Sum([CostIngo[i] * ii[i] for i in Ingos]))\n", + "\n", + "for j in Metals:\n", + " solver.Add(\n", + " metal[j] == p[j] + solver.Sum([PercRaw[j][k] * r[k] for k in Raws]) +\n", + " solver.Sum([PercScrap[j][k] * s[k] for k in Scraps]) +\n", + " solver.Sum([PercIngo[j][k] * ii[k] for k in Ingos]))\n", + "\n", + "solver.Add(solver.Sum(metal) == Alloy)\n", + "\n", + "objective = solver.Minimize(z)\n", + "\n", + "#\n", + "# solution and search\n", + "#\n", + "solver.Solve()\n", + "\n", + "print()\n", + "\n", + "print('z = ', solver.Objective().Value())\n", + "print('Metals')\n", + "for i in Metals:\n", + " print(p[i].SolutionValue(), end=' ')\n", + "print()\n", + "\n", + "print('Raws')\n", + "for i in Raws:\n", + " print(r[i].SolutionValue(), end=' ')\n", + "print()\n", + "\n", + "print('Scraps')\n", + "for i in Scraps:\n", + " print(s[i].SolutionValue(), end=' ')\n", + "print()\n", + "\n", + "print('Ingos')\n", + "for i in Ingos:\n", + " print(ii[i].SolutionValue(), end=' ')\n", + "print()\n", + "\n", + "print('Metals')\n", + "for i in Metals:\n", + " print(metal[i].SolutionValue(), end=' ')\n", + "print()\n", + "\n", + "print()\n", + "\n", + "print('walltime :', solver.WallTime(), 'ms')\n", + "if sol == 'CBC':\n", + " print('iterations:', solver.Iterations())\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/contrib/broken_weights.ipynb b/examples/notebook/contrib/broken_weights.ipynb new file mode 100644 index 0000000000..e7220b1e25 --- /dev/null +++ b/examples/notebook/contrib/broken_weights.ipynb @@ -0,0 +1,147 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010 Hakan Kjellerstrand hakank@gmail.com\n", + "#\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"\n", + "\n", + " Broken weights problem in Google CP Solver.\n", + "\n", + " From http://www.mathlesstraveled.com/?p=701\n", + " '''\n", + " Here's a fantastic problem I recently heard. Apparently it was first\n", + " posed by Claude Gaspard Bachet de Meziriac in a book of arithmetic problems\n", + " published in 1612, and can also be found in Heinrich Dorrie's 100\n", + " Great Problems of Elementary Mathematics.\n", + "\n", + " A merchant had a forty pound measuring weight that broke\n", + " into four pieces as the result of a fall. When the pieces were\n", + " subsequently weighed, it was found that the weight of each piece\n", + " was a whole number of pounds and that the four pieces could be\n", + " used to weigh every integral weight between 1 and 40 pounds. What\n", + " were the weights of the pieces?\n", + "\n", + " Note that since this was a 17th-century merchant, he of course used a\n", + " balance scale to weigh things. So, for example, he could use a 1-pound\n", + " weight and a 4-pound weight to weigh a 3-pound object, by placing the\n", + " 3-pound object and 1-pound weight on one side of the scale, and\n", + " the 4-pound weight on the other side.\n", + " '''\n", + "\n", + " Compare with the following problems:\n", + " * MiniZinc: http://www.hakank.org/minizinc/broken_weights.mzn\n", + " * ECLiPSE: http://www.hakank.org/eclipse/broken_weights.ecl\n", + " * Gecode: http://www.hakank.org/gecode/broken_weights.cpp\n", + " * Comet: http://hakank.org/comet/broken_weights.co\n", + "\n", + " This model was created by Hakan Kjellerstrand (hakank@gmail.com)\n", + " Also see my other Google CP Solver models:\n", + " http://www.hakank.org/google_or_tools/\n", + "\"\"\"\n", + "from __future__ import print_function\n", + "import sys\n", + "\n", + "from ortools.constraint_solver import pywrapcp\n", + "\n", + "\n", + "\n", + "# Create the solver.\n", + "solver = pywrapcp.Solver('Broken weights')\n", + "\n", + "#\n", + "# data\n", + "#\n", + "print('total weight (m):', m)\n", + "print('number of pieces (n):', n)\n", + "print()\n", + "\n", + "#\n", + "# variables\n", + "#\n", + "weights = [solver.IntVar(1, m, 'weights[%i]' % j) for j in range(n)]\n", + "x = {}\n", + "for i in range(m):\n", + " for j in range(n):\n", + " x[i, j] = solver.IntVar(-1, 1, 'x[%i,%i]' % (i, j))\n", + "x_flat = [x[i, j] for i in range(m) for j in range(n)]\n", + "\n", + "#\n", + "# constraints\n", + "#\n", + "\n", + "# symmetry breaking\n", + "for j in range(1, n):\n", + " solver.Add(weights[j - 1] < weights[j])\n", + "\n", + "solver.Add(solver.SumEquality(weights, m))\n", + "\n", + "# Check that all weights from 1 to 40 can be made.\n", + "#\n", + "# Since all weights can be on either side\n", + "# of the side of the scale we allow either\n", + "# -1, 0, or 1 or the weights, assuming that\n", + "# -1 is the weights on the left and 1 is on the right.\n", + "#\n", + "for i in range(m):\n", + " solver.Add(i + 1 == solver.Sum([weights[j] * x[i, j] for j in range(n)]))\n", + "\n", + "# objective\n", + "objective = solver.Minimize(weights[n - 1], 1)\n", + "\n", + "#\n", + "# search and result\n", + "#\n", + "db = solver.Phase(weights + x_flat, solver.CHOOSE_FIRST_UNBOUND,\n", + " solver.ASSIGN_MIN_VALUE)\n", + "\n", + "search_log = solver.SearchLog(1)\n", + "\n", + "solver.NewSearch(db, [objective])\n", + "\n", + "num_solutions = 0\n", + "while solver.NextSolution():\n", + " num_solutions += 1\n", + " print('weights: ', end=' ')\n", + " for w in [weights[j].Value() for j in range(n)]:\n", + " print('%3i ' % w, end=' ')\n", + " print()\n", + " print('-' * 30)\n", + " for i in range(m):\n", + " print('weight %2i:' % (i + 1), end=' ')\n", + " for j in range(n):\n", + " print('%3i ' % x[i, j].Value(), end=' ')\n", + " print()\n", + " print()\n", + "print()\n", + "solver.EndSearch()\n", + "\n", + "print('num_solutions:', num_solutions)\n", + "print('failures :', solver.Failures())\n", + "print('branches :', solver.Branches())\n", + "print('WallTime:', solver.WallTime(), 'ms')\n", + "\n", + "m = 40\n", + "n = 4\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/contrib/bus_schedule.ipynb b/examples/notebook/contrib/bus_schedule.ipynb new file mode 100644 index 0000000000..f27e99fc39 --- /dev/null +++ b/examples/notebook/contrib/bus_schedule.ipynb @@ -0,0 +1,118 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010 Hakan Kjellerstrand hakank@gmail.com\n", + "#\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"\n", + "\n", + " Bus scheduling in Google CP Solver.\n", + "\n", + "\n", + " Problem from Taha \"Introduction to Operations Research\", page 58.\n", + "\n", + " This is a slightly more general model than Taha's.\n", + "\n", + " Compare with the following models:\n", + " * MiniZinc: http://www.hakank.org/minizinc/bus_scheduling.mzn\n", + " * Comet : http://www.hakank.org/comet/bus_schedule.co\n", + " * ECLiPSe : http://www.hakank.org/eclipse/bus_schedule.ecl\n", + " * Gecode : http://www.hakank.org/gecode/bus_schedule.cpp\n", + " * Tailor/Essence' : http://www.hakank.org/tailor/bus_schedule.eprime\n", + " * SICStus: http://hakank.org/sicstus/bus_schedule.pl\n", + "\n", + " This model was created by Hakan Kjellerstrand (hakank@gmail.com)\n", + " Also see my other Google CP Solver models:\n", + " http://www.hakank.org/google_or_tools/\n", + "\n", + "\"\"\"\n", + "from __future__ import print_function\n", + "import sys\n", + "from ortools.constraint_solver import pywrapcp\n", + "\n", + "\n", + "\n", + "# Create the solver.\n", + "solver = pywrapcp.Solver(\"Bus scheduling\")\n", + "\n", + "# data\n", + "time_slots = 6\n", + "demands = [8, 10, 7, 12, 4, 4]\n", + "max_num = sum(demands)\n", + "\n", + "# declare variables\n", + "x = [solver.IntVar(0, max_num, \"x%i\" % i) for i in range(time_slots)]\n", + "num_buses = solver.IntVar(0, max_num, \"num_buses\")\n", + "\n", + "#\n", + "# constraints\n", + "#\n", + "solver.Add(num_buses == solver.Sum(x))\n", + "\n", + "# Meet the demands for this and the next time slot\n", + "for i in range(time_slots - 1):\n", + " solver.Add(x[i] + x[i + 1] >= demands[i])\n", + "\n", + "# The demand \"around the clock\"\n", + "solver.Add(x[time_slots - 1] + x[0] == demands[time_slots - 1])\n", + "\n", + "if num_buses_check > 0:\n", + " solver.Add(num_buses == num_buses_check)\n", + "\n", + "#\n", + "# solution and search\n", + "#\n", + "solution = solver.Assignment()\n", + "solution.Add(x)\n", + "solution.Add(num_buses)\n", + "\n", + "collector = solver.AllSolutionCollector(solution)\n", + "cargs = [collector]\n", + "\n", + "# objective\n", + "if num_buses_check == 0:\n", + " objective = solver.Minimize(num_buses, 1)\n", + " cargs.extend([objective])\n", + "\n", + "solver.Solve(\n", + " solver.Phase(x, solver.CHOOSE_FIRST_UNBOUND, solver.ASSIGN_MIN_VALUE),\n", + " cargs)\n", + "\n", + "num_solutions = collector.SolutionCount()\n", + "num_buses_check_value = 0\n", + "for s in range(num_solutions):\n", + " print(\"x:\", [collector.Value(s, x[i]) for i in range(len(x))], end=\" \")\n", + " num_buses_check_value = collector.Value(s, num_buses)\n", + " print(\" num_buses:\", num_buses_check_value)\n", + "\n", + "print()\n", + "print(\"num_solutions:\", num_solutions)\n", + "print(\"failures:\", solver.Failures())\n", + "print(\"branches:\", solver.Branches())\n", + "print(\"WallTime:\", solver.WallTime())\n", + "print()\n", + "if num_buses_check == 0:\n", + " return num_buses_check_value\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/contrib/car.ipynb b/examples/notebook/contrib/car.ipynb new file mode 100644 index 0000000000..950b258a8c --- /dev/null +++ b/examples/notebook/contrib/car.ipynb @@ -0,0 +1,149 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010 Hakan Kjellerstrand hakank@gmail.com\n", + "#\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"\n", + "\n", + " Car sequencing in Google CP Solver.\n", + "\n", + " This model is based on the car sequencing model in\n", + " Pascal Van Hentenryck\n", + " 'The OPL Optimization Programming Language', page 184ff.\n", + "\n", + "\n", + " Compare with the following models:\n", + " * MiniZinc: http://hakank.org/minizinc/car.mzn\n", + " * Comet: http://hakank.org/comet/car.co\n", + "\n", + " This model was created by Hakan Kjellerstrand (hakank@gmail.com)\n", + " Also see my other Google CP Solver models:\n", + " http://www.hakank.org/google_or_tools/\n", + "\"\"\"\n", + "from __future__ import print_function\n", + "import sys\n", + "\n", + "from ortools.constraint_solver import pywrapcp\n", + "\n", + "\n", + "\n", + "# Create the solver.\n", + "solver = pywrapcp.Solver(\"Car sequence\")\n", + "\n", + "#\n", + "# data\n", + "#\n", + "nbCars = 6\n", + "nbOptions = 5\n", + "nbSlots = 10\n", + "\n", + "Cars = list(range(nbCars))\n", + "Options = list(range(nbOptions))\n", + "Slots = list(range(nbSlots))\n", + "\n", + "# car 0 1 2 3 4 5\n", + "demand = [1, 1, 2, 2, 2, 2]\n", + "\n", + "option = [\n", + " # car 0 1 2 3 4 5\n", + " [1, 0, 0, 0, 1, 1], # option 1\n", + " [0, 0, 1, 1, 0, 1], # option 2\n", + " [1, 0, 0, 0, 1, 0], # option 3\n", + " [1, 1, 0, 1, 0, 0], # option 4\n", + " [0, 0, 1, 0, 0, 0] # option 5\n", + "]\n", + "\n", + "capacity = [(1, 2), (2, 3), (1, 3), (2, 5), (1, 5)]\n", + "\n", + "optionDemand = [\n", + " sum([demand[j] * option[i][j] for j in Cars]) for i in Options\n", + "]\n", + "\n", + "#\n", + "# declare variables\n", + "#\n", + "slot = [solver.IntVar(0, nbCars - 1, \"slot[%i]\" % i) for i in Slots]\n", + "setup = {}\n", + "for i in Options:\n", + " for j in Slots:\n", + " setup[(i, j)] = solver.IntVar(0, 1, \"setup[%i,%i]\" % (i, j))\n", + "setup_flat = [setup[i, j] for i in Options for j in Slots]\n", + "\n", + "#\n", + "# constraints\n", + "#\n", + "for c in Cars:\n", + " b = [solver.IsEqualCstVar(slot[s], c) for s in Slots]\n", + " solver.Add(solver.Sum(b) == demand[c])\n", + "\n", + "for o in Options:\n", + " for s in range(0, nbSlots - capacity[o][1] + 1):\n", + " b = [setup[o, j] for j in range(s, s + capacity[o][1] - 1)]\n", + " solver.Add(solver.Sum(b) <= capacity[o][0])\n", + "\n", + "for o in Options:\n", + " for s in Slots:\n", + " solver.Add(setup[(o, s)] == solver.Element(option[o], slot[s]))\n", + "\n", + "for o in Options:\n", + " for i in range(optionDemand[o]):\n", + " s_range = list(range(0, nbSlots - (i + 1) * capacity[o][1]))\n", + " ss = [setup[o, s] for s in s_range]\n", + " cc = optionDemand[o] - (i + 1) * capacity[o][0]\n", + " if len(ss) > 0 and cc >= 0:\n", + " solver.Add(solver.Sum(ss) >= cc)\n", + "\n", + "#\n", + "# search and result\n", + "#\n", + "db = solver.Phase(slot + setup_flat, solver.CHOOSE_FIRST_UNBOUND,\n", + " solver.ASSIGN_MIN_VALUE)\n", + "\n", + "solver.NewSearch(db)\n", + "num_solutions = 0\n", + "while solver.NextSolution():\n", + " print(\"slot:%s\" % \",\".join([str(slot[i].Value()) for i in Slots]))\n", + " print(\"setup:\")\n", + " for o in Options:\n", + " print(\"%i/%i:\" % (capacity[o][0], capacity[o][1]), end=\" \")\n", + " for s in Slots:\n", + " print(setup[o, s].Value(), end=\" \")\n", + " print()\n", + " print()\n", + " num_solutions += 1\n", + "\n", + " if num_solutions >= num_sol:\n", + " break\n", + "\n", + "solver.EndSearch()\n", + "\n", + "print()\n", + "print(\"num_solutions:\", num_solutions)\n", + "print(\"failures:\", solver.Failures())\n", + "print(\"branches:\", solver.Branches())\n", + "print(\"WallTime:\", solver.WallTime())\n", + "\n", + "num_sol = 3\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/contrib/check_dependencies.ipynb b/examples/notebook/contrib/check_dependencies.ipynb new file mode 100644 index 0000000000..31c4da7eba --- /dev/null +++ b/examples/notebook/contrib/check_dependencies.ipynb @@ -0,0 +1,58 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import logging, sys, inspect\n", + "from os.path import dirname, abspath\n", + "from optparse import OptionParser\n", + "\n", + "\n", + "def log_error_and_exit(error_message):\n", + " logging.error(error_message)\n", + " raise SystemExit\n", + "\n", + "\n", + "#try to import setuptools\n", + "try:\n", + " from setuptools import setup, Extension\n", + " from setuptools.command import easy_install\n", + "except ImportError:\n", + " log_error_and_exit(\"\"\"setuptools is not installed for \\\"\"\"\" + sys.executable +\n", + " \"\"\"\\\"\n", + "Follow this link for installing instructions :\n", + "https://pypi.python.org/pypi/setuptools\n", + "make sure you use \\\"\"\"\" + sys.executable + \"\"\"\\\" during the installation\"\"\")\n", + "\n", + "from pkg_resources import parse_version\n", + "\n", + "\n", + "def notinstalled(modulename):\n", + " return modulename + \"\"\" could not be imported for \\\"\"\"\" + sys.executable + \"\"\"\\\"\n", + "Set PYTHONPATH to the output of this command \\\"make print-OR_TOOLS_PYTHONPATH\\\" before running the examples\"\"\"\n", + "\n", + "\n", + "def wrong_module(module_file, modulename):\n", + " return \"\"\"\n", + "The python examples are not importing the \"\"\" + modulename + \"\"\" module from the sources.\n", + "Remove the site-package that contains \\\"\"\"\" + module_file + \"\"\"\\\", either manually or by using pip, and rerun this script again.\"\"\"\n", + "\n", + "\n", + "# Returns the n_th parent of file\n", + "def n_dirname(n, file):\n", + " directory = file\n", + " for x in range(0, n):\n", + " directory = dirname(directory)\n", + " return directory\n", + "\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/contrib/circuit.ipynb b/examples/notebook/contrib/circuit.ipynb new file mode 100644 index 0000000000..fcc11ccd99 --- /dev/null +++ b/examples/notebook/contrib/circuit.ipynb @@ -0,0 +1,140 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010 Hakan Kjellerstrand hakank@gmail.com\n", + "#\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"\n", + "\n", + " Decomposition of the circuit constraint in Google CP Solver.\n", + "\n", + "\n", + " Cf Global constraint catalog:\n", + " http://www.emn.fr/x-info/sdemasse/gccat/Ccircuit.html\n", + "\n", + " Solution of n=4:\n", + " x: [2, 0, 3, 1]\n", + " x: [3, 0, 1, 2]\n", + " x: [1, 3, 0, 2]\n", + " x: [3, 2, 0, 1]\n", + " x: [1, 2, 3, 0]\n", + " x: [2, 3, 1, 0]\n", + "\n", + " The 'orbit' method that is used here is based on some\n", + " observations on permutation orbits.\n", + "\n", + " Compare with the following models:\n", + " * MiniZinc: http://www.hakank.org/minizinc/circuit_test.mzn\n", + " * Gecode: http://www.hakank.org/gecode/circuit_orbit.mzn\n", + "\n", + "\n", + " This model was created by Hakan Kjellerstrand (hakank@gmail.com)\n", + " Also see my other Google CP Solver models:\n", + " http://www.hakank.org/google_or_tools/\n", + "\n", + "\"\"\"\n", + "\n", + "from __future__ import print_function\n", + "\n", + "import sys\n", + "from ortools.constraint_solver import pywrapcp\n", + "\n", + "#\n", + "# circuit(x)\n", + "# constraints x to be an circuit\n", + "#\n", + "# Note: This assumes that x is has the domain 0..len(x)-1,\n", + "# i.e. 0-based.\n", + "#\n", + "\n", + "\n", + "def circuit(solver, x):\n", + " n = len(x)\n", + " z = [solver.IntVar(0, n - 1, \"z%i\" % i) for i in range(n)]\n", + "\n", + " solver.Add(solver.AllDifferent(x))\n", + " solver.Add(solver.AllDifferent(z))\n", + "\n", + " # put the orbit of x[0] in in z[0..n-1]\n", + " solver.Add(z[0] == x[0])\n", + " for i in range(1, n - 1):\n", + " # The following constraint give the error\n", + " # \"TypeError: list indices must be integers, not IntVar\"\n", + " # solver.Add(z[i] == x[z[i-1]])\n", + "\n", + " # solution: use Element instead\n", + " solver.Add(z[i] == solver.Element(x, z[i - 1]))\n", + "\n", + " #\n", + " # Note: At least one of the following two constraint must be set.\n", + " #\n", + " # may not be 0 for i < n-1\n", + " for i in range(1, n - 1):\n", + " solver.Add(z[i] != 0)\n", + "\n", + " # when i = n-1 it must be 0\n", + " solver.Add(z[n - 1] == 0)\n", + "\n", + "\n", + "\n", + "# Create the solver.\n", + "solver = pywrapcp.Solver(\"Send most money\")\n", + "\n", + "# data\n", + "print(\"n:\", n)\n", + "\n", + "# declare variables\n", + "# Note: domain should be 0..n-1\n", + "x = [solver.IntVar(0, n - 1, \"x%i\" % i) for i in range(n)]\n", + "\n", + "#\n", + "# constraints\n", + "#\n", + "circuit(solver, x)\n", + "\n", + "#\n", + "# solution and search\n", + "#\n", + "solution = solver.Assignment()\n", + "solution.Add(x)\n", + "\n", + "collector = solver.AllSolutionCollector(solution)\n", + "\n", + "solver.Solve(\n", + " solver.Phase(x, solver.CHOOSE_FIRST_UNBOUND, solver.ASSIGN_MIN_VALUE),\n", + " [collector])\n", + "\n", + "num_solutions = collector.SolutionCount()\n", + "for s in range(num_solutions):\n", + " print(\"x:\", [collector.Value(s, x[i]) for i in range(len(x))])\n", + "\n", + "print()\n", + "print(\"num_solutions:\", num_solutions)\n", + "print(\"failures:\", solver.Failures())\n", + "print(\"branches:\", solver.Branches())\n", + "print(\"WallTime:\", solver.WallTime())\n", + "print()\n", + "\n", + "n = 5\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/contrib/coins3.ipynb b/examples/notebook/contrib/coins3.ipynb new file mode 100644 index 0000000000..8833f33d3d --- /dev/null +++ b/examples/notebook/contrib/coins3.ipynb @@ -0,0 +1,115 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010 Hakan Kjellerstrand hakank@gmail.com\n", + "#\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"\n", + "\n", + " Coin application in Google CP Solver.\n", + "\n", + " From 'Constraint Logic Programming using ECLiPSe'\n", + " pages 99f and 234 ff.\n", + " The solution in ECLiPSe is at page 236.\n", + "\n", + " '''\n", + " What is the minimum number of coins that allows one to pay _exactly_\n", + " any amount smaller than one Euro? Recall that there are six different\n", + " euro cents, of denomination 1, 2, 5, 10, 20, 50\n", + " '''\n", + "\n", + " Compare with the following models:\n", + " * MiniZinc: http://hakank.org/minizinc/coins3.mzn\n", + " * Comet : http://www.hakank.org/comet/coins3.co\n", + " * Gecode : http://hakank.org/gecode/coins3.cpp\n", + " * SICStus : http://hakank.org/sicstus/coins3.pl\n", + "\n", + "\n", + " This model was created by Hakan Kjellerstrand (hakank@gmail.com)\n", + " Also see my other Google CP Solver models:\n", + " http://www.hakank.org/google_or_tools/\n", + "\"\"\"\n", + "\n", + "from __future__ import print_function\n", + "\n", + "import sys\n", + "from ortools.constraint_solver import pywrapcp\n", + "\n", + "\n", + "# Create the solver.\n", + "solver = pywrapcp.Solver(\"Coins\")\n", + "\n", + "#\n", + "# data\n", + "#\n", + "n = 6 # number of different coins\n", + "variables = [1, 2, 5, 10, 25, 50]\n", + "\n", + "# declare variables\n", + "x = [solver.IntVar(0, 99, \"x%i\" % i) for i in range(n)]\n", + "num_coins = solver.IntVar(0, 99, \"num_coins\")\n", + "\n", + "#\n", + "# constraints\n", + "#\n", + "\n", + "# number of used coins, to be minimized\n", + "solver.Add(num_coins == solver.Sum(x))\n", + "\n", + "# Check that all changes from 1 to 99 can be made.\n", + "for j in range(1, 100):\n", + " tmp = [solver.IntVar(0, 99, \"b%i\" % i) for i in range(n)]\n", + " solver.Add(solver.ScalProd(tmp, variables) == j)\n", + " [solver.Add(tmp[i] <= x[i]) for i in range(n)]\n", + "\n", + "# objective\n", + "objective = solver.Minimize(num_coins, 1)\n", + "\n", + "#\n", + "# solution and search\n", + "#\n", + "solution = solver.Assignment()\n", + "solution.Add(x)\n", + "solution.Add(num_coins)\n", + "solution.AddObjective(num_coins)\n", + "\n", + "db = solver.Phase(x, solver.CHOOSE_MIN_SIZE_LOWEST_MAX,\n", + " solver.ASSIGN_MIN_VALUE)\n", + "\n", + "solver.NewSearch(db, [objective])\n", + "num_solutions = 0\n", + "while solver.NextSolution():\n", + " print(\"x: \", [x[i].Value() for i in range(n)])\n", + " print(\"num_coins:\", num_coins.Value())\n", + " print()\n", + " num_solutions += 1\n", + "solver.EndSearch()\n", + "\n", + "print()\n", + "print(\"num_solutions:\", num_solutions)\n", + "print(\"failures:\", solver.Failures())\n", + "print(\"branches:\", solver.Branches())\n", + "print(\"WallTime:\", solver.WallTime())\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/contrib/coins_grid.ipynb b/examples/notebook/contrib/coins_grid.ipynb new file mode 100644 index 0000000000..4a10868e60 --- /dev/null +++ b/examples/notebook/contrib/coins_grid.ipynb @@ -0,0 +1,129 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010 Hakan Kjellerstrand hakank@gmail.com\n", + "#\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"\n", + " Coins grid problem in Google CP Solver.\n", + "\n", + " Problem from\n", + " Tony Hurlimann: \"A coin puzzle - SVOR-contest 2007\"\n", + " http://www.svor.ch/competitions/competition2007/AsroContestSolution.pdf\n", + " '''\n", + " In a quadratic grid (or a larger chessboard) with 31x31 cells, one should\n", + " place coins in such a way that the following conditions are fulfilled:\n", + " 1. In each row exactly 14 coins must be placed.\n", + " 2. In each column exactly 14 coins must be placed.\n", + " 3. The sum of the quadratic horizontal distance from the main diagonal\n", + " of all cells containing a coin must be as small as possible.\n", + " 4. In each cell at most one coin can be placed.\n", + " The description says to place 14x31 = 434 coins on the chessboard each row\n", + " containing 14 coins and each column also containing 14 coins.\n", + " '''\n", + "\n", + " Cf the LPL model:\n", + " http://diuflx71.unifr.ch/lpl/GetModel?name=/puzzles/coin\n", + "\n", + " Note: Laurent Perron helped me to improve this model.\n", + "\n", + " Compare with the following models:\n", + " * Tailor/Essence': http://hakank.org/tailor/coins_grid.eprime\n", + " * MiniZinc: http://hakank.org/minizinc/coins_grid.mzn\n", + " * SICStus: http://hakank.org/sicstus/coins_grid.pl\n", + " * Zinc: http://hakank.org/minizinc/coins_grid.zinc\n", + " * Choco: http://hakank.org/choco/CoinsGrid.java\n", + " * Comet: http://hakank.org/comet/coins_grid.co\n", + " * ECLiPSe: http://hakank.org/eclipse/coins_grid.ecl\n", + " * Gecode: http://hakank.org/gecode/coins_grid.cpp\n", + " * Gecode/R: http://hakank.org/gecode_r/coins_grid.rb\n", + " * JaCoP: http://hakank.org/JaCoP/CoinsGrid.java\n", + "\n", + " This model was created by Hakan Kjellerstrand (hakank@gmail.com)\n", + " Also see my other Google CP Solver models:\n", + " http://www.hakank.org/google_or_tools/\n", + "\"\"\"\n", + "\n", + "from __future__ import print_function\n", + "import sys\n", + "from ortools.constraint_solver import pywrapcp\n", + "\n", + "\n", + "# Create the solver.\n", + "solver = pywrapcp.Solver(\"Coins grid\")\n", + "# data\n", + "\n", + "print(\"n: \", n)\n", + "print(\"c: \", c)\n", + "\n", + "# declare variables\n", + "x = {}\n", + "for i in range(n):\n", + " for j in range(n):\n", + " x[(i, j)] = solver.BoolVar(\"x %i %i\" % (i, j))\n", + "\n", + "#\n", + "# constraints\n", + "#\n", + "\n", + "# sum rows/columns == c\n", + "for i in range(n):\n", + " solver.Add(solver.SumEquality([x[(i, j)] for j in range(n)], c)) # sum rows\n", + " solver.Add(solver.SumEquality([x[(j, i)] for j in range(n)], c)) # sum cols\n", + "\n", + "# quadratic horizonal distance var\n", + "objective_var = solver.Sum(\n", + " [x[(i, j)] * (i - j) * (i - j) for i in range(n) for j in range(n)])\n", + "\n", + "# objective\n", + "objective = solver.Minimize(objective_var, 1)\n", + "\n", + "#\n", + "# solution and search\n", + "#\n", + "solution = solver.Assignment()\n", + "solution.Add([x[(i, j)] for i in range(n) for j in range(n)])\n", + "solution.AddObjective(objective_var)\n", + "\n", + "# last solutions\n", + "collector = solver.LastSolutionCollector(solution)\n", + "search_log = solver.SearchLog(1000000, objective_var)\n", + "restart = solver.ConstantRestart(300)\n", + "solver.Solve(\n", + " solver.Phase([x[(i, j)] for i in range(n) for j in range(n)],\n", + " solver.CHOOSE_RANDOM, solver.ASSIGN_MAX_VALUE),\n", + " [collector, search_log, objective])\n", + "\n", + "print(\"objective:\", collector.ObjectiveValue(0))\n", + "for i in range(n):\n", + " for j in range(n):\n", + " print(collector.Value(0, x[(i, j)]), end=\" \")\n", + " print()\n", + "print()\n", + "\n", + "print(\"failures:\", solver.Failures())\n", + "print(\"branches:\", solver.Branches())\n", + "print(\"WallTime:\", solver.WallTime())\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/contrib/coins_grid_mip.ipynb b/examples/notebook/contrib/coins_grid_mip.ipynb new file mode 100644 index 0000000000..ec586e5854 --- /dev/null +++ b/examples/notebook/contrib/coins_grid_mip.ipynb @@ -0,0 +1,113 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2011 Hakan Kjellerstrand hakank@gmail.com\n", + "#\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"\n", + "\n", + " Coins grid problem in Google CP Solver.\n", + "\n", + "\n", + " Problem from\n", + " Tony Hurlimann: \"A coin puzzle - SVOR-contest 2007\"\n", + " http://www.svor.ch/competitions/competition2007/AsroContestSolution.pdf\n", + " '''\n", + " In a quadratic grid (or a larger chessboard) with 31x31 cells, one should\n", + " place coins in such a way that the following conditions are fulfilled:\n", + " 1. In each row exactly 14 coins must be placed.\n", + " 2. In each column exactly 14 coins must be placed.\n", + " 3. The sum of the quadratic horizontal distance from the main diagonal\n", + " of all cells containing a coin must be as small as possible.\n", + " 4. In each cell at most one coin can be placed.\n", + " The description says to place 14x31 = 434 coins on the chessboard each row\n", + " containing 14 coins and each column also containing 14 coins.\n", + " '''\n", + "\n", + " This is a MIP version of\n", + " http://www.hakank.org/google_or_tools/coins_grid.py\n", + " and use\n", + "\n", + " This model was created by Hakan Kjellerstrand (hakank@gmail.com)\n", + " Also see my other Google CP Solver models:\n", + " http://www.hakank.org/google_or_tools/\n", + "\"\"\"\n", + "\n", + "from __future__ import print_function\n", + "from ortools.linear_solver import pywraplp\n", + "\n", + "\n", + "# Create the solver.\n", + "\n", + "# using CBC\n", + "solver = pywraplp.Solver('CoinsGridCBC',\n", + " pywraplp.Solver.CBC_MIXED_INTEGER_PROGRAMMING)\n", + "\n", + "# Using CLP\n", + "# solver = pywraplp.Solver('CoinsGridCLP',\n", + "# pywraplp.Solver.CBC_MIXED_INTEGER_PROGRAMMING)\n", + "\n", + "# data\n", + "n = 31 # the grid size\n", + "c = 14 # number of coins per row/column\n", + "\n", + "# declare variables\n", + "x = {}\n", + "for i in range(n):\n", + " for j in range(n):\n", + " x[(i, j)] = solver.IntVar(0, 1, 'x[%i,%i]' % (i, j))\n", + "\n", + "#\n", + "# constraints\n", + "#\n", + "\n", + "# sum rows/columns == c\n", + "for i in range(n):\n", + " solver.Add(solver.Sum([x[(i, j)] for j in range(n)]) == c) # sum rows\n", + " solver.Add(solver.Sum([x[(j, i)] for j in range(n)]) == c) # sum cols\n", + "\n", + "# quadratic horizonal distance var\n", + "objective_var = solver.Sum(\n", + " [x[(i, j)] * (i - j) * (i - j) for i in range(n) for j in range(n)])\n", + "\n", + "# objective\n", + "objective = solver.Minimize(objective_var)\n", + "\n", + "#\n", + "# solution and search\n", + "#\n", + "solver.Solve()\n", + "\n", + "for i in range(n):\n", + " for j in range(n):\n", + " # int representation\n", + " print(int(x[(i, j)].SolutionValue()), end=' ')\n", + " print()\n", + "print()\n", + "\n", + "print()\n", + "print('walltime :', solver.WallTime(), 'ms')\n", + "# print 'iterations:', solver.Iterations()\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/contrib/coloring_ip.ipynb b/examples/notebook/contrib/coloring_ip.ipynb new file mode 100644 index 0000000000..2878efb06a --- /dev/null +++ b/examples/notebook/contrib/coloring_ip.ipynb @@ -0,0 +1,152 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010 Hakan Kjellerstrand hakank@gmail.com\n", + "#\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"\n", + "\n", + " Simple coloring problem using MIP in Google CP Solver.\n", + "\n", + " Inspired by the GLPK:s model color.mod\n", + " '''\n", + " COLOR, Graph Coloring Problem\n", + "\n", + " Written in GNU MathProg by Andrew Makhorin \n", + "\n", + " Given an undirected loopless graph G = (V, E), where V is a set of\n", + " nodes, E <= V x V is a set of arcs, the Graph Coloring Problem is to\n", + " find a mapping (coloring) F: V -> C, where C = {1, 2, ... } is a set\n", + " of colors whose cardinality is as small as possible, such that\n", + " F(i) != F(j) for every arc (i,j) in E, that is adjacent nodes must\n", + " be assigned different colors.\n", + " '''\n", + "\n", + " Compare with the MiniZinc model:\n", + " http://www.hakank.org/minizinc/coloring_ip.mzn\n", + "\n", + " This model was created by Hakan Kjellerstrand (hakank@gmail.com)\n", + " Also see my other Google CP Solver models:\n", + " http://www.hakank.org/google_or_tools/\n", + "\"\"\"\n", + "from __future__ import print_function\n", + "import sys\n", + "from ortools.linear_solver import pywraplp\n", + "\n", + "\n", + "\n", + "# Create the solver.\n", + "\n", + "print('Solver: ', sol)\n", + "\n", + "if sol == 'GLPK':\n", + " # using GLPK\n", + " solver = pywraplp.Solver('CoinsGridGLPK',\n", + " pywraplp.Solver.GLPK_MIXED_INTEGER_PROGRAMMING)\n", + "else:\n", + " # Using CBC\n", + " solver = pywraplp.Solver('CoinsGridCLP',\n", + " pywraplp.Solver.CBC_MIXED_INTEGER_PROGRAMMING)\n", + "\n", + "#\n", + "# data\n", + "#\n", + "\n", + "# max number of colors\n", + "# [we know that 4 suffices for normal maps]\n", + "nc = 5\n", + "\n", + "# number of nodes\n", + "n = 11\n", + "# set of nodes\n", + "V = list(range(n))\n", + "\n", + "num_edges = 20\n", + "\n", + "#\n", + "# Neighbours\n", + "#\n", + "# This data correspond to the instance myciel3.col from:\n", + "# http://mat.gsia.cmu.edu/COLOR/instances.html\n", + "#\n", + "# Note: 1-based (adjusted below)\n", + "E = [[1, 2], [1, 4], [1, 7], [1, 9], [2, 3], [2, 6], [2, 8], [3, 5], [3, 7],\n", + " [3, 10], [4, 5], [4, 6], [4, 10], [5, 8], [5, 9], [6, 11], [7, 11],\n", + " [8, 11], [9, 11], [10, 11]]\n", + "\n", + "#\n", + "# declare variables\n", + "#\n", + "\n", + "# x[i,c] = 1 means that node i is assigned color c\n", + "x = {}\n", + "for v in V:\n", + " for j in range(nc):\n", + " x[v, j] = solver.IntVar(0, 1, 'v[%i,%i]' % (v, j))\n", + "\n", + "# u[c] = 1 means that color c is used, i.e. assigned to some node\n", + "u = [solver.IntVar(0, 1, 'u[%i]' % i) for i in range(nc)]\n", + "\n", + "# number of colors used, to minimize\n", + "obj = solver.Sum(u)\n", + "\n", + "#\n", + "# constraints\n", + "#\n", + "\n", + "# each node must be assigned exactly one color\n", + "for i in V:\n", + " solver.Add(solver.Sum([x[i, c] for c in range(nc)]) == 1)\n", + "\n", + "# adjacent nodes cannot be assigned the same color\n", + "# (and adjust to 0-based)\n", + "for i in range(num_edges):\n", + " for c in range(nc):\n", + " solver.Add(x[E[i][0] - 1, c] + x[E[i][1] - 1, c] <= u[c])\n", + "\n", + "# objective\n", + "objective = solver.Minimize(obj)\n", + "\n", + "#\n", + "# solution\n", + "#\n", + "solver.Solve()\n", + "\n", + "print()\n", + "print('number of colors:', int(solver.Objective().Value()))\n", + "print('colors used:', [int(u[i].SolutionValue()) for i in range(nc)])\n", + "print()\n", + "\n", + "for v in V:\n", + " print('v%i' % v, ' color ', end=' ')\n", + " for c in range(nc):\n", + " if int(x[v, c].SolutionValue()) == 1:\n", + " print(c)\n", + "\n", + "print()\n", + "print('WallTime:', solver.WallTime())\n", + "if sol == 'CBC':\n", + " print('iterations:', solver.Iterations())\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/contrib/combinatorial_auction2.ipynb b/examples/notebook/contrib/combinatorial_auction2.ipynb new file mode 100644 index 0000000000..cecccd2a2f --- /dev/null +++ b/examples/notebook/contrib/combinatorial_auction2.ipynb @@ -0,0 +1,119 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010 Hakan Kjellerstrand hakank@gmail.com\n", + "#\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"Combinatorial auction in Google CP Solver.\n", + "\n", + " This is a more general model for the combinatorial example\n", + " in the Numberjack Tutorial, pages 9 and 24 (slides 19/175 and\n", + " 51/175).\n", + "\n", + " The original and more talkative model is here:\n", + " http://www.hakank.org/numberjack/combinatorial_auction.py\n", + "\n", + " Compare with the following models:\n", + " * MiniZinc: http://hakank.org/minizinc/combinatorial_auction.mzn\n", + " * Gecode: http://hakank.org/gecode/combinatorial_auction.cpp\n", + "\n", + " This model was created by Hakan Kjellerstrand (hakank@gmail.com)\n", + " Also see my other Google CP Solver models:\n", + " http://www.hakank.org/google_or_tools/\n", + "\"\"\"\n", + "from __future__ import print_function\n", + "import sys\n", + "from collections import *\n", + "from ortools.constraint_solver import pywrapcp\n", + "\n", + "\n", + "\n", + "# Create the solver.\n", + "solver = pywrapcp.Solver(\"Problem\")\n", + "\n", + "#\n", + "# data\n", + "#\n", + "N = 5\n", + "\n", + "# the items for each bid\n", + "items = [\n", + " [0, 1], # A,B\n", + " [0, 2], # A, C\n", + " [1, 3], # B,D\n", + " [1, 2, 3], # B,C,D\n", + " [0] # A\n", + "]\n", + "# collect the bids for each item\n", + "items_t = defaultdict(list)\n", + "\n", + "# [items_t.setdefault(j,[]).append(i) for i in range(N) for j in items[i] ]\n", + "# nicer:\n", + "[items_t[j].append(i) for i in range(N) for j in items[i]]\n", + "\n", + "bid_amount = [10, 20, 30, 40, 14]\n", + "\n", + "#\n", + "# declare variables\n", + "#\n", + "X = [solver.BoolVar(\"x%i\" % i) for i in range(N)]\n", + "obj = solver.IntVar(0, 100, \"obj\")\n", + "\n", + "#\n", + "# constraints\n", + "#\n", + "solver.Add(obj == solver.ScalProd(X, bid_amount))\n", + "for item in items_t:\n", + " solver.Add(solver.Sum([X[bid] for bid in items_t[item]]) <= 1)\n", + "\n", + "# objective\n", + "objective = solver.Maximize(obj, 1)\n", + "\n", + "#\n", + "# solution and search\n", + "#\n", + "solution = solver.Assignment()\n", + "solution.Add(X)\n", + "solution.Add(obj)\n", + "\n", + "# db: DecisionBuilder\n", + "db = solver.Phase(X, solver.CHOOSE_FIRST_UNBOUND, solver.ASSIGN_MIN_VALUE)\n", + "\n", + "solver.NewSearch(db, [objective])\n", + "num_solutions = 0\n", + "while solver.NextSolution():\n", + " print(\"X:\", [X[i].Value() for i in range(N)])\n", + " print(\"obj:\", obj.Value())\n", + " print()\n", + " num_solutions += 1\n", + "\n", + "solver.EndSearch()\n", + "\n", + "print()\n", + "print(\"num_solutions:\", num_solutions)\n", + "print(\"failures:\", solver.Failures())\n", + "print(\"branches:\", solver.Branches())\n", + "print(\"WallTime:\", solver.WallTime())\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/contrib/contiguity_regular.ipynb b/examples/notebook/contrib/contiguity_regular.ipynb new file mode 100644 index 0000000000..a1ac4aa29b --- /dev/null +++ b/examples/notebook/contrib/contiguity_regular.ipynb @@ -0,0 +1,187 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010 Hakan Kjellerstrand hakank@gmail.com\n", + "#\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"\n", + "\n", + " Global constraint contiguity using regularin Google CP Solver.\n", + "\n", + " This is a decomposition of the global constraint\n", + " global contiguity.\n", + "\n", + " From Global Constraint Catalogue\n", + " http://www.emn.fr/x-info/sdemasse/gccat/Cglobal_contiguity.html\n", + " '''\n", + " Enforce all variables of the VARIABLES collection to be assigned to 0 or 1.\n", + " In addition, all variables assigned to value 1 appear contiguously.\n", + "\n", + " Example:\n", + " (<0, 1, 1, 0>)\n", + "\n", + " The global_contiguity constraint holds since the sequence 0 1 1 0 contains\n", + " no more than one group of contiguous 1.\n", + " '''\n", + "\n", + " Compare with the following model:\n", + " * MiniZinc: http://www.hakank.org/minizinc/contiguity_regular.mzn\n", + "\n", + " This model was created by Hakan Kjellerstrand (hakank@gmail.com)\n", + " Also see my other Google CP Solver models:\n", + " http://www.hakank.org/google_or_tools/\n", + "\n", + "\"\"\"\n", + "from __future__ import print_function\n", + "from ortools.constraint_solver import pywrapcp\n", + "\n", + "#\n", + "# Global constraint regular\n", + "#\n", + "# This is a translation of MiniZinc's regular constraint (defined in\n", + "# lib/zinc/globals.mzn), via the Comet code refered above.\n", + "# All comments are from the MiniZinc code.\n", + "# '''\n", + "# The sequence of values in array 'x' (which must all be in the range 1..S)\n", + "# is accepted by the DFA of 'Q' states with input 1..S and transition\n", + "# function 'd' (which maps (1..Q, 1..S) -> 0..Q)) and initial state 'q0'\n", + "# (which must be in 1..Q) and accepting states 'F' (which all must be in\n", + "# 1..Q). We reserve state 0 to be an always failing state.\n", + "# '''\n", + "#\n", + "# x : IntVar array\n", + "# Q : number of states\n", + "# S : input_max\n", + "# d : transition matrix\n", + "# q0: initial state\n", + "# F : accepting states\n", + "\n", + "\n", + "def regular(x, Q, S, d, q0, F):\n", + "\n", + " solver = x[0].solver()\n", + "\n", + " assert Q > 0, 'regular: \"Q\" must be greater than zero'\n", + " assert S > 0, 'regular: \"S\" must be greater than zero'\n", + "\n", + " # d2 is the same as d, except we add one extra transition for\n", + " # each possible input; each extra transition is from state zero\n", + " # to state zero. This allows us to continue even if we hit a\n", + " # non-accepted input.\n", + "\n", + " # Comet: int d2[0..Q, 1..S]\n", + " d2 = []\n", + " for i in range(Q + 1):\n", + " row = []\n", + " for j in range(S):\n", + " if i == 0:\n", + " row.append(0)\n", + " else:\n", + " row.append(d[i - 1][j])\n", + " d2.append(row)\n", + "\n", + " d2_flatten = [d2[i][j] for i in range(Q + 1) for j in range(S)]\n", + "\n", + " # If x has index set m..n, then a[m-1] holds the initial state\n", + " # (q0), and a[i+1] holds the state we're in after processing\n", + " # x[i]. If a[n] is in F, then we succeed (ie. accept the\n", + " # string).\n", + " x_range = list(range(0, len(x)))\n", + " m = 0\n", + " n = len(x)\n", + "\n", + " a = [solver.IntVar(0, Q + 1, 'a[%i]' % i) for i in range(m, n + 1)]\n", + "\n", + " # Check that the final state is in F\n", + " solver.Add(solver.MemberCt(a[-1], F))\n", + " # First state is q0\n", + " solver.Add(a[m] == q0)\n", + " for i in x_range:\n", + " solver.Add(x[i] >= 1)\n", + " solver.Add(x[i] <= S)\n", + "\n", + " # Determine a[i+1]: a[i+1] == d2[a[i], x[i]]\n", + " solver.Add(\n", + " a[i + 1] == solver.Element(d2_flatten, ((a[i]) * S) + (x[i] - 1)))\n", + "\n", + "\n", + "\n", + "# Create the solver.\n", + "solver = pywrapcp.Solver('Global contiguity using regular')\n", + "\n", + "#\n", + "# data\n", + "#\n", + "# the DFA (for regular)\n", + "n_states = 3\n", + "input_max = 2\n", + "initial_state = 1 # 0 is for the failing state\n", + "\n", + "# all states are accepting states\n", + "accepting_states = [1, 2, 3]\n", + "\n", + "# The regular expression 0*1*0*\n", + "transition_fn = [\n", + " [1, 2], # state 1 (start): input 0 -> state 1, input 1 -> state 2 i.e. 0*\n", + " [3, 2], # state 2: 1*\n", + " [3, 0], # state 3: 0*\n", + "]\n", + "\n", + "n = 7\n", + "\n", + "#\n", + "# declare variables\n", + "#\n", + "\n", + "# We use 1..2 and subtract 1 in the solution\n", + "reg_input = [solver.IntVar(1, 2, 'x[%i]' % i) for i in range(n)]\n", + "\n", + "#\n", + "# constraints\n", + "#\n", + "regular(reg_input, n_states, input_max, transition_fn, initial_state,\n", + " accepting_states)\n", + "\n", + "#\n", + "# solution and search\n", + "#\n", + "db = solver.Phase(reg_input, solver.CHOOSE_FIRST_UNBOUND,\n", + " solver.ASSIGN_MIN_VALUE)\n", + "\n", + "solver.NewSearch(db)\n", + "\n", + "num_solutions = 0\n", + "while solver.NextSolution():\n", + " num_solutions += 1\n", + " # Note: here we subract 1 from the solution\n", + " print('reg_input:', [int(reg_input[i].Value() - 1) for i in range(n)])\n", + "\n", + "solver.EndSearch()\n", + "print()\n", + "print('num_solutions:', num_solutions)\n", + "print('failures:', solver.Failures())\n", + "print('branches:', solver.Branches())\n", + "print('wall_time:', solver.WallTime(), 'ms')\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/contrib/costas_array.ipynb b/examples/notebook/contrib/costas_array.ipynb new file mode 100644 index 0000000000..1414eda3d4 --- /dev/null +++ b/examples/notebook/contrib/costas_array.ipynb @@ -0,0 +1,178 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010 Hakan Kjellerstrand hakank@gmail.com\n", + "#\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"\n", + "\n", + " Costas array in Google CP Solver.\n", + "\n", + " From http://mathworld.wolfram.com/CostasArray.html:\n", + " '''\n", + " An order-n Costas array is a permutation on {1,...,n} such\n", + " that the distances in each row of the triangular difference\n", + " table are distinct. For example, the permutation {1,3,4,2,5}\n", + " has triangular difference table {2,1,-2,3}, {3,-1,1}, {1,2},\n", + " and {4}. Since each row contains no duplications, the permutation\n", + " is therefore a Costas array.\n", + " '''\n", + "\n", + " Also see\n", + " http://en.wikipedia.org/wiki/Costas_array\n", + "\n", + " About this model:\n", + " This model is based on Barry O'Sullivan's model:\n", + " http://www.g12.cs.mu.oz.au/mzn/costas_array/CostasArray.mzn\n", + "\n", + " and my small changes in\n", + " http://hakank.org/minizinc/costas_array.mzn\n", + "\n", + " Since there is no symmetry breaking of the order of the Costas\n", + " array it gives all the solutions for a specific length of\n", + " the array, e.g. those listed in\n", + " http://mathworld.wolfram.com/CostasArray.html\n", + "\n", + " 1 1 (1)\n", + " 2 2 (1, 2), (2,1)\n", + " 3 4 (1, 3, 2), (2, 1, 3), (2, 3, 1), (3, 1, 2)\n", + " 4 12 (1, 2, 4, 3), (1, 3, 4, 2), (1, 4, 2, 3), (2, 1, 3, 4),\n", + " (2, 3, 1, 4), (2, 4, 3, 1), (3, 1, 2, 4), (3, 2, 4, 1),\n", + " (3, 4, 2, 1), (4, 1, 3, 2), (4, 2, 1, 3), (4, 3, 1, 2)\n", + " ....\n", + "\n", + " See http://www.research.att.com/~njas/sequences/A008404\n", + " for the number of solutions for n=1..\n", + " 1, 2, 4, 12, 40, 116, 200, 444, 760, 2160, 4368, 7852, 12828,\n", + " 17252, 19612, 21104, 18276, 15096, 10240, 6464, 3536, 2052,\n", + " 872, 200, 88, 56, 204,...\n", + "\n", + "\n", + " This model was created by Hakan Kjellerstrand (hakank@gmail.com)\n", + " Also see my other Google CP Solver models:\n", + " http://www.hakank.org/google_or_tools/\n", + "\"\"\"\n", + "from __future__ import print_function\n", + "import sys\n", + "\n", + "from ortools.constraint_solver import pywrapcp\n", + "\n", + "\n", + "\n", + "# Create the solver.\n", + "solver = pywrapcp.Solver(\"Costas array\")\n", + "\n", + "#\n", + "# data\n", + "#\n", + "print(\"n:\", n)\n", + "\n", + "#\n", + "# declare variables\n", + "#\n", + "costas = [solver.IntVar(1, n, \"costas[%i]\" % i) for i in range(n)]\n", + "differences = {}\n", + "for i in range(n):\n", + " for j in range(n):\n", + " differences[(i, j)] = solver.IntVar(-n + 1, n - 1,\n", + " \"differences[%i,%i]\" % (i, j))\n", + "differences_flat = [differences[i, j] for i in range(n) for j in range(n)]\n", + "\n", + "#\n", + "# constraints\n", + "#\n", + "\n", + "# Fix the values in the lower triangle in the\n", + "# difference matrix to -n+1. This removes variants\n", + "# of the difference matrix for the the same Costas array.\n", + "for i in range(n):\n", + " for j in range(i + 1):\n", + " solver.Add(differences[i, j] == -n + 1)\n", + "\n", + "# hakank: All the following constraints are from\n", + "# Barry O'Sullivans's original model.\n", + "#\n", + "solver.Add(solver.AllDifferent(costas))\n", + "\n", + "# \"How do the positions in the Costas array relate\n", + "# to the elements of the distance triangle.\"\n", + "for i in range(n):\n", + " for j in range(n):\n", + " if i < j:\n", + " solver.Add(differences[(i, j)] == costas[j] - costas[j - i - 1])\n", + "\n", + "# \"All entries in a particular row of the difference\n", + "# triangle must be distint.\"\n", + "for i in range(n - 2):\n", + " solver.Add(\n", + " solver.AllDifferent([differences[i, j] for j in range(n) if j > i]))\n", + "\n", + "#\n", + "# \"All the following are redundant - only here to speed up search.\"\n", + "#\n", + "\n", + "# \"We can never place a 'token' in the same row as any other.\"\n", + "for i in range(n):\n", + " for j in range(n):\n", + " if i < j:\n", + " solver.Add(differences[i, j] != 0)\n", + "\n", + "for k in range(2, n):\n", + " for l in range(2, n):\n", + " if k < l:\n", + " solver.Add(differences[k - 2, l - 1] + differences[k, l] ==\n", + " differences[k - 1, l - 1] + differences[k - 1, l])\n", + "\n", + "#\n", + "# search and result\n", + "#\n", + "db = solver.Phase(costas + differences_flat, solver.CHOOSE_FIRST_UNBOUND,\n", + " solver.ASSIGN_MIN_VALUE)\n", + "\n", + "solver.NewSearch(db)\n", + "num_solutions = 0\n", + "while solver.NextSolution():\n", + " print(\"costas:\", [costas[i].Value() for i in range(n)])\n", + " print(\"differences:\")\n", + " for i in range(n):\n", + " for j in range(n):\n", + " v = differences[i, j].Value()\n", + " if v == -n + 1:\n", + " print(\" \", end=\" \")\n", + " else:\n", + " print(\"%2d\" % v, end=\" \")\n", + " print()\n", + " print()\n", + " num_solutions += 1\n", + "\n", + "solver.EndSearch()\n", + "\n", + "print()\n", + "print(\"num_solutions:\", num_solutions)\n", + "print(\"failures:\", solver.Failures())\n", + "print(\"branches:\", solver.Branches())\n", + "print(\"WallTime:\", solver.WallTime())\n", + "\n", + "n = 6\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/contrib/covering_opl.ipynb b/examples/notebook/contrib/covering_opl.ipynb new file mode 100644 index 0000000000..461c8e9a22 --- /dev/null +++ b/examples/notebook/contrib/covering_opl.ipynb @@ -0,0 +1,153 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010 Hakan Kjellerstrand hakank@gmail.com\n", + "#\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"\n", + "\n", + " Set covering problem in Google CP Solver.\n", + "\n", + " This example is from the OPL example covering.mod\n", + " '''\n", + " Consider selecting workers to build a house. The construction of a\n", + " house can be divided into a number of tasks, each requiring a number of\n", + " skills (e.g., plumbing or masonry). A worker may or may not perform a\n", + " task, depending on skills. In addition, each worker can be hired for a\n", + " cost that also depends on his qualifications. The problem consists of\n", + " selecting a set of workers to perform all the tasks, while minimizing the\n", + " cost. This is known as a set-covering problem. The key idea in modeling\n", + " a set-covering problem as an integer program is to associate a 0/1\n", + " variable with each worker to represent whether the worker is hired.\n", + " To make sure that all the tasks are performed, it is sufficient to\n", + " choose at least one worker by task. This constraint can be expressed by a\n", + " simple linear inequality.\n", + " '''\n", + "\n", + " Solution from the OPL model (1-based)\n", + " '''\n", + " Optimal solution found with objective: 14\n", + " crew= {23 25 26}\n", + " '''\n", + "\n", + " Solution from this model (0-based):\n", + " '''\n", + " Total cost 14\n", + " We should hire these workers: 22 24 25\n", + " '''\n", + "\n", + "\n", + " Compare with the following models:\n", + " * Comet: http://hakank.org/comet/covering_opl.co\n", + " * MiniZinc: http://hakank.org/minizinc/covering_opl.mzn\n", + " * ECLiPSe: http://hakank.org/eclipse/covering_opl.ecl\n", + " * Gecode: http://hakank.org/gecode/covering_opl.cpp\n", + " * SICStus: http://hakank.org/sicstus/covering_opl.pl\n", + "\n", + " This model was created by Hakan Kjellerstrand (hakank@gmail.com)\n", + " Also see my other Google CP Solver models:\n", + " http://www.hakank.org/google_or_tools/\n", + "\"\"\"\n", + "from __future__ import print_function\n", + "import sys\n", + "\n", + "from ortools.constraint_solver import pywrapcp\n", + "\n", + "\n", + "\n", + "# Create the solver.\n", + "solver = pywrapcp.Solver(\"Set covering\")\n", + "\n", + "#\n", + "# data\n", + "#\n", + "nb_workers = 32\n", + "Workers = list(range(nb_workers))\n", + "num_tasks = 15\n", + "Tasks = list(range(num_tasks))\n", + "\n", + "# Which worker is qualified for each task.\n", + "# Note: This is 1-based and will be made 0-base below.\n", + "Qualified = [[1, 9, 19, 22, 25, 28, 31],\n", + " [2, 12, 15, 19, 21, 23, 27, 29, 30, 31, 32],\n", + " [3, 10, 19, 24, 26, 30, 32], [4, 21, 25, 28, 32],\n", + " [5, 11, 16, 22, 23, 27, 31], [6, 20, 24, 26, 30, 32],\n", + " [7, 12, 17, 25, 30, 31], [8, 17, 20, 22, 23],\n", + " [9, 13, 14, 26, 29, 30, 31], [10, 21, 25, 31, 32],\n", + " [14, 15, 18, 23, 24, 27, 30, 32], [18, 19, 22, 24, 26, 29, 31],\n", + " [11, 20, 25, 28, 30, 32], [16, 19, 23, 31],\n", + " [9, 18, 26, 28, 31, 32]]\n", + "\n", + "Cost = [\n", + " 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5,\n", + " 5, 6, 6, 6, 7, 8, 9\n", + "]\n", + "\n", + "#\n", + "# variables\n", + "#\n", + "Hire = [solver.IntVar(0, 1, \"Hire[%i]\" % w) for w in Workers]\n", + "total_cost = solver.IntVar(0, nb_workers * sum(Cost), \"total_cost\")\n", + "\n", + "#\n", + "# constraints\n", + "#\n", + "solver.Add(total_cost == solver.ScalProd(Hire, Cost))\n", + "\n", + "for j in Tasks:\n", + " # Sum the cost for hiring the qualified workers\n", + " # (also, make 0-base)\n", + " b = solver.Sum([Hire[c - 1] for c in Qualified[j]])\n", + " solver.Add(b >= 1)\n", + "\n", + "# objective: Minimize total cost\n", + "objective = solver.Minimize(total_cost, 1)\n", + "\n", + "#\n", + "# search and result\n", + "#\n", + "db = solver.Phase(Hire, solver.CHOOSE_FIRST_UNBOUND, solver.ASSIGN_MIN_VALUE)\n", + "\n", + "solver.NewSearch(db, [objective])\n", + "\n", + "num_solutions = 0\n", + "while solver.NextSolution():\n", + " num_solutions += 1\n", + " print(\"Total cost\", total_cost.Value())\n", + " print(\"We should hire these workers: \", end=\" \")\n", + " for w in Workers:\n", + " if Hire[w].Value() == 1:\n", + " print(w, end=\" \")\n", + " print()\n", + " print()\n", + "\n", + "solver.EndSearch()\n", + "\n", + "print()\n", + "print(\"num_solutions:\", num_solutions)\n", + "print(\"failures:\", solver.Failures())\n", + "print(\"branches:\", solver.Branches())\n", + "print(\"WallTime:\", solver.WallTime())\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/contrib/crew.ipynb b/examples/notebook/contrib/crew.ipynb new file mode 100644 index 0000000000..5c9528dc18 --- /dev/null +++ b/examples/notebook/contrib/crew.ipynb @@ -0,0 +1,219 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010 Hakan Kjellerstrand hakank@gmail.com\n", + "#\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"\n", + "\n", + " Crew allocation problem in Google CP Solver.\n", + "\n", + " From Gecode example crew\n", + " examples/crew.cc\n", + " '''\n", + " * Example: Airline crew allocation\n", + " *\n", + " * Assign 20 flight attendants to 10 flights. Each flight needs a certain\n", + " * number of cabin crew, and they have to speak certain languages.\n", + " * Every cabin crew member has two flights off after an attended flight.\n", + " *\n", + " '''\n", + "\n", + " Compare with the following models:\n", + " * MiniZinc: http://www.hakank.org/minizinc/crew.mzn\n", + " * Comet : http://www.hakank.org/comet/crew.co\n", + " * ECLiPSe : http://hakank.org/eclipse/crew.ecl\n", + " * SICStus : http://hakank.org/sicstus/crew.pl\n", + "\n", + "\n", + " This model was created by Hakan Kjellerstrand (hakank@gmail.com)\n", + " Also see my other Google CP Solver models:\n", + " http://www.hakank.org/google_or_tools/\n", + "\"\"\"\n", + "from __future__ import print_function\n", + "import sys\n", + "from ortools.constraint_solver import pywrapcp\n", + "\n", + "\n", + "\n", + "# Create the solver.\n", + "solver = pywrapcp.Solver(\"Crew\")\n", + "\n", + "#\n", + "# data\n", + "#\n", + "names = [\n", + " \"Tom\", \"David\", \"Jeremy\", \"Ron\", \"Joe\", \"Bill\", \"Fred\", \"Bob\", \"Mario\",\n", + " \"Ed\", \"Carol\", \"Janet\", \"Tracy\", \"Marilyn\", \"Carolyn\", \"Cathy\", \"Inez\",\n", + " \"Jean\", \"Heather\", \"Juliet\"\n", + "]\n", + "\n", + "num_persons = len(names) # number of persons\n", + "\n", + "attributes = [\n", + " # steward, hostess, french, spanish, german\n", + " [1, 0, 0, 0, 1], # Tom = 1\n", + " [1, 0, 0, 0, 0], # David = 2\n", + " [1, 0, 0, 0, 1], # Jeremy = 3\n", + " [1, 0, 0, 0, 0], # Ron = 4\n", + " [1, 0, 0, 1, 0], # Joe = 5\n", + " [1, 0, 1, 1, 0], # Bill = 6\n", + " [1, 0, 0, 1, 0], # Fred = 7\n", + " [1, 0, 0, 0, 0], # Bob = 8\n", + " [1, 0, 0, 1, 1], # Mario = 9\n", + " [1, 0, 0, 0, 0], # Ed = 10\n", + " [0, 1, 0, 0, 0], # Carol = 11\n", + " [0, 1, 0, 0, 0], # Janet = 12\n", + " [0, 1, 0, 0, 0], # Tracy = 13\n", + " [0, 1, 0, 1, 1], # Marilyn = 14\n", + " [0, 1, 0, 0, 0], # Carolyn = 15\n", + " [0, 1, 0, 0, 0], # Cathy = 16\n", + " [0, 1, 1, 1, 1], # Inez = 17\n", + " [0, 1, 1, 0, 0], # Jean = 18\n", + " [0, 1, 0, 1, 1], # Heather = 19\n", + " [0, 1, 1, 0, 0] # Juliet = 20\n", + "]\n", + "\n", + "# The columns are in the following order:\n", + "# staff : Overall number of cabin crew needed\n", + "# stewards : How many stewards are required\n", + "# hostesses : How many hostesses are required\n", + "# french : How many French speaking employees are required\n", + "# spanish : How many Spanish speaking employees are required\n", + "# german : How many German speaking employees are required\n", + "required_crew = [\n", + " [4, 1, 1, 1, 1, 1], # Flight 1\n", + " [5, 1, 1, 1, 1, 1], # Flight 2\n", + " [5, 1, 1, 1, 1, 1], # ..\n", + " [6, 2, 2, 1, 1, 1],\n", + " [7, 3, 3, 1, 1, 1],\n", + " [4, 1, 1, 1, 1, 1],\n", + " [5, 1, 1, 1, 1, 1],\n", + " [6, 1, 1, 1, 1, 1],\n", + " [6, 2, 2, 1, 1, 1], # ...\n", + " [7, 3, 3, 1, 1, 1] # Flight 10\n", + "]\n", + "\n", + "num_flights = len(required_crew) # number of flights\n", + "\n", + "#\n", + "# declare variables\n", + "#\n", + "crew = {}\n", + "for i in range(num_flights):\n", + " for j in range(num_persons):\n", + " crew[(i, j)] = solver.IntVar(0, 1, \"crew[%i,%i]\" % (i, j))\n", + "crew_flat = [\n", + " crew[(i, j)] for i in range(num_flights) for j in range(num_persons)\n", + "]\n", + "\n", + "# number of working persons\n", + "num_working = solver.IntVar(1, num_persons, \"num_working\")\n", + "\n", + "#\n", + "# constraints\n", + "#\n", + "\n", + "# number of working persons\n", + "solver.Add(num_working == solver.Sum([\n", + " solver.IsGreaterOrEqualCstVar(\n", + " solver.Sum([crew[(f, p)]\n", + " for f in range(num_flights)]), 1)\n", + " for p in range(num_persons)\n", + "]))\n", + "\n", + "for f in range(num_flights):\n", + " # size of crew\n", + " tmp = [crew[(f, i)] for i in range(num_persons)]\n", + " solver.Add(solver.Sum(tmp) == required_crew[f][0])\n", + "\n", + " # attributes and requirements\n", + " for j in range(5):\n", + " tmp = [attributes[i][j] * crew[(f, i)] for i in range(num_persons)]\n", + " solver.Add(solver.Sum(tmp) >= required_crew[f][j + 1])\n", + "\n", + "# after a flight, break for at least two flights\n", + "for f in range(num_flights - 2):\n", + " for i in range(num_persons):\n", + " solver.Add(crew[f, i] + crew[f + 1, i] + crew[f + 2, i] <= 1)\n", + "\n", + "# extra contraint: all must work at least two of the flights\n", + "# for i in range(num_persons):\n", + "# [solver.Add(solver.Sum([crew[f,i] for f in range(num_flights)]) >= 2) ]\n", + "\n", + "#\n", + "# solution and search\n", + "#\n", + "solution = solver.Assignment()\n", + "solution.Add(crew_flat)\n", + "solution.Add(num_working)\n", + "\n", + "db = solver.Phase(crew_flat, solver.CHOOSE_FIRST_UNBOUND,\n", + " solver.ASSIGN_MIN_VALUE)\n", + "\n", + "#\n", + "# result\n", + "#\n", + "solver.NewSearch(db)\n", + "num_solutions = 0\n", + "while solver.NextSolution():\n", + " num_solutions += 1\n", + " print(\"Solution #%i\" % num_solutions)\n", + " print(\"Number working:\", num_working.Value())\n", + " for i in range(num_flights):\n", + " for j in range(num_persons):\n", + " print(crew[i, j].Value(), end=\" \")\n", + " print()\n", + " print()\n", + "\n", + " print(\"Flights:\")\n", + " for flight in range(num_flights):\n", + " print(\"Flight\", flight, \"persons:\", end=\" \")\n", + " for person in range(num_persons):\n", + " if crew[flight, person].Value() == 1:\n", + " print(names[person], end=\" \")\n", + " print()\n", + " print()\n", + "\n", + " print(\"Crew:\")\n", + " for person in range(num_persons):\n", + " print(\"%-10s flights\" % names[person], end=\" \")\n", + " for flight in range(num_flights):\n", + " if crew[flight, person].Value() == 1:\n", + " print(flight, end=\" \")\n", + " print()\n", + " print()\n", + "\n", + " if num_solutions >= sols:\n", + " break\n", + "solver.EndSearch()\n", + "\n", + "print()\n", + "print(\"num_solutions:\", num_solutions)\n", + "print(\"failures:\", solver.Failures())\n", + "print(\"branches:\", solver.Branches())\n", + "print(\"WallTime:\", solver.WallTime())\n", + "\n", + "num_solutions_to_show = 1\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/contrib/crossword2.ipynb b/examples/notebook/contrib/crossword2.ipynb new file mode 100644 index 0000000000..9b2811b319 --- /dev/null +++ b/examples/notebook/contrib/crossword2.ipynb @@ -0,0 +1,207 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010 Hakan Kjellerstrand hakank@gmail.com\n", + "#\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"\n", + "\n", + " Crosswords in Google CP Solver.\n", + "\n", + " This is a standard example for constraint logic programming. See e.g.\n", + "\n", + " http://www.cis.temple.edu/~ingargio/cis587/readings/constraints.html\n", + " '''\n", + " We are to complete the puzzle\n", + "\n", + " 1 2 3 4 5\n", + " +---+---+---+---+---+ Given the list of words:\n", + " 1 | 1 | | 2 | | 3 | AFT LASER\n", + " +---+---+---+---+---+ ALE LEE\n", + " 2 | # | # | | # | | EEL LINE\n", + " +---+---+---+---+---+ HEEL SAILS\n", + " 3 | # | 4 | | 5 | | HIKE SHEET\n", + " +---+---+---+---+---+ HOSES STEER\n", + " 4 | 6 | # | 7 | | | KEEL TIE\n", + " +---+---+---+---+---+ KNOT\n", + " 5 | 8 | | | | |\n", + " +---+---+---+---+---+\n", + " 6 | | # | # | | # | The numbers 1,2,3,4,5,6,7,8 in the crossword\n", + " +---+---+---+---+---+ puzzle correspond to the words\n", + " that will start at those locations.\n", + " '''\n", + "\n", + " The model was inspired by Sebastian Brand's Array Constraint cross word\n", + " example\n", + " http://www.cs.mu.oz.au/~sbrand/project/ac/\n", + " http://www.cs.mu.oz.au/~sbrand/project/ac/examples.pl\n", + "\n", + "\n", + " Also, see the following models:\n", + " * MiniZinc: http://www.hakank.org/minizinc/crossword.mzn\n", + " * Comet: http://www.hakank.org/comet/crossword.co\n", + " * ECLiPSe: http://hakank.org/eclipse/crossword2.ecl\n", + " * Gecode: http://hakank.org/gecode/crossword2.cpp\n", + " * SICStus: http://hakank.org/sicstus/crossword2.pl\n", + " * Zinc: http://hakank.org/minizinc/crossword2.zinc\n", + "\n", + " This model was created by Hakan Kjellerstrand (hakank@gmail.com)\n", + " Also see my other Google CP Solver models:\n", + " http://www.hakank.org/google_or_tools/\n", + "\"\"\"\n", + "from __future__ import print_function\n", + "from ortools.constraint_solver import pywrapcp\n", + "\n", + "\n", + "# Create the solver.\n", + "solver = pywrapcp.Solver(\"Problem\")\n", + "\n", + "#\n", + "# data\n", + "#\n", + "alpha = \"_abcdefghijklmnopqrstuvwxyz\"\n", + "a = 1\n", + "b = 2\n", + "c = 3\n", + "d = 4\n", + "e = 5\n", + "f = 6\n", + "g = 7\n", + "h = 8\n", + "i = 9\n", + "j = 10\n", + "k = 11\n", + "l = 12\n", + "m = 13\n", + "n = 14\n", + "o = 15\n", + "p = 16\n", + "q = 17\n", + "r = 18\n", + "s = 19\n", + "t = 20\n", + "u = 21\n", + "v = 22\n", + "w = 23\n", + "x = 24\n", + "y = 25\n", + "z = 26\n", + "\n", + "num_words = 15\n", + "word_len = 5\n", + "AA = [\n", + " [h, o, s, e, s], # HOSES\n", + " [l, a, s, e, r], # LASER\n", + " [s, a, i, l, s], # SAILS\n", + " [s, h, e, e, t], # SHEET\n", + " [s, t, e, e, r], # STEER\n", + " [h, e, e, l, 0], # HEEL\n", + " [h, i, k, e, 0], # HIKE\n", + " [k, e, e, l, 0], # KEEL\n", + " [k, n, o, t, 0], # KNOT\n", + " [l, i, n, e, 0], # LINE\n", + " [a, f, t, 0, 0], # AFT\n", + " [a, l, e, 0, 0], # ALE\n", + " [e, e, l, 0, 0], # EEL\n", + " [l, e, e, 0, 0], # LEE\n", + " [t, i, e, 0, 0] # TIE\n", + "]\n", + "\n", + "num_overlapping = 12\n", + "overlapping = [\n", + " [0, 2, 1, 0], # s\n", + " [0, 4, 2, 0], # s\n", + " [3, 1, 1, 2], # i\n", + " [3, 2, 4, 0], # k\n", + " [3, 3, 2, 2], # e\n", + " [6, 0, 1, 3], # l\n", + " [6, 1, 4, 1], # e\n", + " [6, 2, 2, 3], # e\n", + " [7, 0, 5, 1], # l\n", + " [7, 2, 1, 4], # s\n", + " [7, 3, 4, 2], # e\n", + " [7, 4, 2, 4] # r\n", + "]\n", + "\n", + "n = 8\n", + "\n", + "# declare variables\n", + "A = {}\n", + "for I in range(num_words):\n", + " for J in range(word_len):\n", + " A[(I, J)] = solver.IntVar(0, 26, \"A(%i,%i)\" % (I, J))\n", + "\n", + "A_flat = [A[(I, J)] for I in range(num_words) for J in range(word_len)]\n", + "E = [solver.IntVar(0, num_words, \"E%i\" % I) for I in range(n)]\n", + "\n", + "#\n", + "# constraints\n", + "#\n", + "solver.Add(solver.AllDifferent(E))\n", + "\n", + "for I in range(num_words):\n", + " for J in range(word_len):\n", + " solver.Add(A[(I, J)] == AA[I][J])\n", + "\n", + "for I in range(num_overlapping):\n", + " # This is what I would do:\n", + " # solver.Add(A[(E[overlapping[I][0]], overlapping[I][1])] == A[(E[overlapping[I][2]], overlapping[I][3])])\n", + "\n", + " # But we must use Element explicitly\n", + " solver.Add(\n", + " solver.Element(A_flat, E[overlapping[I][0]] * word_len +\n", + " overlapping[I][1]) == solver\n", + " .Element(A_flat, E[overlapping[I][2]] * word_len + overlapping[I][3]))\n", + "\n", + "#\n", + "# solution and search\n", + "#\n", + "solution = solver.Assignment()\n", + "solution.Add(E)\n", + "\n", + "# db: DecisionBuilder\n", + "db = solver.Phase(E + A_flat, solver.INT_VAR_SIMPLE, solver.ASSIGN_MIN_VALUE)\n", + "\n", + "solver.NewSearch(db)\n", + "num_solutions = 0\n", + "while solver.NextSolution():\n", + " print(E)\n", + " print_solution(A, E, alpha, n, word_len)\n", + " num_solutions += 1\n", + "solver.EndSearch()\n", + "\n", + "print()\n", + "print(\"num_solutions:\", num_solutions)\n", + "print(\"failures:\", solver.Failures())\n", + "print(\"branches:\", solver.Branches())\n", + "print(\"WallTime:\", solver.WallTime())\n", + "\n", + "def print_solution(A, E, alpha, n, word_len):\n", + " for ee in range(n):\n", + " print(\"%i: (%2i)\" % (ee, E[ee].Value()), end=\" \")\n", + " print(\"\".join(\n", + " [\"%s\" % (alpha[A[ee, ii].Value()]) for ii in range(word_len)]))\n", + "\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/contrib/crypta.ipynb b/examples/notebook/contrib/crypta.ipynb new file mode 100644 index 0000000000..70b5fa852d --- /dev/null +++ b/examples/notebook/contrib/crypta.ipynb @@ -0,0 +1,126 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010 Hakan Kjellerstrand hakank@gmail.com\n", + "#\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"\n", + "\n", + " Cryptarithmetic puzzle in Google CP Solver.\n", + "\n", + " Prolog benchmark problem GNU Prolog (crypta.pl)\n", + " '''\n", + " Name : crypta.pl\n", + " Title : crypt-arithmetic\n", + " Original Source: P. Van Hentenryck's book\n", + " Adapted by : Daniel Diaz - INRIA France\n", + " Date : September 1992\n", + "\n", + " Solve the operation:\n", + "\n", + " B A I J J A J I I A H F C F E B B J E A\n", + " + D H F G A B C D I D B I F F A G F E J E\n", + " -----------------------------------------\n", + " = G J E G A C D D H F A F J B F I H E E F\n", + " '''\n", + "\n", + "\n", + " Compare with the following models:\n", + " * Comet: http://hakank.org/comet/crypta.co\n", + " * MiniZinc: http://hakank.org/minizinc/crypta.mzn\n", + " * ECLiPSe: http://hakank.org/eclipse/crypta.ecl\n", + " * Gecode: http://hakank.org/gecode/crypta.cpp\n", + " * SICStus: http://hakank.org/sicstus/crypta.pl\n", + "\n", + " This model was created by Hakan Kjellerstrand (hakank@gmail.com)\n", + " Also see my other Google CP Solver models:\n", + " http://www.hakank.org/google_or_tools/\n", + "\"\"\"\n", + "from __future__ import print_function\n", + "from ortools.constraint_solver import pywrapcp\n", + "\n", + "\n", + "\n", + "# Create the solver.\n", + "solver = pywrapcp.Solver(\"Crypta\")\n", + "\n", + "#\n", + "# data\n", + "#\n", + "\n", + "#\n", + "# variables\n", + "#\n", + "LD = [solver.IntVar(0, 9, \"LD[%i]\" % i) for i in range(0, 10)]\n", + "A, B, C, D, E, F, G, H, I, J = LD\n", + "\n", + "Sr1 = solver.IntVar(0, 1, \"Sr1\")\n", + "Sr2 = solver.IntVar(0, 1, \"Sr2\")\n", + "\n", + "#\n", + "# constraints\n", + "#\n", + "solver.Add(solver.AllDifferent(LD))\n", + "solver.Add(B >= 1)\n", + "solver.Add(D >= 1)\n", + "solver.Add(G >= 1)\n", + "\n", + "solver.Add(A + 10 * E + 100 * J + 1000 * B + 10000 * B + 100000 * E +\n", + " 1000000 * F + E + 10 * J + 100 * E + 1000 * F + 10000 * G +\n", + " 100000 * A + 1000000 * F == F + 10 * E + 100 * E + 1000 * H +\n", + " 10000 * I + 100000 * F + 1000000 * B + 10000000 * Sr1)\n", + "\n", + "solver.Add(C + 10 * F + 100 * H + 1000 * A + 10000 * I + 100000 * I +\n", + " 1000000 * J + F + 10 * I + 100 * B + 1000 * D + 10000 * I +\n", + " 100000 * D + 1000000 * C + Sr1 == J + 10 * F + 100 * A + 1000 * F +\n", + " 10000 * H + 100000 * D + 1000000 * D + 10000000 * Sr2)\n", + "\n", + "solver.Add(A + 10 * J + 100 * J + 1000 * I + 10000 * A + 100000 * B + B +\n", + " 10 * A + 100 * G + 1000 * F + 10000 * H + 100000 * D + Sr2 == C +\n", + " 10 * A + 100 * G + 1000 * E + 10000 * J + 100000 * G)\n", + "\n", + "#\n", + "# search and result\n", + "#\n", + "db = solver.Phase(LD, solver.INT_VAR_SIMPLE, solver.INT_VALUE_SIMPLE)\n", + "\n", + "solver.NewSearch(db)\n", + "\n", + "num_solutions = 0\n", + "str = \"ABCDEFGHIJ\"\n", + "while solver.NextSolution():\n", + " num_solutions += 1\n", + " for (letter, val) in [(str[i], LD[i].Value()) for i in range(len(LD))]:\n", + " print(\"%s: %i\" % (letter, val))\n", + " print()\n", + "\n", + "solver.EndSearch()\n", + "\n", + "print()\n", + "print(\"num_solutions:\", num_solutions)\n", + "print(\"failures:\", solver.Failures())\n", + "print(\"branches:\", solver.Branches())\n", + "print(\"WallTime:\", solver.WallTime())\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/contrib/crypto.ipynb b/examples/notebook/contrib/crypto.ipynb new file mode 100644 index 0000000000..a5ea78dd11 --- /dev/null +++ b/examples/notebook/contrib/crypto.ipynb @@ -0,0 +1,148 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010 Hakan Kjellerstrand hakank@gmail.com\n", + "#\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"\n", + "\n", + " Crypto problem in Google CP Solver.\n", + "\n", + " Prolog benchmark problem GNU Prolog (crypta.pl)\n", + " '''\n", + " Name : crypta.pl\n", + " Title : crypt-arithmetic\n", + " Original Source: P. Van Hentenryck's book\n", + " Adapted by : Daniel Diaz - INRIA France\n", + " Date : September 1992\n", + "\n", + " Solve the operation:\n", + "\n", + " B A I J J A J I I A H F C F E B B J E A\n", + " + D H F G A B C D I D B I F F A G F E J E\n", + " -----------------------------------------\n", + " = G J E G A C D D H F A F J B F I H E E F\n", + " '''\n", + "\n", + " Compare with the following models:\n", + " * MiniZinc: http://www.hakank.org/minizinc/crypta.mzn\n", + " * Comet : http://www.hakank.org/comet/crypta.co\n", + " * ECLiPSe : http://www.hakank.org/eclipse/crypta.ecl\n", + " * SICStus : http://hakank.org/sicstus/crypta.pl\n", + "\n", + "\n", + " This model was created by Hakan Kjellerstrand (hakank@gmail.com)\n", + " Also see my other Google CP Solver models:\n", + " http://www.hakank.org/google_or_tools/\n", + "\"\"\"\n", + "from __future__ import print_function\n", + "from ortools.constraint_solver import pywrapcp\n", + "\n", + "\n", + "\n", + "# Create the solver.\n", + "solver = pywrapcp.Solver(\"Crypto problem\")\n", + "\n", + "#\n", + "# data\n", + "#\n", + "num_letters = 26\n", + "\n", + "BALLET = 45\n", + "CELLO = 43\n", + "CONCERT = 74\n", + "FLUTE = 30\n", + "FUGUE = 50\n", + "GLEE = 66\n", + "JAZZ = 58\n", + "LYRE = 47\n", + "OBOE = 53\n", + "OPERA = 65\n", + "POLKA = 59\n", + "QUARTET = 50\n", + "SAXOPHONE = 134\n", + "SCALE = 51\n", + "SOLO = 37\n", + "SONG = 61\n", + "SOPRANO = 82\n", + "THEME = 72\n", + "VIOLIN = 100\n", + "WALTZ = 34\n", + "\n", + "#\n", + "# variables\n", + "#\n", + "LD = [solver.IntVar(1, num_letters, \"LD[%i]\" % i) for i in range(num_letters)]\n", + "A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z = LD\n", + "\n", + "#\n", + "# constraints\n", + "#\n", + "solver.Add(solver.AllDifferent(LD))\n", + "solver.Add(B + A + L + L + E + T == BALLET)\n", + "solver.Add(C + E + L + L + O == CELLO)\n", + "solver.Add(C + O + N + C + E + R + T == CONCERT)\n", + "solver.Add(F + L + U + T + E == FLUTE)\n", + "solver.Add(F + U + G + U + E == FUGUE)\n", + "solver.Add(G + L + E + E == GLEE)\n", + "solver.Add(J + A + Z + Z == JAZZ)\n", + "solver.Add(L + Y + R + E == LYRE)\n", + "solver.Add(O + B + O + E == OBOE)\n", + "solver.Add(O + P + E + R + A == OPERA)\n", + "solver.Add(P + O + L + K + A == POLKA)\n", + "solver.Add(Q + U + A + R + T + E + T == QUARTET)\n", + "solver.Add(S + A + X + O + P + H + O + N + E == SAXOPHONE)\n", + "solver.Add(S + C + A + L + E == SCALE)\n", + "solver.Add(S + O + L + O == SOLO)\n", + "solver.Add(S + O + N + G == SONG)\n", + "solver.Add(S + O + P + R + A + N + O == SOPRANO)\n", + "solver.Add(T + H + E + M + E == THEME)\n", + "solver.Add(V + I + O + L + I + N == VIOLIN)\n", + "solver.Add(W + A + L + T + Z == WALTZ)\n", + "\n", + "#\n", + "# search and result\n", + "#\n", + "db = solver.Phase(LD, solver.CHOOSE_MIN_SIZE_LOWEST_MIN,\n", + " solver.ASSIGN_CENTER_VALUE)\n", + "\n", + "solver.NewSearch(db)\n", + "\n", + "num_solutions = 0\n", + "str = \"ABCDEFGHIJKLMNOPQRSTUVWXYZ\"\n", + "while solver.NextSolution():\n", + " num_solutions += 1\n", + " for (letter, val) in [(str[i], LD[i].Value()) for i in range(num_letters)]:\n", + " print(\"%s: %i\" % (letter, val))\n", + " print()\n", + "\n", + "solver.EndSearch()\n", + "\n", + "print()\n", + "print(\"num_solutions:\", num_solutions)\n", + "print(\"failures:\", solver.Failures())\n", + "print(\"branches:\", solver.Branches())\n", + "print(\"WallTime:\", solver.WallTime())\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/contrib/curious_set_of_integers.ipynb b/examples/notebook/contrib/curious_set_of_integers.ipynb new file mode 100644 index 0000000000..1a5fb5b097 --- /dev/null +++ b/examples/notebook/contrib/curious_set_of_integers.ipynb @@ -0,0 +1,138 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010 Hakan Kjellerstrand hakank@gmail.com\n", + "#\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"\n", + "\n", + " Crypto problem in Google CP Solver.\n", + "\n", + " Martin Gardner (February 1967):\n", + " '''\n", + " The integers 1,3,8, and 120 form a set with a remarkable property: the\n", + " product of any two integers is one less than a perfect square. Find\n", + " a fifth number that can be added to the set without destroying\n", + " this property.\n", + " '''\n", + "\n", + " Solution: The number is 0.\n", + "\n", + " There are however other sets of five numbers with this property.\n", + " Here are the one in the range of 0.10000:\n", + " [0, 1, 3, 8, 120]\n", + " [0, 1, 3, 120, 1680]\n", + " [0, 1, 8, 15, 528]\n", + " [0, 1, 8, 120, 4095]\n", + " [0, 1, 15, 24, 1520]\n", + " [0, 1, 24, 35, 3480]\n", + " [0, 1, 35, 48, 6888]\n", + " [0, 2, 4, 12, 420]\n", + " [0, 2, 12, 24, 2380]\n", + " [0, 2, 24, 40, 7812]\n", + " [0, 3, 5, 16, 1008]\n", + " [0, 3, 8, 21, 2080]\n", + " [0, 3, 16, 33, 6440]\n", + " [0, 4, 6, 20, 1980]\n", + " [0, 4, 12, 30, 5852]\n", + " [0, 5, 7, 24, 3432]\n", + " [0, 6, 8, 28, 5460]\n", + " [0, 7, 9, 32, 8160]\n", + "\n", + "\n", + " Compare with the following models:\n", + " * MiniZinc: http://www.hakank.org/minizinc/crypta.mzn\n", + " * Comet : http://www.hakank.org/comet/crypta.co\n", + " * ECLiPSe : http://www.hakank.org/eclipse/crypta.ecl\n", + " * SICStus : http://hakank.org/sicstus/crypta.pl\n", + "\n", + "\n", + " This model was created by Hakan Kjellerstrand (hakank@gmail.com)\n", + " Also see my other Google CP Solver models:\n", + " http://www.hakank.org/google_or_tools/\n", + "\"\"\"\n", + "from __future__ import print_function\n", + "from ortools.constraint_solver import pywrapcp\n", + "\n", + "\n", + "def decreasing(solver, x):\n", + " for i in range(len(x) - 1):\n", + " solver.Add(x[i] <= x[i + 1])\n", + "\n", + "\n", + "\n", + "# Create the solver.\n", + "solver = pywrapcp.Solver(\"Curious set of integers\")\n", + "\n", + "#\n", + "# data\n", + "#\n", + "n = 5\n", + "max_val = 10000\n", + "\n", + "#\n", + "# variables\n", + "#\n", + "x = [solver.IntVar(0, max_val, \"x[%i]\" % i) for i in range(n)]\n", + "\n", + "#\n", + "# constraints\n", + "#\n", + "solver.Add(solver.AllDifferent(x))\n", + "decreasing(solver, x)\n", + "\n", + "for i in range(n):\n", + " for j in range(n):\n", + " if i != j:\n", + " p = solver.IntVar(0, max_val, \"p[%i,%i]\" % (i, j))\n", + " solver.Add(p * p - 1 == (x[i] * x[j]))\n", + "\n", + "# This is the original problem:\n", + "# Which is the fifth number?\n", + "v = [1, 3, 8, 120]\n", + "b = [solver.IsMemberVar(x[i], v) for i in range(n)]\n", + "solver.Add(solver.Sum(b) == 4)\n", + "\n", + "#\n", + "# search and result\n", + "#\n", + "db = solver.Phase(x, solver.CHOOSE_MIN_SIZE_LOWEST_MIN,\n", + " solver.ASSIGN_MIN_VALUE)\n", + "\n", + "solver.NewSearch(db)\n", + "\n", + "num_solutions = 0\n", + "while solver.NextSolution():\n", + " num_solutions += 1\n", + " print(\"x:\", [int(x[i].Value()) for i in range(n)])\n", + "\n", + "solver.EndSearch()\n", + "\n", + "print()\n", + "print(\"num_solutions:\", num_solutions)\n", + "print(\"failures:\", solver.Failures())\n", + "print(\"branches:\", solver.Branches())\n", + "print(\"WallTime:\", solver.WallTime())\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/contrib/debruijn_binary.ipynb b/examples/notebook/contrib/debruijn_binary.ipynb new file mode 100644 index 0000000000..e8fcc5a495 --- /dev/null +++ b/examples/notebook/contrib/debruijn_binary.ipynb @@ -0,0 +1,199 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010 Hakan Kjellerstrand hakank@gmail.com\n", + "#\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"\n", + "\n", + " de Bruijn sequences in Google CP Solver.\n", + "\n", + " Implementation of de Bruijn sequences in Minizinc, both 'classical' and\n", + " 'arbitrary'.\n", + " The 'arbitrary' version is when the length of the sequence (m here) is <\n", + " base**n.\n", + "\n", + "\n", + " Compare with the the web based programs:\n", + " http://www.hakank.org/comb/debruijn.cgi\n", + " http://www.hakank.org/comb/debruijn_arb.cgi\n", + "\n", + " Compare with the following models:\n", + " * Tailor/Essence': http://hakank.org/tailor/debruijn.eprime\n", + " * MiniZinc: http://hakank.org/minizinc/debruijn_binary.mzn\n", + " * SICStus: http://hakank.org/sicstus/debruijn.pl\n", + " * Zinc: http://hakank.org/minizinc/debruijn_binary.zinc\n", + " * Choco: http://hakank.org/choco/DeBruijn.java\n", + " * Comet: http://hakank.org/comet/debruijn.co\n", + " * ECLiPSe: http://hakank.org/eclipse/debruijn.ecl\n", + " * Gecode: http://hakank.org/gecode/debruijn.cpp\n", + " * Gecode/R: http://hakank.org/gecode_r/debruijn_binary.rb\n", + " * JaCoP: http://hakank.org/JaCoP/DeBruijn.java\n", + "\n", + " This model was created by Hakan Kjellerstrand (hakank@gmail.com)\n", + " Also see my other Google CP Solver models:\n", + " http://www.hakank.org/google_or_tools/\n", + "\"\"\"\n", + "from __future__ import print_function\n", + "import sys\n", + "from ortools.constraint_solver import pywrapcp\n", + "\n", + "# converts a number (s) <-> an array of numbers (t) in the specific base.\n", + "\n", + "\n", + "def toNum(solver, t, s, base):\n", + " tlen = len(t)\n", + " solver.Add(\n", + " s == solver.Sum([(base**(tlen - i - 1)) * t[i] for i in range(tlen)]))\n", + "\n", + "\n", + "# Create the solver.\n", + "solver = pywrapcp.Solver(\"de Bruijn sequences\")\n", + "\n", + "#\n", + "# data\n", + "#\n", + "# base = 2 # the base to use, i.e. the alphabet 0..n-1\n", + "# n = 3 # number of bits to use (n = 4 -> 0..base^n-1 = 0..2^4 -1, i.e. 0..15)\n", + "# m = base**n # the length of the sequence. For \"arbitrary\" de Bruijn\n", + "# sequences\n", + "\n", + "# base = 4\n", + "# n = 4\n", + "# m = base**n\n", + "\n", + "# harder problem\n", + "#base = 13\n", + "#n = 4\n", + "#m = 52\n", + "\n", + "# for n = 4 with different value of base\n", + "# base = 2 0.030 seconds 16 failures\n", + "# base = 3 0.041 108\n", + "# base = 4 0.070 384\n", + "# base = 5 0.231 1000\n", + "# base = 6 0.736 2160\n", + "# base = 7 2.2 seconds 4116\n", + "# base = 8 6 seconds 7168\n", + "# base = 9 16 seconds 11664\n", + "# base = 10 42 seconds 18000\n", + "# base = 6\n", + "# n = 4\n", + "# m = base**n\n", + "\n", + "# if True then ensure that the number of occurrences of 0..base-1 is\n", + "# the same (and if m mod base = 0)\n", + "check_same_gcc = True\n", + "\n", + "print(\"base: %i n: %i m: %i\" % (base, n, m))\n", + "if check_same_gcc:\n", + " print(\"Checks gcc\")\n", + "\n", + "# declare variables\n", + "x = [solver.IntVar(0, (base**n) - 1, \"x%i\" % i) for i in range(m)]\n", + "binary = {}\n", + "for i in range(m):\n", + " for j in range(n):\n", + " binary[(i, j)] = solver.IntVar(0, base - 1, \"x_%i_%i\" % (i, j))\n", + "\n", + "bin_code = [solver.IntVar(0, base - 1, \"bin_code%i\" % i) for i in range(m)]\n", + "\n", + "#\n", + "# constraints\n", + "#\n", + "#solver.Add(solver.AllDifferent([x[i] for i in range(m)]))\n", + "solver.Add(solver.AllDifferent(x))\n", + "\n", + "# converts x <-> binary\n", + "for i in range(m):\n", + " t = [solver.IntVar(0, base - 1, \"t_%i\" % j) for j in range(n)]\n", + " toNum(solver, t, x[i], base)\n", + " for j in range(n):\n", + " solver.Add(binary[(i, j)] == t[j])\n", + "\n", + "# the de Bruijn condition\n", + "# the first elements in binary[i] is the same as the last\n", + "# elements in binary[i-i]\n", + "for i in range(1, m - 1):\n", + " for j in range(1, n - 1):\n", + " solver.Add(binary[(i - 1, j)] == binary[(i, j - 1)])\n", + "\n", + "# ... and around the corner\n", + "for j in range(1, n):\n", + " solver.Add(binary[(m - 1, j)] == binary[(0, j - 1)])\n", + "\n", + "# converts binary -> bin_code\n", + "for i in range(m):\n", + " solver.Add(bin_code[i] == binary[(i, 0)])\n", + "\n", + "# extra: ensure that all the numbers in the de Bruijn sequence\n", + "# (bin_code) has the same occurrences (if check_same_gcc is True\n", + "# and mathematically possible)\n", + "gcc = [solver.IntVar(0, m, \"gcc%i\" % i) for i in range(base)]\n", + "solver.Add(solver.Distribute(bin_code, list(range(base)), gcc))\n", + "if check_same_gcc and m % base == 0:\n", + " for i in range(1, base):\n", + " solver.Add(gcc[i] == gcc[i - 1])\n", + "\n", + "#\n", + "# solution and search\n", + "#\n", + "solution = solver.Assignment()\n", + "solution.Add([x[i] for i in range(m)])\n", + "solution.Add([bin_code[i] for i in range(m)])\n", + "# solution.Add([binary[(i,j)] for i in range(m) for j in range(n)])\n", + "solution.Add([gcc[i] for i in range(base)])\n", + "\n", + "db = solver.Phase([x[i] for i in range(m)] + [bin_code[i] for i in range(m)],\n", + " solver.CHOOSE_MIN_SIZE_LOWEST_MAX, solver.ASSIGN_MIN_VALUE)\n", + "\n", + "num_solutions = 0\n", + "solver.NewSearch(db)\n", + "num_solutions = 0\n", + "while solver.NextSolution():\n", + " num_solutions += 1\n", + " print(\"\\nSolution %i\" % num_solutions)\n", + " print(\"x:\", [int(x[i].Value()) for i in range(m)])\n", + " print(\"gcc:\", [int(gcc[i].Value()) for i in range(base)])\n", + " print(\"de Bruijn sequence:\", [int(bin_code[i].Value()) for i in range(m)])\n", + " # for i in range(m):\n", + " # for j in range(n):\n", + " # print binary[(i,j)].Value(),\n", + " # print\n", + " # print\n", + "solver.EndSearch()\n", + "\n", + "if num_solutions == 0:\n", + " print(\"No solution found\")\n", + "\n", + "print()\n", + "print(\"num_solutions:\", num_solutions)\n", + "print(\"failures:\", solver.Failures())\n", + "print(\"branches:\", solver.Branches())\n", + "print(\"WallTime:\", solver.WallTime())\n", + "\n", + "base = 2\n", + "n = 3\n", + "m = base**n\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/contrib/diet1.ipynb b/examples/notebook/contrib/diet1.ipynb new file mode 100644 index 0000000000..b9e2ccfa68 --- /dev/null +++ b/examples/notebook/contrib/diet1.ipynb @@ -0,0 +1,112 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010 Hakan Kjellerstrand hakank@gmail.com\n", + "#\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"\n", + "\n", + " Simple diet problem in Google CP Solver.\n", + "\n", + " Standard Operations Research example in Minizinc\n", + "\n", + "\n", + " Minimize the cost for the products:\n", + " Type of Calories Chocolate Sugar Fat\n", + " Food (ounces) (ounces) (ounces)\n", + " Chocolate Cake (1 slice) 400 3 2 2\n", + " Chocolate ice cream (1 scoop) 200 2 2 4\n", + " Cola (1 bottle) 150 0 4 1\n", + " Pineapple cheesecake (1 piece) 500 0 4 5\n", + "\n", + " Compare with the following models:\n", + " * Tailor/Essence': http://hakank.org/tailor/diet1.eprime\n", + " * MiniZinc: http://hakank.org/minizinc/diet1.mzn\n", + " * SICStus: http://hakank.org/sicstus/diet1.pl\n", + " * Zinc: http://hakank.org/minizinc/diet1.zinc\n", + " * Choco: http://hakank.org/choco/Diet.java\n", + " * Comet: http://hakank.org/comet/diet.co\n", + " * ECLiPSe: http://hakank.org/eclipse/diet.ecl\n", + " * Gecode: http://hakank.org/gecode/diet.cpp\n", + " * Gecode/R: http://hakank.org/gecode_r/diet.rb\n", + " * JaCoP: http://hakank.org/JaCoP/Diet.java\n", + "\n", + "\n", + " This model was created by Hakan Kjellerstrand (hakank@gmail.com)\n", + " Also see my other Google CP Solver models:\n", + " http://www.hakank.org/google_or_tools/\n", + "\"\"\"\n", + "from __future__ import print_function\n", + "from ortools.sat.python import cp_model\n", + "\n", + "\n", + "# Create the solver.\n", + "model = cp_model.CpModel()\n", + "\n", + "#\n", + "# data\n", + "#\n", + "n = 4\n", + "price = [50, 20, 30, 80] # in cents\n", + "limits = [500, 6, 10, 8] # requirements for each nutrition type\n", + "\n", + "# nutritions for each product\n", + "calories = [400, 200, 150, 500]\n", + "chocolate = [3, 2, 0, 0]\n", + "sugar = [2, 2, 4, 4]\n", + "fat = [2, 4, 1, 5]\n", + "\n", + "#\n", + "# declare variables\n", + "#\n", + "x = [model.NewIntVar(0, 100, \"x%d\" % i) for i in range(n)]\n", + "cost = model.NewIntVar(0, 10000, \"cost\")\n", + "\n", + "#\n", + "# constraints\n", + "#\n", + "model.Add(sum(x[i] * calories[i] for i in range(n)) >= limits[0])\n", + "model.Add(sum(x[i] * chocolate[i] for i in range(n)) >= limits[1])\n", + "model.Add(sum(x[i] * sugar[i] for i in range(n)) >= limits[2])\n", + "model.Add(sum(x[i] * fat[i] for i in range(n)) >= limits[3])\n", + "\n", + "# objective\n", + "model.Minimize(cost)\n", + "\n", + "# Solve model.\n", + "solver = cp_model.CpSolver()\n", + "status = solver.Solve(model)\n", + "\n", + "# Output solution.\n", + "if status == cp_model.OPTIMAL:\n", + " print(\"cost:\", solver.ObjectiveValue())\n", + " print([(\"abcdefghij\" [i], solver.Value(x[i])) for i in range(n)])\n", + " print()\n", + " print(' - status : %s' % solver.StatusName(status))\n", + " print(' - conflicts : %i' % solver.NumConflicts())\n", + " print(' - branches : %i' % solver.NumBranches())\n", + " print(' - wall time : %f ms' % solver.WallTime())\n", + " print()\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/contrib/diet1_b.ipynb b/examples/notebook/contrib/diet1_b.ipynb new file mode 100644 index 0000000000..ac1514ebaf --- /dev/null +++ b/examples/notebook/contrib/diet1_b.ipynb @@ -0,0 +1,122 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010 Hakan Kjellerstrand hakank@gmail.com\n", + "#\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"\n", + "\n", + " Simple diet problem in Google CP Solver.\n", + "\n", + " Standard Operations Research example in Minizinc\n", + "\n", + "\n", + " Minimize the cost for the products:\n", + " Type of Calories Chocolate Sugar Fat\n", + " Food (ounces) (ounces) (ounces)\n", + " Chocolate Cake (1 slice) 400 3 2 2\n", + " Chocolate ice cream (1 scoop) 200 2 2 4\n", + " Cola (1 bottle) 150 0 4 1\n", + " Pineapple cheesecake (1 piece) 500 0 4 5\n", + "\n", + " Compare with the following models:\n", + " * Tailor/Essence': http://hakank.org/tailor/diet1.eprime\n", + " * MiniZinc: http://hakank.org/minizinc/diet1.mzn\n", + " * SICStus: http://hakank.org/sicstus/diet1.pl\n", + " * Zinc: http://hakank.org/minizinc/diet1.zinc\n", + " * Choco: http://hakank.org/choco/Diet.java\n", + " * Comet: http://hakank.org/comet/diet.co\n", + " * ECLiPSe: http://hakank.org/eclipse/diet.ecl\n", + " * Gecode: http://hakank.org/gecode/diet.cpp\n", + " * Gecode/R: http://hakank.org/gecode_r/diet.rb\n", + " * JaCoP: http://hakank.org/JaCoP/Diet.java\n", + "\n", + " This version use ScalProd() instead of Sum().\n", + "\n", + "\n", + " This model was created by Hakan Kjellerstrand (hakank@gmail.com)\n", + " Also see my other Google CP Solver models:\n", + " http://www.hakank.org/google_or_tools/\n", + "\"\"\"\n", + "from __future__ import print_function\n", + "from ortools.constraint_solver import pywrapcp\n", + "\n", + "\n", + "# Create the solver.\n", + "solver = pywrapcp.Solver(\"Diet\")\n", + "\n", + "#\n", + "# data\n", + "#\n", + "n = 4\n", + "price = [50, 20, 30, 80] # in cents\n", + "limits = [500, 6, 10, 8] # requirements for each nutrition type\n", + "\n", + "# nutritions for each product\n", + "calories = [400, 200, 150, 500]\n", + "chocolate = [3, 2, 0, 0]\n", + "sugar = [2, 2, 4, 4]\n", + "fat = [2, 4, 1, 5]\n", + "\n", + "#\n", + "# declare variables\n", + "#\n", + "x = [solver.IntVar(0, 100, \"x%d\" % i) for i in range(n)]\n", + "cost = solver.IntVar(0, 10000, \"cost\")\n", + "\n", + "#\n", + "# constraints\n", + "#\n", + "solver.Add(solver.ScalProd(x, calories) >= limits[0])\n", + "solver.Add(solver.ScalProd(x, chocolate) >= limits[1])\n", + "solver.Add(solver.ScalProd(x, sugar) >= limits[2])\n", + "solver.Add(solver.ScalProd(x, fat) >= limits[3])\n", + "\n", + "# objective\n", + "objective = solver.Minimize(cost, 1)\n", + "\n", + "#\n", + "# solution\n", + "#\n", + "solution = solver.Assignment()\n", + "solution.AddObjective(cost)\n", + "solution.Add(x)\n", + "\n", + "# last solution since it's a minimization problem\n", + "collector = solver.LastSolutionCollector(solution)\n", + "search_log = solver.SearchLog(100, cost)\n", + "solver.Solve(\n", + " solver.Phase(x + [cost], solver.INT_VAR_SIMPLE, solver.ASSIGN_MIN_VALUE),\n", + " [objective, search_log, collector])\n", + "\n", + "# get the first (and only) solution\n", + "print(\"cost:\", collector.ObjectiveValue(0))\n", + "print([(\"abcdefghij\" [i], collector.Value(0, x[i])) for i in range(n)])\n", + "print()\n", + "print(\"failures:\", solver.Failures())\n", + "print(\"branches:\", solver.Branches())\n", + "print(\"WallTime:\", solver.WallTime())\n", + "print()\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/contrib/diet1_mip.ipynb b/examples/notebook/contrib/diet1_mip.ipynb new file mode 100644 index 0000000000..4c32a66e40 --- /dev/null +++ b/examples/notebook/contrib/diet1_mip.ipynb @@ -0,0 +1,113 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010 Hakan Kjellerstrand hakank@gmail.com\n", + "#\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"\n", + "\n", + " Simple diet problem using MIP in Google CP Solver.\n", + "\n", + " Standard Operations Research example.\n", + "\n", + "\n", + " Minimize the cost for the products:\n", + " Type of Calories Chocolate Sugar Fat\n", + " Food (ounces) (ounces) (ounces)\n", + " Chocolate Cake (1 slice) 400 3 2 2\n", + " Chocolate ice cream (1 scoop) 200 2 2 4\n", + " Cola (1 bottle) 150 0 4 1\n", + " Pineapple cheesecake (1 piece) 500 0 4 5\n", + "\n", + " Compare with the CP model:\n", + " http://www.hakank.org/google_or_tools/diet1.py\n", + "\n", + "\n", + " This model was created by Hakan Kjellerstrand (hakank@gmail.com)\n", + " Also see my other Google CP Solver models:\n", + " http://www.hakank.org/google_or_tools/\n", + "\"\"\"\n", + "from __future__ import print_function\n", + "import sys\n", + "from ortools.linear_solver import pywraplp\n", + "\n", + "\n", + "\n", + "# Create the solver.\n", + "\n", + "print('Solver: ', sol)\n", + "\n", + "if sol == 'GLPK':\n", + " # using GLPK\n", + " solver = pywraplp.Solver('CoinsGridGLPK',\n", + " pywraplp.Solver.GLPK_MIXED_INTEGER_PROGRAMMING)\n", + "else:\n", + " # Using CBC\n", + " solver = pywraplp.Solver('CoinsGridCLP',\n", + " pywraplp.Solver.CBC_MIXED_INTEGER_PROGRAMMING)\n", + "\n", + "#\n", + "# data\n", + "#\n", + "n = 4\n", + "price = [50, 20, 30, 80] # in cents\n", + "limits = [500, 6, 10, 8] # requirements for each nutrition type\n", + "\n", + "# nutritions for each product\n", + "calories = [400, 200, 150, 500]\n", + "chocolate = [3, 2, 0, 0]\n", + "sugar = [2, 2, 4, 4]\n", + "fat = [2, 4, 1, 5]\n", + "\n", + "#\n", + "# declare variables\n", + "#\n", + "x = [solver.IntVar(0, 100, 'x%d' % i) for i in range(n)]\n", + "cost = solver.Sum([x[i] * price[i] for i in range(n)])\n", + "\n", + "#\n", + "# constraints\n", + "#\n", + "solver.Add(solver.Sum([x[i] * calories[i] for i in range(n)]) >= limits[0])\n", + "solver.Add(solver.Sum([x[i] * chocolate[i] for i in range(n)]) >= limits[1])\n", + "solver.Add(solver.Sum([x[i] * sugar[i] for i in range(n)]) >= limits[2])\n", + "solver.Add(solver.Sum([x[i] * fat[i] for i in range(n)]) >= limits[3])\n", + "\n", + "# objective\n", + "objective = solver.Minimize(cost)\n", + "\n", + "#\n", + "# solution\n", + "#\n", + "solver.Solve()\n", + "\n", + "print('Cost:', solver.Objective().Value())\n", + "print([int(x[i].SolutionValue()) for i in range(n)])\n", + "\n", + "print()\n", + "print('WallTime:', solver.WallTime())\n", + "if sol == 'CBC':\n", + " print('iterations:', solver.Iterations())\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/contrib/discrete_tomography.ipynb b/examples/notebook/contrib/discrete_tomography.ipynb new file mode 100644 index 0000000000..8120e73517 --- /dev/null +++ b/examples/notebook/contrib/discrete_tomography.ipynb @@ -0,0 +1,170 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010 Hakan Kjellerstrand hakank@gmail.com\n", + "#\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"\n", + "\n", + " Discrete tomography in Google CP Solver.\n", + "\n", + " Problem from http://eclipse.crosscoreop.com/examples/tomo.ecl.txt\n", + " '''\n", + " This is a little 'tomography' problem, taken from an old issue\n", + " of Scientific American.\n", + "\n", + " A matrix which contains zeroes and ones gets \"x-rayed\" vertically and\n", + " horizontally, giving the total number of ones in each row and column.\n", + " The problem is to reconstruct the contents of the matrix from this\n", + " information. Sample run:\n", + "\n", + " ?- go.\n", + " 0 0 7 1 6 3 4 5 2 7 0 0\n", + " 0\n", + " 0\n", + " 8 * * * * * * * *\n", + " 2 * *\n", + " 6 * * * * * *\n", + " 4 * * * *\n", + " 5 * * * * *\n", + " 3 * * *\n", + " 7 * * * * * * *\n", + " 0\n", + " 0\n", + "\n", + " Eclipse solution by Joachim Schimpf, IC-Parc\n", + " '''\n", + "\n", + " Compare with the following models:\n", + " * Comet: http://www.hakank.org/comet/discrete_tomography.co\n", + " * Gecode: http://www.hakank.org/gecode/discrete_tomography.cpp\n", + " * MiniZinc: http://www.hakank.org/minizinc/tomography.mzn\n", + " * Tailor/Essence': http://www.hakank.org/tailor/tomography.eprime\n", + " * SICStus: http://hakank.org/sicstus/discrete_tomography.pl\n", + "\n", + " This model was created by Hakan Kjellerstrand (hakank@gmail.com)\n", + " Also see my other Google CP Solver models:\n", + " http://www.hakank.org/google_or_tools/\n", + "\"\"\"\n", + "from __future__ import print_function\n", + "import sys\n", + "from ortools.constraint_solver import pywrapcp\n", + "\n", + "\n", + "\n", + "# Create the solver.\n", + "solver = pywrapcp.Solver(\"n-queens\")\n", + "\n", + "#\n", + "# data\n", + "#\n", + "if row_sums == \"\":\n", + " print(\"Using default problem instance\")\n", + " row_sums = [0, 0, 8, 2, 6, 4, 5, 3, 7, 0, 0]\n", + " col_sums = [0, 0, 7, 1, 6, 3, 4, 5, 2, 7, 0, 0]\n", + "\n", + "r = len(row_sums)\n", + "c = len(col_sums)\n", + "\n", + "# declare variables\n", + "x = []\n", + "for i in range(r):\n", + " t = []\n", + " for j in range(c):\n", + " t.append(solver.IntVar(0, 1, \"x[%i,%i]\" % (i, j)))\n", + " x.append(t)\n", + "x_flat = [x[i][j] for i in range(r) for j in range(c)]\n", + "\n", + "#\n", + "# constraints\n", + "#\n", + "[\n", + " solver.Add(solver.Sum([x[i][j]\n", + " for j in range(c)]) == row_sums[i])\n", + " for i in range(r)\n", + "]\n", + "[\n", + " solver.Add(solver.Sum([x[i][j]\n", + " for i in range(r)]) == col_sums[j])\n", + " for j in range(c)\n", + "]\n", + "\n", + "#\n", + "# solution and search\n", + "#\n", + "solution = solver.Assignment()\n", + "solution.Add(x_flat)\n", + "\n", + "# db: DecisionBuilder\n", + "db = solver.Phase(x_flat, solver.INT_VAR_SIMPLE, solver.ASSIGN_MIN_VALUE)\n", + "\n", + "solver.NewSearch(db)\n", + "num_solutions = 0\n", + "while solver.NextSolution():\n", + " print_solution(x, r, c, row_sums, col_sums)\n", + " print()\n", + "\n", + " num_solutions += 1\n", + "solver.EndSearch()\n", + "\n", + "print()\n", + "print(\"num_solutions:\", num_solutions)\n", + "print(\"failures:\", solver.Failures())\n", + "print(\"branches:\", solver.Branches())\n", + "print(\"WallTime:\", solver.WallTime())\n", + "\n", + "\n", + "#\n", + "# Print solution\n", + "#\n", + "\n", + "def print_solution(x, rows, cols, row_sums, col_sums):\n", + " print(\" \", end=\" \")\n", + " for j in range(cols):\n", + " print(col_sums[j], end=\" \")\n", + " print()\n", + " for i in range(rows):\n", + " print(row_sums[i], end=\" \")\n", + " for j in range(cols):\n", + " if x[i][j].Value() == 1:\n", + " print(\"#\", end=\" \")\n", + " else:\n", + " print(\".\", end=\" \")\n", + " print(\"\")\n", + "\n", + "\n", + "#\n", + "# Read a problem instance from a file\n", + "#\n", + "def read_problem(file):\n", + " f = open(file, \"r\")\n", + " row_sums = f.readline()\n", + " col_sums = f.readline()\n", + " row_sums = [int(r) for r in (row_sums.rstrip()).split(\",\")]\n", + " col_sums = [int(c) for c in (col_sums.rstrip()).split(\",\")]\n", + "\n", + " return [row_sums, col_sums]\n", + "\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/contrib/divisible_by_9_through_1.ipynb b/examples/notebook/contrib/divisible_by_9_through_1.ipynb new file mode 100644 index 0000000000..27363b0158 --- /dev/null +++ b/examples/notebook/contrib/divisible_by_9_through_1.ipynb @@ -0,0 +1,179 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010 Hakan Kjellerstrand hakank@gmail.com\n", + "#\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"\n", + "\n", + " Divisible by 9 through 1 puzzle in Google CP Solver.\n", + "\n", + " From http://msdn.microsoft.com/en-us/vcsharp/ee957404.aspx\n", + " ' Solving Combinatory Problems with LINQ'\n", + " '''\n", + " Find a number consisting of 9 digits in which each of the digits\n", + " from 1 to 9 appears only once. This number must also satisfy these\n", + " divisibility requirements:\n", + "\n", + " 1. The number should be divisible by 9.\n", + " 2. If the rightmost digit is removed, the remaining number should\n", + " be divisible by 8.\n", + " 3. If the rightmost digit of the new number is removed, the remaining\n", + " number should be divisible by 7.\n", + " 4. And so on, until there's only one digit (which will necessarily\n", + " be divisible by 1).\n", + " '''\n", + "\n", + " Also, see\n", + " 'Intel Parallel Studio: Great for Serial Code Too (Episode 1)'\n", + " http://software.intel.com/en-us/blogs/2009/12/07/intel-parallel-studio-great-for-serial-code-too-episode-1/\n", + "\n", + "\n", + " This model is however generalized to handle any base, for reasonable limits.\n", + " The 'reasonable limit' for this model is that base must be between 2..16.\n", + "\n", + " Compare with the following models:\n", + " * MiniZinc: http://www.hakank.org/minizinc/divisible_by_9_through_1.mzn\n", + " * Comet : http://www.hakank.org/comet/divisible_by_9_through_1.co\n", + " * ECLiPSe : http://www.hakank.org/eclipse/divisible_by_9_through_1.ecl\n", + " * Gecode : http://www.hakank.org/gecode/divisible_by_9_through_1.cpp\n", + "\n", + "\n", + " This model was created by Hakan Kjellerstrand (hakank@gmail.com)\n", + " Also see my other Google CP Solver models:\n", + " http://www.hakank.org/google_or_tools/\n", + "\n", + "\"\"\"\n", + "from __future__ import print_function\n", + "import sys\n", + "\n", + "from ortools.constraint_solver import pywrapcp\n", + "\n", + "#\n", + "# Decomposition of modulo constraint\n", + "#\n", + "# This implementation is based on the ECLiPSe version\n", + "# mentioned in\n", + "# - A Modulo propagator for ECLiPSE'\n", + "# http://www.hakank.org/constraint_programming_blog/2010/05/a_modulo_propagator_for_eclips.html\n", + "# The ECLiPSe source code:\n", + "# http://www.hakank.org/eclipse/modulo_propagator.ecl\n", + "#\n", + "\n", + "\n", + "def my_mod(solver, x, y, r):\n", + "\n", + " if not isinstance(y, int):\n", + " solver.Add(y != 0)\n", + "\n", + " lbx = x.Min()\n", + " ubx = x.Max()\n", + " ubx_neg = -ubx\n", + " lbx_neg = -lbx\n", + " min_x = min(lbx, ubx_neg)\n", + " max_x = max(ubx, lbx_neg)\n", + "\n", + " d = solver.IntVar(max(0, min_x), max_x, \"d\")\n", + "\n", + " if not isinstance(r, int):\n", + " solver.Add(r >= 0)\n", + " solver.Add(x * r >= 0)\n", + "\n", + " if not isinstance(r, int) and not isinstance(r, int):\n", + " solver.Add(-abs(y) < r)\n", + " solver.Add(r < abs(y))\n", + "\n", + " solver.Add(min_x <= d)\n", + " solver.Add(d <= max_x)\n", + " solver.Add(x == y * d + r)\n", + "\n", + "\n", + "#\n", + "# converts a number (s) <-> an array of integers (t) in the specific base.\n", + "#\n", + "def toNum(solver, t, s, base):\n", + " tlen = len(t)\n", + " solver.Add(\n", + " s == solver.Sum([(base**(tlen - i - 1)) * t[i] for i in range(tlen)]))\n", + "\n", + "\n", + "\n", + "# Create the solver.\n", + "solver = pywrapcp.Solver(\"Divisible by 9 through 1\")\n", + "\n", + "# data\n", + "m = base**(base - 1) - 1\n", + "n = base - 1\n", + "\n", + "digits_str = \"_0123456789ABCDEFGH\"\n", + "\n", + "print(\"base:\", base)\n", + "\n", + "# declare variables\n", + "\n", + "# the digits\n", + "x = [solver.IntVar(1, base - 1, \"x[%i]\" % i) for i in range(n)]\n", + "\n", + "# the numbers, t[0] contains the answer\n", + "t = [solver.IntVar(0, m, \"t[%i]\" % i) for i in range(n)]\n", + "\n", + "#\n", + "# constraints\n", + "#\n", + "solver.Add(solver.AllDifferent(x))\n", + "\n", + "for i in range(n):\n", + " mm = base - i - 1\n", + " toNum(solver, [x[j] for j in range(mm)], t[i], base)\n", + " my_mod(solver, t[i], mm, 0)\n", + "\n", + "#\n", + "# solution and search\n", + "#\n", + "solution = solver.Assignment()\n", + "solution.Add(x)\n", + "solution.Add(t)\n", + "\n", + "db = solver.Phase(x, solver.CHOOSE_FIRST_UNBOUND, solver.ASSIGN_MIN_VALUE)\n", + "\n", + "solver.NewSearch(db)\n", + "num_solutions = 0\n", + "while solver.NextSolution():\n", + " print(\"x: \", [x[i].Value() for i in range(n)])\n", + " print(\"t: \", [t[i].Value() for i in range(n)])\n", + " print(\"number base 10: %i base %i: %s\" % (t[0].Value(), base, \"\".join(\n", + " [digits_str[x[i].Value() + 1] for i in range(n)])))\n", + " print()\n", + " num_solutions += 1\n", + "solver.EndSearch()\n", + "\n", + "print(\"num_solutions:\", num_solutions)\n", + "print(\"failures:\", solver.Failures())\n", + "print(\"branches:\", solver.Branches())\n", + "print(\"WallTime:\", solver.WallTime())\n", + "\n", + "base = 10\n", + "default_base = 10\n", + "max_base = 16\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/contrib/dudeney.ipynb b/examples/notebook/contrib/dudeney.ipynb new file mode 100644 index 0000000000..d3b58e3c0e --- /dev/null +++ b/examples/notebook/contrib/dudeney.ipynb @@ -0,0 +1,57 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010 Pierre Schaus (pschaus@gmail.com)\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "from __future__ import print_function\n", + "from ortools.constraint_solver import pywrapcp\n", + "\n", + "\n", + "def dudeney(n):\n", + " solver = pywrapcp.Solver('Dudeney')\n", + " x = [solver.IntVar(list(range(10)), 'x' + str(i)) for i in range(n)]\n", + " nb = solver.IntVar(list(range(3, 10**n)), 'nb')\n", + " s = solver.IntVar(list(range(1, 9 * n + 1)), 's')\n", + "\n", + " solver.Add(nb == s * s * s)\n", + " solver.Add(sum([10**(n - i - 1) * x[i] for i in range(n)]) == nb)\n", + " solver.Add(sum([x[i] for i in range(n)]) == s)\n", + "\n", + " solution = solver.Assignment()\n", + " solution.Add(nb)\n", + " collector = solver.AllSolutionCollector(solution)\n", + "\n", + " solver.Solve(\n", + " solver.Phase(x, solver.INT_VAR_DEFAULT, solver.INT_VALUE_DEFAULT),\n", + " [collector])\n", + "\n", + " for i in range(collector.SolutionCount()):\n", + " nbsol = collector.Value(i, nb)\n", + " print(nbsol)\n", + "\n", + " print('#fails:', solver.Failures())\n", + " print('time:', solver.WallTime(), 'ms')\n", + "\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/contrib/einav_puzzle.ipynb b/examples/notebook/contrib/einav_puzzle.ipynb new file mode 100644 index 0000000000..5e8c6e6abb --- /dev/null +++ b/examples/notebook/contrib/einav_puzzle.ipynb @@ -0,0 +1,210 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010 Hakan Kjellerstrand hakank@gmail.com\n", + "#\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"\n", + "\n", + " A programming puzzle from Einav in Google CP Solver.\n", + "\n", + " From\n", + " 'A programming puzzle from Einav'\n", + " http://gcanyon.wordpress.com/2009/10/28/a-programming-puzzle-from-einav/\n", + " '''\n", + " My friend Einav gave me this programming puzzle to work on. Given\n", + " this array of positive and negative numbers:\n", + " 33 30 -10 -6 18 7 -11 -23 6\n", + " ...\n", + " -25 4 16 30 33 -23 -4 4 -23\n", + "\n", + " You can flip the sign of entire rows and columns, as many of them\n", + " as you like. The goal is to make all the rows and columns sum to positive\n", + " numbers (or zero), and then to find the solution (there are more than one)\n", + " that has the smallest overall sum. So for example, for this array:\n", + " 33 30 -10\n", + " -16 19 9\n", + " -17 -12 -14\n", + " You could flip the sign for the bottom row to get this array:\n", + " 33 30 -10\n", + " -16 19 9\n", + " 17 12 14\n", + " Now all the rows and columns have positive sums, and the overall total is\n", + " 108.\n", + " But you could instead flip the second and third columns, and the second\n", + " row, to get this array:\n", + " 33 -30 10\n", + " 16 19 9\n", + " -17 12 14\n", + " All the rows and columns still total positive, and the overall sum is just\n", + " 66. So this solution is better (I don't know if it's the best)\n", + " A pure brute force solution would have to try over 30 billion solutions.\n", + " I wrote code to solve this in J. I'll post that separately.\n", + " '''\n", + "\n", + " Compare with the following models:\n", + " * MiniZinc http://www.hakank.org/minizinc/einav_puzzle.mzn\n", + " * SICStus: http://hakank.org/sicstus/einav_puzzle.pl\n", + "\n", + " Note:\n", + " einav_puzzle2.py is Laurent Perron version, which don't use as many\n", + " decision variables as this version.\n", + "\n", + "\n", + " This model was created by Hakan Kjellerstrand (hakank@gmail.com)\n", + " Also see my other Google CP Solver models:\n", + " http://www.hakank.org/google_or_tools/\n", + "\"\"\"\n", + "from __future__ import print_function\n", + "from ortools.constraint_solver import pywrapcp\n", + "\n", + "\n", + "\n", + "# Create the solver.\n", + "solver = pywrapcp.Solver('Einav puzzle')\n", + "\n", + "#\n", + "# data\n", + "#\n", + "\n", + "# small problem\n", + "# rows = 3;\n", + "# cols = 3;\n", + "# data = [\n", + "# [ 33, 30, -10],\n", + "# [-16, 19, 9],\n", + "# [-17, -12, -14]\n", + "# ]\n", + "\n", + "# Full problem\n", + "rows = 27\n", + "cols = 9\n", + "data = [[33, 30, 10, -6, 18, -7, -11, 23, -6],\n", + " [16, -19, 9, -26, -8, -19, -8, -21, -14],\n", + " [17, 12, -14, 31, -30, 13, -13, 19, 16],\n", + " [-6, -11, 1, 17, -12, -4, -7, 14, -21],\n", + " [18, -31, 34, -22, 17, -19, 20, 24, 6],\n", + " [33, -18, 17, -15, 31, -5, 3, 27, -3],\n", + " [-18, -20, -18, 31, 6, 4, -2, -12, 24],\n", + " [27, 14, 4, -29, -3, 5, -29, 8, -12],\n", + " [-15, -7, -23, 23, -9, -8, 6, 8, -12],\n", + " [33, -23, -19, -4, -8, -7, 11, -12, 31],\n", + " [-20, 19, -15, -30, 11, 32, 7, 14, -5],\n", + " [-23, 18, -32, -2, -31, -7, 8, 24, 16],\n", + " [32, -4, -10, -14, -6, -1, 0, 23, 23],\n", + " [25, 0, -23, 22, 12, 28, -27, 15, 4],\n", + " [-30, -13, -16, -3, -3, -32, -3, 27, -31],\n", + " [22, 1, 26, 4, -2, -13, 26, 17, 14],\n", + " [-9, -18, 3, -20, -27, -32, -11, 27, 13],\n", + " [-17, 33, -7, 19, -32, 13, -31, -2, -24],\n", + " [-31, 27, -31, -29, 15, 2, 29, -15, 33],\n", + " [-18, -23, 15, 28, 0, 30, -4, 12, -32],\n", + " [-3, 34, 27, -25, -18, 26, 1, 34, 26],\n", + " [-21, -31, -10, -13, -30, -17, -12, -26, 31],\n", + " [23, -31, -19, 21, -17, -10, 2, -23, 23],\n", + " [-3, 6, 0, -3, -32, 0, -10, -25, 14],\n", + " [-19, 9, 14, -27, 20, 15, -5, -27, 18],\n", + " [11, -6, 24, 7, -17, 26, 20, -31, -25],\n", + " [-25, 4, -16, 30, 33, 23, -4, -4, 23]]\n", + "\n", + "#\n", + "# variables\n", + "#\n", + "x = {}\n", + "for i in range(rows):\n", + " for j in range(cols):\n", + " x[i, j] = solver.IntVar(-100, 100, 'x[%i,%i]' % (i, j))\n", + "\n", + "x_flat = [x[i, j] for i in range(rows) for j in range(cols)]\n", + "\n", + "row_sums = [solver.IntVar(0, 300, 'row_sums(%i)' % i) for i in range(rows)]\n", + "col_sums = [solver.IntVar(0, 300, 'col_sums(%i)' % j) for j in range(cols)]\n", + "\n", + "row_signs = [solver.IntVar([-1, 1], 'row_signs(%i)' % i) for i in range(rows)]\n", + "col_signs = [solver.IntVar([-1, 1], 'col_signs(%i)' % j) for j in range(cols)]\n", + "\n", + "# total sum: to be minimized\n", + "total_sum = solver.IntVar(0, 1000, 'total_sum')\n", + "\n", + "#\n", + "# constraints\n", + "#\n", + "for i in range(rows):\n", + " for j in range(cols):\n", + " solver.Add(x[i, j] == data[i][j] * row_signs[i] * col_signs[j])\n", + "\n", + "total_sum_a = [\n", + " data[i][j] * row_signs[i] * col_signs[j]\n", + " for i in range(rows)\n", + " for j in range(cols)\n", + "]\n", + "solver.Add(total_sum == solver.Sum(total_sum_a))\n", + "\n", + "# row sums\n", + "for i in range(rows):\n", + " s = [row_signs[i] * col_signs[j] * data[i][j] for j in range(cols)]\n", + " solver.Add(row_sums[i] == solver.Sum(s))\n", + "\n", + "# column sums\n", + "for j in range(cols):\n", + " s = [row_signs[i] * col_signs[j] * data[i][j] for i in range(rows)]\n", + " solver.Add(col_sums[j] == solver.Sum(s))\n", + "\n", + "# objective\n", + "objective = solver.Minimize(total_sum, 1)\n", + "\n", + "#\n", + "# search and result\n", + "#\n", + "# Note: The order of the variables makes a big difference.\n", + "# If row_signs are before col_sign it is much slower.\n", + "db = solver.Phase(col_signs + row_signs, solver.CHOOSE_MIN_SIZE_LOWEST_MIN,\n", + " solver.ASSIGN_MAX_VALUE)\n", + "\n", + "solver.NewSearch(db, [objective])\n", + "\n", + "num_solutions = 0\n", + "while solver.NextSolution():\n", + " num_solutions += 1\n", + " print('total_sum:', total_sum.Value())\n", + " print('row_sums:', [row_sums[i].Value() for i in range(rows)])\n", + " print('col_sums:', [col_sums[j].Value() for j in range(cols)])\n", + " print('row_signs:', [row_signs[i].Value() for i in range(rows)])\n", + " print('col_signs:', [col_signs[j].Value() for j in range(cols)])\n", + " print('x:')\n", + " for i in range(rows):\n", + " for j in range(cols):\n", + " print('%3i' % x[i, j].Value(), end=' ')\n", + " print()\n", + " print()\n", + "\n", + "solver.EndSearch()\n", + "\n", + "print()\n", + "print('num_solutions:', num_solutions)\n", + "print('failures:', solver.Failures())\n", + "print('branches:', solver.Branches())\n", + "print('WallTime:', solver.WallTime(), 'ms')\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/contrib/einav_puzzle2.ipynb b/examples/notebook/contrib/einav_puzzle2.ipynb new file mode 100644 index 0000000000..ad4a02c24d --- /dev/null +++ b/examples/notebook/contrib/einav_puzzle2.ipynb @@ -0,0 +1,204 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010 Hakan Kjellerstrand hakank@gmail.com\n", + "#\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"\n", + "\n", + " A programming puzzle from Einav in Google CP Solver.\n", + "\n", + " From\n", + " 'A programming puzzle from Einav'\n", + " http://gcanyon.wordpress.com/2009/10/28/a-programming-puzzle-from-einav/\n", + " '''\n", + " My friend Einav gave me this programming puzzle to work on. Given\n", + " this array of positive and negative numbers:\n", + " 33 30 -10 -6 18 7 -11 -23 6\n", + " ...\n", + " -25 4 16 30 33 -23 -4 4 -23\n", + "\n", + " You can flip the sign of entire rows and columns, as many of them\n", + " as you like. The goal is to make all the rows and columns sum to positive\n", + " numbers (or zero), and then to find the solution (there are more than one)\n", + " that has the smallest overall sum. So for example, for this array:\n", + " 33 30 -10\n", + " -16 19 9\n", + " -17 -12 -14\n", + " You could flip the sign for the bottom row to get this array:\n", + " 33 30 -10\n", + " -16 19 9\n", + " 17 12 14\n", + " Now all the rows and columns have positive sums, and the overall total is\n", + " 108.\n", + " But you could instead flip the second and third columns, and the second\n", + " row, to get this array:\n", + " 33 -30 10\n", + " 16 19 9\n", + " -17 12 14\n", + " All the rows and columns still total positive, and the overall sum is just\n", + " 66. So this solution is better (I don't know if it's the best)\n", + " A pure brute force solution would have to try over 30 billion solutions.\n", + " I wrote code to solve this in J. I'll post that separately.\n", + " '''\n", + "\n", + " Compare with the following models:\n", + " * MiniZinc http://www.hakank.org/minizinc/einav_puzzle.mzn\n", + " * SICStus: http://hakank.org/sicstus/einav_puzzle.pl\n", + "\n", + " Note:\n", + " This is a Larent Perrons's variant of einav_puzzle.py.\n", + " He removed some of the decision variables and made it more efficient.\n", + " Thanks!\n", + "\n", + "\n", + " This model was created by Hakan Kjellerstrand (hakank@gmail.com)\n", + " Also see my other Google CP Solver models:\n", + " http://www.hakank.org/google_or_tools/\n", + "\"\"\"\n", + "from __future__ import print_function\n", + "from ortools.constraint_solver import pywrapcp\n", + "\n", + "\n", + "\n", + "# Create the solver.\n", + "solver = pywrapcp.Solver(\"Einav puzzle\")\n", + "\n", + "#\n", + "# data\n", + "#\n", + "\n", + "# small problem\n", + "# rows = 3;\n", + "# cols = 3;\n", + "# data = [\n", + "# [ 33, 30, -10],\n", + "# [-16, 19, 9],\n", + "# [-17, -12, -14]\n", + "# ]\n", + "\n", + "# Full problem\n", + "rows = 27\n", + "cols = 9\n", + "data = [[33, 30, 10, -6, 18, -7, -11, 23, -6],\n", + " [16, -19, 9, -26, -8, -19, -8, -21, -14],\n", + " [17, 12, -14, 31, -30, 13, -13, 19, 16],\n", + " [-6, -11, 1, 17, -12, -4, -7, 14, -21],\n", + " [18, -31, 34, -22, 17, -19, 20, 24, 6],\n", + " [33, -18, 17, -15, 31, -5, 3, 27, -3],\n", + " [-18, -20, -18, 31, 6, 4, -2, -12, 24],\n", + " [27, 14, 4, -29, -3, 5, -29, 8, -12],\n", + " [-15, -7, -23, 23, -9, -8, 6, 8, -12],\n", + " [33, -23, -19, -4, -8, -7, 11, -12, 31],\n", + " [-20, 19, -15, -30, 11, 32, 7, 14, -5],\n", + " [-23, 18, -32, -2, -31, -7, 8, 24, 16],\n", + " [32, -4, -10, -14, -6, -1, 0, 23, 23],\n", + " [25, 0, -23, 22, 12, 28, -27, 15, 4],\n", + " [-30, -13, -16, -3, -3, -32, -3, 27, -31],\n", + " [22, 1, 26, 4, -2, -13, 26, 17, 14],\n", + " [-9, -18, 3, -20, -27, -32, -11, 27, 13],\n", + " [-17, 33, -7, 19, -32, 13, -31, -2, -24],\n", + " [-31, 27, -31, -29, 15, 2, 29, -15, 33],\n", + " [-18, -23, 15, 28, 0, 30, -4, 12, -32],\n", + " [-3, 34, 27, -25, -18, 26, 1, 34, 26],\n", + " [-21, -31, -10, -13, -30, -17, -12, -26, 31],\n", + " [23, -31, -19, 21, -17, -10, 2, -23, 23],\n", + " [-3, 6, 0, -3, -32, 0, -10, -25, 14],\n", + " [-19, 9, 14, -27, 20, 15, -5, -27, 18],\n", + " [11, -6, 24, 7, -17, 26, 20, -31, -25],\n", + " [-25, 4, -16, 30, 33, 23, -4, -4, 23]]\n", + "\n", + "#\n", + "# variables\n", + "#\n", + "x = {}\n", + "for i in range(rows):\n", + " for j in range(cols):\n", + " x[i, j] = solver.IntVar(-100, 100, \"x[%i,%i]\" % (i, j))\n", + "\n", + "x_flat = [x[i, j] for i in range(rows) for j in range(cols)]\n", + "\n", + "row_signs = [solver.IntVar([-1, 1], \"row_signs(%i)\" % i) for i in range(rows)]\n", + "col_signs = [solver.IntVar([-1, 1], \"col_signs(%i)\" % j) for j in range(cols)]\n", + "\n", + "#\n", + "# constraints\n", + "#\n", + "for i in range(rows):\n", + " for j in range(cols):\n", + " solver.Add(x[i, j] == data[i][j] * row_signs[i] * col_signs[j])\n", + "\n", + "total_sum = solver.Sum([x[i, j] for i in range(rows) for j in range(cols)])\n", + "\n", + "#\n", + "# Note: In einav_puzzle.py row_sums and col_sums are decision variables.\n", + "#\n", + "\n", + "# row sums\n", + "row_sums = [\n", + " solver.Sum([x[i, j] for j in range(cols)]).Var() for i in range(rows)\n", + "]\n", + "# >= 0\n", + "for i in range(rows):\n", + " row_sums[i].SetMin(0)\n", + "\n", + "# column sums\n", + "col_sums = [\n", + " solver.Sum([x[i, j] for i in range(rows)]).Var() for j in range(cols)\n", + "]\n", + "for j in range(cols):\n", + " col_sums[j].SetMin(0)\n", + "\n", + "# objective\n", + "objective = solver.Minimize(total_sum, 1)\n", + "\n", + "#\n", + "# search and result\n", + "#\n", + "db = solver.Phase(col_signs + row_signs, solver.CHOOSE_MIN_SIZE_LOWEST_MIN,\n", + " solver.ASSIGN_MAX_VALUE)\n", + "\n", + "solver.NewSearch(db, [objective])\n", + "\n", + "num_solutions = 0\n", + "while solver.NextSolution():\n", + " num_solutions += 1\n", + " print(\"Sum =\", objective.Best())\n", + " print(\"row_sums:\", [row_sums[i].Value() for i in range(rows)])\n", + " print(\"col_sums:\", [col_sums[j].Value() for j in range(cols)])\n", + " for i in range(rows):\n", + " for j in range(cols):\n", + " print(\"%3i\" % x[i, j].Value(), end=\" \")\n", + " print()\n", + " print()\n", + "\n", + "solver.EndSearch()\n", + "\n", + "print()\n", + "print(\"num_solutions:\", num_solutions)\n", + "print(\"failures:\", solver.Failures())\n", + "print(\"branches:\", solver.Branches())\n", + "print(\"WallTime:\", solver.WallTime())\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/contrib/eq10.ipynb b/examples/notebook/contrib/eq10.ipynb new file mode 100644 index 0000000000..f0cf707dd4 --- /dev/null +++ b/examples/notebook/contrib/eq10.ipynb @@ -0,0 +1,116 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010 Hakan Kjellerstrand hakank@gmail.com\n", + "#\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"\n", + "\n", + " Eq 10 in Google CP Solver.\n", + "\n", + " Standard benchmark problem.\n", + "\n", + " Compare with the following models:\n", + " * MiniZinc: http://hakank.org/minizinc/eq10.mzn\n", + " * ECLiPSe: http://hakank.org/eclipse/eq10.ecl\n", + " * SICStus: http://hakank.org/sicstus/eq10.pl\n", + "\n", + " This model was created by Hakan Kjellerstrand (hakank@gmail.com)\n", + " Also see my other Google CP Solver models:\n", + " http://www.hakank.org/google_or_tools/\n", + "\"\"\"\n", + "from __future__ import print_function\n", + "from ortools.constraint_solver import pywrapcp\n", + "\n", + "\n", + "\n", + "# Create the solver.\n", + "solver = pywrapcp.Solver(\"Eq 10\")\n", + "\n", + "#\n", + "# data\n", + "#\n", + "n = 7\n", + "\n", + "#\n", + "# variables\n", + "#\n", + "X = [solver.IntVar(0, 10, \"X(%i)\" % i) for i in range(n)]\n", + "X1, X2, X3, X4, X5, X6, X7 = X\n", + "\n", + "#\n", + "# constraints\n", + "#\n", + "solver.Add(0 + 98527 * X1 + 34588 * X2 + 5872 * X3 + 59422 * X5 +\n", + " 65159 * X7 == 1547604 + 30704 * X4 + 29649 * X6)\n", + "\n", + "solver.Add(0 + 98957 * X2 + 83634 * X3 + 69966 * X4 + 62038 * X5 +\n", + " 37164 * X6 + 85413 * X7 == 1823553 + 93989 * X1)\n", + "\n", + "solver.Add(900032 + 10949 * X1 + 77761 * X2 + 67052 * X5 == 0 + 80197 * X3 +\n", + " 61944 * X4 + 92964 * X6 + 44550 * X7)\n", + "\n", + "solver.Add(0 + 73947 * X1 + 84391 * X3 + 81310 * X5 == 1164380 + 96253 * X2 +\n", + " 44247 * X4 + 70582 * X6 + 33054 * X7)\n", + "\n", + "solver.Add(0 + 13057 * X3 + 42253 * X4 + 77527 * X5 + 96552 * X7 == 1185471 +\n", + " 60152 * X1 + 21103 * X2 + 97932 * X6)\n", + "\n", + "solver.Add(1394152 + 66920 * X1 + 55679 * X4 == 0 + 64234 * X2 + 65337 * X3 +\n", + " 45581 * X5 + 67707 * X6 + 98038 * X7)\n", + "\n", + "solver.Add(0 + 68550 * X1 + 27886 * X2 + 31716 * X3 + 73597 * X4 +\n", + " 38835 * X7 == 279091 + 88963 * X5 + 76391 * X6)\n", + "\n", + "solver.Add(0 + 76132 * X2 + 71860 * X3 + 22770 * X4 + 68211 * X5 +\n", + " 78587 * X6 == 480923 + 48224 * X1 + 82817 * X7)\n", + "\n", + "solver.Add(519878 + 94198 * X2 + 87234 * X3 + 37498 * X4 == 0 + 71583 * X1 +\n", + " 25728 * X5 + 25495 * X6 + 70023 * X7)\n", + "\n", + "solver.Add(361921 + 78693 * X1 + 38592 * X5 + 38478 * X6 == 0 + 94129 * X2 +\n", + " 43188 * X3 + 82528 * X4 + 69025 * X7)\n", + "\n", + "#\n", + "# search and result\n", + "#\n", + "db = solver.Phase(X, solver.INT_VAR_SIMPLE, solver.INT_VALUE_SIMPLE)\n", + "\n", + "solver.NewSearch(db)\n", + "\n", + "num_solutions = 0\n", + "while solver.NextSolution():\n", + " num_solutions += 1\n", + " print(\"X:\", [X[i].Value() for i in range(n)])\n", + " print()\n", + "\n", + "solver.EndSearch()\n", + "\n", + "print()\n", + "print(\"num_solutions:\", num_solutions)\n", + "print(\"failures:\", solver.Failures())\n", + "print(\"branches:\", solver.Branches())\n", + "print(\"WallTime:\", solver.WallTime())\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/contrib/eq20.ipynb b/examples/notebook/contrib/eq20.ipynb new file mode 100644 index 0000000000..484cd20c02 --- /dev/null +++ b/examples/notebook/contrib/eq20.ipynb @@ -0,0 +1,127 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010 Hakan Kjellerstrand hakank@gmail.com\n", + "#\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"\n", + "\n", + " Eq 20 in Google CP Solver.\n", + "\n", + " Standard benchmark problem.\n", + "\n", + " Compare with the following models:\n", + " * Gecode/R: http://hakank.org/gecode_r/eq20.rb\n", + " * ECLiPSe: http://hakank.org/eclipse/eq20.ecl\n", + " * SICStus: http://hakank.org/sicstus/eq20.pl\n", + "\n", + " This model was created by Hakan Kjellerstrand (hakank@gmail.com)\n", + " Also see my other Google CP Solver models:\n", + " http://www.hakank.org/google_or_tools/\n", + "\"\"\"\n", + "from __future__ import print_function\n", + "from ortools.constraint_solver import pywrapcp\n", + "\n", + "\n", + "\n", + "# Create the solver.\n", + "solver = pywrapcp.Solver(\"Eq 20\")\n", + "\n", + "#\n", + "# data\n", + "#\n", + "n = 7\n", + "\n", + "#\n", + "# variables\n", + "#\n", + "X = [solver.IntVar(0, 10, \"X(%i)\" % i) for i in range(n)]\n", + "X0, X1, X2, X3, X4, X5, X6 = X\n", + "\n", + "#\n", + "# constraints\n", + "#\n", + "solver.Add(-76706 * X0 + 98205 * X1 + 23445 * X2 + 67921 * X3 + 24111 * X4 +\n", + " -48614 * X5 + -41906 * X6 == 821228)\n", + "solver.Add(87059 * X0 + -29101 * X1 + -5513 * X2 + -21219 * X3 + 22128 * X4 +\n", + " 7276 * X5 + 57308 * X6 == 22167)\n", + "solver.Add(-60113 * X0 + 29475 * X1 + 34421 * X2 + -76870 * X3 + 62646 * X4 +\n", + " 29278 * X5 + -15212 * X6 == 251591)\n", + "solver.Add(49149 * X0 + 52871 * X1 + -7132 * X2 + 56728 * X3 + -33576 * X4 +\n", + " -49530 * X5 + -62089 * X6 == 146074)\n", + "solver.Add(-10343 * X0 + 87758 * X1 + -11782 * X2 + 19346 * X3 + 70072 * X4 +\n", + " -36991 * X5 + 44529 * X6 == 740061)\n", + "solver.Add(85176 * X0 + -95332 * X1 + -1268 * X2 + 57898 * X3 + 15883 * X4 +\n", + " 50547 * X5 + 83287 * X6 == 373854)\n", + "solver.Add(-85698 * X0 + 29958 * X1 + 57308 * X2 + 48789 * X3 + -78219 * X4 +\n", + " 4657 * X5 + 34539 * X6 == 249912)\n", + "solver.Add(-67456 * X0 + 84750 * X1 + -51553 * X2 + 21239 * X3 + 81675 * X4 +\n", + " -99395 * X5 + -4254 * X6 == 277271)\n", + "solver.Add(94016 * X0 + -82071 * X1 + 35961 * X2 + 66597 * X3 + -30705 * X4 +\n", + " -44404 * X5 + -38304 * X6 == 25334)\n", + "solver.Add(-60301 * X0 + 31227 * X1 + 93951 * X2 + 73889 * X3 + 81526 * X4 +\n", + " -72702 * X5 + 68026 * X6 == 1410723)\n", + "solver.Add(-16835 * X0 + 47385 * X1 + 97715 * X2 + -12640 * X3 + 69028 * X4 +\n", + " 76212 * X5 + -81102 * X6 == 1244857)\n", + "solver.Add(-43277 * X0 + 43525 * X1 + 92298 * X2 + 58630 * X3 + 92590 * X4 +\n", + " -9372 * X5 + -60227 * X6 == 1503588)\n", + "solver.Add(-64919 * X0 + 80460 * X1 + 90840 * X2 + -59624 * X3 + -75542 * X4 +\n", + " 25145 * X5 + -47935 * X6 == 18465)\n", + "solver.Add(-45086 * X0 + 51830 * X1 + -4578 * X2 + 96120 * X3 + 21231 * X4 +\n", + " 97919 * X5 + 65651 * X6 == 1198280)\n", + "solver.Add(85268 * X0 + 54180 * X1 + -18810 * X2 + -48219 * X3 + 6013 * X4 +\n", + " 78169 * X5 + -79785 * X6 == 90614)\n", + "solver.Add(8874 * X0 + -58412 * X1 + 73947 * X2 + 17147 * X3 + 62335 * X4 +\n", + " 16005 * X5 + 8632 * X6 == 752447)\n", + "solver.Add(71202 * X0 + -11119 * X1 + 73017 * X2 + -38875 * X3 + -14413 * X4 +\n", + " -29234 * X5 + 72370 * X6 == 129768)\n", + "solver.Add(1671 * X0 + -34121 * X1 + 10763 * X2 + 80609 * X3 + 42532 * X4 +\n", + " 93520 * X5 + -33488 * X6 == 915683)\n", + "solver.Add(51637 * X0 + 67761 * X1 + 95951 * X2 + 3834 * X3 + -96722 * X4 +\n", + " 59190 * X5 + 15280 * X6 == 533909)\n", + "solver.Add(-16105 * X0 + 62397 * X1 + -6704 * X2 + 43340 * X3 + 95100 * X4 +\n", + " -68610 * X5 + 58301 * X6 == 876370)\n", + "\n", + "#\n", + "# search and result\n", + "#\n", + "db = solver.Phase(X, solver.CHOOSE_FIRST_UNBOUND, solver.ASSIGN_MIN_VALUE)\n", + "\n", + "solver.NewSearch(db)\n", + "\n", + "num_solutions = 0\n", + "while solver.NextSolution():\n", + " num_solutions += 1\n", + " print(\"X:\", [X[i].Value() for i in range(n)])\n", + " print()\n", + "\n", + "solver.EndSearch()\n", + "\n", + "print()\n", + "print(\"num_solutions:\", num_solutions)\n", + "print(\"failures:\", solver.Failures())\n", + "print(\"branches:\", solver.Branches())\n", + "print(\"WallTime:\", solver.WallTime())\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/contrib/fill_a_pix.ipynb b/examples/notebook/contrib/fill_a_pix.ipynb new file mode 100644 index 0000000000..97f65415d6 --- /dev/null +++ b/examples/notebook/contrib/fill_a_pix.ipynb @@ -0,0 +1,182 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010 Hakan Kjellerstrand hakank@gmail.com\n", + "#\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"\n", + "\n", + " Fill-a-Pix problem in Google CP Solver.\n", + "\n", + " From\n", + " http://www.conceptispuzzles.com/index.aspx?uri=puzzle/fill-a-pix/basiclogic\n", + " '''\n", + " Each puzzle consists of a grid containing clues in various places. The\n", + " object is to reveal a hidden picture by painting the squares around each\n", + " clue so that the number of painted squares, including the square with\n", + " the clue, matches the value of the clue.\n", + " '''\n", + "\n", + " http://www.conceptispuzzles.com/index.aspx?uri=puzzle/fill-a-pix/rules\n", + " '''\n", + " Fill-a-Pix is a Minesweeper-like puzzle based on a grid with a pixilated\n", + " picture hidden inside. Using logic alone, the solver determines which\n", + " squares are painted and which should remain empty until the hidden picture\n", + " is completely exposed.\n", + " '''\n", + "\n", + " Fill-a-pix History:\n", + " http://www.conceptispuzzles.com/index.aspx?uri=puzzle/fill-a-pix/history\n", + "\n", + "\n", + " Compare with the following models:\n", + " * MiniZinc: http://www.hakank.org/minizinc/fill_a_pix.mzn\n", + " * SICStus Prolog: http://www.hakank.org/sicstus/fill_a_pix.pl\n", + " * ECLiPSe: http://hakank.org/eclipse/fill_a_pix.ecl\n", + " * Gecode: http://hakank.org/gecode/fill_a_pix.cpp\n", + "\n", + " And see the Minesweeper model:\n", + " * http://www.hakank.org/google_or_tools/minesweeper.py\n", + "\n", + "\n", + " This model was created by Hakan Kjellerstrand (hakank@gmail.com)\n", + " Also see my other Google CP Solver models:\n", + " http://www.hakank.org/google_or_tools/\n", + "\"\"\"\n", + "from __future__ import print_function\n", + "import sys\n", + "from ortools.constraint_solver import pywrapcp\n", + "\n", + "# Puzzle 1 from\n", + "# http://www.conceptispuzzles.com/index.aspx?uri=puzzle/fill-a-pix/rules\n", + "default_n = 10\n", + "X = -1\n", + "default_puzzle = [\n", + " [X, X, X, X, X, X, X, X, 0, X], [X, 8, 8, X, 2, X, 0, X, X, X],\n", + " [5, X, 8, X, X, X, X, X, X, X], [X, X, X, X, X, 2, X, X, X, 2],\n", + " [1, X, X, X, 4, 5, 6, X, X, X], [X, 0, X, X, X, 7, 9, X, X, 6],\n", + " [X, X, X, 6, X, X, 9, X, X, 6], [X, X, 6, 6, 8, 7, 8, 7, X, 5],\n", + " [X, 4, X, 6, 6, 6, X, 6, X, 4], [X, X, X, X, X, X, 3, X, X, X]\n", + "]\n", + "\n", + "\n", + "\n", + "# Create the solver.\n", + "solver = pywrapcp.Solver('Fill-a-Pix')\n", + "\n", + "#\n", + "# data\n", + "#\n", + "\n", + "# Set default problem\n", + "if puzzle == '':\n", + " puzzle = default_puzzle\n", + " n = default_n\n", + "else:\n", + " print('n:', n)\n", + "\n", + "# for the neighbors of 'this' cell\n", + "S = [-1, 0, 1]\n", + "\n", + "# print problem instance\n", + "print('Problem:')\n", + "for i in range(n):\n", + " for j in range(n):\n", + " if puzzle[i][j] == X:\n", + " sys.stdout.write('.')\n", + " else:\n", + " sys.stdout.write(str(puzzle[i][j]))\n", + " print()\n", + "print()\n", + "\n", + "#\n", + "# declare variables\n", + "#\n", + "pict = {}\n", + "for i in range(n):\n", + " for j in range(n):\n", + " pict[(i, j)] = solver.IntVar(0, 1, 'pict %i %i' % (i, j))\n", + "\n", + "pict_flat = [pict[i, j] for i in range(n) for j in range(n)]\n", + "\n", + "#\n", + "# constraints\n", + "#\n", + "for i in range(n):\n", + " for j in range(n):\n", + " if puzzle[i][j] > X:\n", + " # this cell is the sum of all the surrounding cells\n", + " solver.Add(puzzle[i][j] == solver.Sum([\n", + " pict[i + a, j + b]\n", + " for a in S\n", + " for b in S\n", + " if i + a >= 0 and j + b >= 0 and i + a < n and j + b < n\n", + " ]))\n", + "\n", + "#\n", + "# solution and search\n", + "#\n", + "db = solver.Phase(pict_flat, solver.INT_VAR_DEFAULT, solver.INT_VALUE_DEFAULT)\n", + "\n", + "solver.NewSearch(db)\n", + "num_solutions = 0\n", + "print('Solution:')\n", + "while solver.NextSolution():\n", + " num_solutions += 1\n", + " for i in range(n):\n", + " row = [str(pict[i, j].Value()) for j in range(n)]\n", + " for j in range(n):\n", + " if row[j] == '0':\n", + " row[j] = ' '\n", + " else:\n", + " row[j] = '#'\n", + " print(''.join(row))\n", + " print()\n", + "\n", + "print('num_solutions:', num_solutions)\n", + "print('failures:', solver.Failures())\n", + "print('branches:', solver.Branches())\n", + "print('WallTime:', solver.WallTime(), 'ms')\n", + "\n", + "\n", + "#\n", + "# Read a problem instance from a file\n", + "#def read_problem(file):\n", + " f = open(file, 'r')\n", + " n = int(f.readline())\n", + " puzzle = []\n", + " for i in range(n):\n", + " x = f.readline()\n", + " row = [0] * n\n", + " for j in range(n):\n", + " if x[j] == '.':\n", + " tmp = -1\n", + " else:\n", + " tmp = int(x[j])\n", + " row[j] = tmp\n", + " puzzle.append(row)\n", + " return [puzzle, n]\n", + "\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/contrib/furniture_moving.ipynb b/examples/notebook/contrib/furniture_moving.ipynb new file mode 100644 index 0000000000..c6d7b01818 --- /dev/null +++ b/examples/notebook/contrib/furniture_moving.ipynb @@ -0,0 +1,187 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010 Hakan Kjellerstrand hakank@gmail.com\n", + "#\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"\n", + "\n", + " Moving furnitures (scheduling) problem in Google CP Solver.\n", + "\n", + " Marriott & Stukey: 'Programming with constraints', page 112f\n", + "\n", + " The model implements an experimental decomposition of the\n", + " global constraint cumulative.\n", + "\n", + " Compare with the following models:\n", + " * ECLiPSE: http://www.hakank.org/eclipse/furniture_moving.ecl\n", + " * MiniZinc: http://www.hakank.org/minizinc/furniture_moving.mzn\n", + " * Comet: http://www.hakank.org/comet/furniture_moving.co\n", + " * Choco: http://www.hakank.org/choco/FurnitureMoving.java\n", + " * Gecode: http://www.hakank.org/gecode/furniture_moving.cpp\n", + " * JaCoP: http://www.hakank.org/JaCoP/FurnitureMoving.java\n", + " * SICStus: http://hakank.org/sicstus/furniture_moving.pl\n", + " * Zinc: http://hakank.org/minizinc/furniture_moving.zinc\n", + "\n", + "\n", + " This model was created by Hakan Kjellerstrand (hakank@gmail.com)\n", + " Also see my other Google CP Solver models:\n", + " http://www.hakank.org/google_or_tools/\n", + "\"\"\"\n", + "from __future__ import print_function\n", + "import sys\n", + "from ortools.constraint_solver import pywrapcp\n", + "\n", + "\n", + "#\n", + "# Decompositon of cumulative.\n", + "#\n", + "# Inspired by the MiniZinc implementation:\n", + "# http://www.g12.csse.unimelb.edu.au/wiki/doku.php?id=g12:zinc:lib:minizinc:std:cumulative.mzn&s[]=cumulative\n", + "# The MiniZinc decomposition is discussed in the paper:\n", + "# A. Schutt, T. Feydy, P.J. Stuckey, and M. G. Wallace.\n", + "# 'Why cumulative decomposition is not as bad as it sounds.'\n", + "# Download:\n", + "# http://www.cs.mu.oz.au/%7Epjs/rcpsp/papers/cp09-cu.pdf\n", + "# http://www.cs.mu.oz.au/%7Epjs/rcpsp/cumu_lazyfd.pdf\n", + "#\n", + "#\n", + "# Parameters:\n", + "#\n", + "# s: start_times assumption: array of IntVar\n", + "# d: durations assumption: array of int\n", + "# r: resources assumption: array of int\n", + "# b: resource limit assumption: IntVar or int\n", + "#\n", + "def my_cumulative(solver, s, d, r, b):\n", + "\n", + " # tasks = [i for i in range(len(s))]\n", + " tasks = [i for i in range(len(s)) if r[i] > 0 and d[i] > 0]\n", + " times_min = min([s[i].Min() for i in tasks])\n", + " times_max = max([s[i].Max() + max(d) for i in tasks])\n", + " for t in range(times_min, times_max + 1):\n", + " bb = []\n", + " for i in tasks:\n", + " c1 = solver.IsLessOrEqualCstVar(s[i], t) # s[i] <= t\n", + " c2 = solver.IsGreaterCstVar(s[i] + d[i], t) # t < s[i] + d[i]\n", + " bb.append(c1 * c2 * r[i])\n", + " solver.Add(solver.Sum(bb) <= b)\n", + "\n", + " # Somewhat experimental:\n", + " # This constraint is needed to contrain the upper limit of b.\n", + " if not isinstance(b, int):\n", + " solver.Add(b <= sum(r))\n", + "\n", + "\n", + "\n", + "# Create the solver.\n", + "solver = pywrapcp.Solver(\"Furniture moving\")\n", + "\n", + "#\n", + "# data\n", + "#\n", + "n = 4\n", + "duration = [30, 10, 15, 15]\n", + "demand = [3, 1, 3, 2]\n", + "upper_limit = 160\n", + "\n", + "#\n", + "# declare variables\n", + "#\n", + "start_times = [\n", + " solver.IntVar(0, upper_limit, \"start_times[%i]\" % i) for i in range(n)\n", + "]\n", + "end_times = [\n", + " solver.IntVar(0, upper_limit * 2, \"end_times[%i]\" % i) for i in range(n)\n", + "]\n", + "end_time = solver.IntVar(0, upper_limit * 2, \"end_time\")\n", + "\n", + "# number of needed resources, to be minimized\n", + "num_resources = solver.IntVar(0, 10, \"num_resources\")\n", + "\n", + "#\n", + "# constraints\n", + "#\n", + "for i in range(n):\n", + " solver.Add(end_times[i] == start_times[i] + duration[i])\n", + "\n", + "solver.Add(end_time == solver.Max(end_times))\n", + "\n", + "my_cumulative(solver, start_times, duration, demand, num_resources)\n", + "\n", + "#\n", + "# Some extra constraints to play with\n", + "#\n", + "\n", + "# all tasks must end within an hour\n", + "# solver.Add(end_time <= 60)\n", + "\n", + "# All tasks should start at time 0\n", + "# for i in range(n):\n", + "# solver.Add(start_times[i] == 0)\n", + "\n", + "# limitation of the number of people\n", + "# solver.Add(num_resources <= 3)\n", + "\n", + "#\n", + "# objective\n", + "#\n", + "# objective = solver.Minimize(end_time, 1)\n", + "objective = solver.Minimize(num_resources, 1)\n", + "\n", + "#\n", + "# solution and search\n", + "#\n", + "solution = solver.Assignment()\n", + "solution.Add(start_times)\n", + "solution.Add(end_times)\n", + "solution.Add(end_time)\n", + "solution.Add(num_resources)\n", + "\n", + "db = solver.Phase(start_times, solver.CHOOSE_FIRST_UNBOUND,\n", + " solver.ASSIGN_MIN_VALUE)\n", + "\n", + "#\n", + "# result\n", + "#\n", + "solver.NewSearch(db, [objective])\n", + "num_solutions = 0\n", + "while solver.NextSolution():\n", + " num_solutions += 1\n", + " print(\"num_resources:\", num_resources.Value())\n", + " print(\"start_times :\", [start_times[i].Value() for i in range(n)])\n", + " print(\"duration :\", [duration[i] for i in range(n)])\n", + " print(\"end_times :\", [end_times[i].Value() for i in range(n)])\n", + " print(\"end_time :\", end_time.Value())\n", + " print()\n", + "\n", + "solver.EndSearch()\n", + "\n", + "print()\n", + "print(\"num_solutions:\", num_solutions)\n", + "print(\"failures:\", solver.Failures())\n", + "print(\"branches:\", solver.Branches())\n", + "print(\"WallTime:\", solver.WallTime())\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/contrib/futoshiki.ipynb b/examples/notebook/contrib/futoshiki.ipynb new file mode 100644 index 0000000000..c8662a9d21 --- /dev/null +++ b/examples/notebook/contrib/futoshiki.ipynb @@ -0,0 +1,169 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010 Hakan Kjellerstrand hakank@gmail.com\n", + "#\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"\n", + "\n", + " Futoshiki problem in Google CP Solver.\n", + "\n", + " From http://en.wikipedia.org/wiki/Futoshiki\n", + " '''\n", + " The puzzle is played on a square grid, such as 5 x 5. The objective\n", + " is to place the numbers 1 to 5 (or whatever the dimensions are)\n", + " such that each row, and column contains each of the digits 1 to 5.\n", + " Some digits may be given at the start. In addition, inequality\n", + " constraints are also initially specifed between some of the squares,\n", + " such that one must be higher or lower than its neighbour. These\n", + " constraints must be honoured as the grid is filled out.\n", + " '''\n", + "\n", + " Also see\n", + " http://www.guardian.co.uk/world/2006/sep/30/japan.estheraddley\n", + "\n", + "\n", + " This Google CP Solver model is inspired by the Minion/Tailor\n", + " example futoshiki.eprime.\n", + "\n", + "\n", + " Compare with the following models:\n", + " * MiniZinc: http://hakank.org/minizinc/futoshiki.mzn\n", + " * ECLiPSe: http://hakank.org/eclipse/futoshiki.ecl\n", + " * Gecode: http://hakank.org/gecode/futoshiki.cpp\n", + " * SICStus: http://hakank.org/sicstus/futoshiki.pl\n", + "\n", + " This model was created by Hakan Kjellerstrand (hakank@gmail.com)\n", + " Also see my other Google CP Solver models:\n", + " http://www.hakank.org/google_or_tools/\n", + "\"\"\"\n", + "from __future__ import print_function\n", + "from ortools.constraint_solver import pywrapcp\n", + "\n", + "\n", + "\n", + "# Create the solver.\n", + "solver = pywrapcp.Solver(\"Futoshiki problem\")\n", + "\n", + "#\n", + "# data\n", + "#\n", + "size = len(values)\n", + "RANGE = list(range(size))\n", + "NUMQD = list(range(len(lt)))\n", + "\n", + "#\n", + "# variables\n", + "#\n", + "field = {}\n", + "for i in RANGE:\n", + " for j in RANGE:\n", + " field[i, j] = solver.IntVar(1, size, \"field[%i,%i]\" % (i, j))\n", + "field_flat = [field[i, j] for i in RANGE for j in RANGE]\n", + "\n", + "#\n", + "# constraints\n", + "#\n", + "# set initial values\n", + "for row in RANGE:\n", + " for col in RANGE:\n", + " if values[row][col] > 0:\n", + " solver.Add(field[row, col] == values[row][col])\n", + "\n", + "# all rows have to be different\n", + "for row in RANGE:\n", + " solver.Add(solver.AllDifferent([field[row, col] for col in RANGE]))\n", + "\n", + "# all columns have to be different\n", + "for col in RANGE:\n", + " solver.Add(solver.AllDifferent([field[row, col] for row in RANGE]))\n", + "\n", + "# all < constraints are satisfied\n", + "# Also: make 0-based\n", + "for i in NUMQD:\n", + " solver.Add(\n", + " field[lt[i][0] - 1, lt[i][1] - 1] < field[lt[i][2] - 1, lt[i][3] - 1])\n", + "\n", + "#\n", + "# search and result\n", + "#\n", + "db = solver.Phase(field_flat, solver.CHOOSE_FIRST_UNBOUND,\n", + " solver.ASSIGN_MIN_VALUE)\n", + "\n", + "solver.NewSearch(db)\n", + "\n", + "num_solutions = 0\n", + "while solver.NextSolution():\n", + " num_solutions += 1\n", + " for i in RANGE:\n", + " for j in RANGE:\n", + " print(field[i, j].Value(), end=\" \")\n", + " print()\n", + " print()\n", + "\n", + "solver.EndSearch()\n", + "\n", + "print(\"num_solutions:\", num_solutions)\n", + "print(\"failures:\", solver.Failures())\n", + "print(\"branches:\", solver.Branches())\n", + "print(\"WallTime:\", solver.WallTime())\n", + "\n", + "\n", + "#\n", + "# Example from Tailor model futoshiki.param/futoshiki.param\n", + "# Solution:\n", + "# 5 1 3 2 4\n", + "# 1 4 2 5 3\n", + "# 2 3 1 4 5\n", + "# 3 5 4 1 2\n", + "# 4 2 5 3 1\n", + "#\n", + "# Futoshiki instance, by Andras Salamon\n", + "# specify the numbers in the grid\n", + "#values1 = [[0, 0, 3, 2, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0],\n", + " [0, 0, 0, 0, 0]]\n", + "\n", + "# [i1,j1, i2,j2] requires that values[i1,j1] < values[i2,j2]\n", + "# Note: 1-based\n", + "lt1 = [[1, 2, 1, 1], [1, 4, 1, 5], [2, 3, 1, 3], [3, 3, 2, 3], [3, 4, 2, 4],\n", + " [2, 5, 3, 5], [3, 2, 4, 2], [4, 4, 4, 3], [5, 2, 5, 1], [5, 4, 5, 3],\n", + " [5, 5, 4, 5]]\n", + "\n", + "#\n", + "# Example from http://en.wikipedia.org/wiki/Futoshiki\n", + "# Solution:\n", + "# 5 4 3 2 1\n", + "# 4 3 1 5 2\n", + "# 2 1 4 3 5\n", + "# 3 5 2 1 4\n", + "# 1 2 5 4 3\n", + "#\n", + "values2 = [[0, 0, 0, 0, 0], [4, 0, 0, 0, 2], [0, 0, 4, 0, 0], [0, 0, 0, 0, 4],\n", + " [0, 0, 0, 0, 0]]\n", + "\n", + "# Note: 1-based\n", + "lt2 = [[1, 2, 1, 1], [1, 4, 1, 3], [1, 5, 1, 4], [4, 4, 4, 5], [5, 1, 5, 2],\n", + " [5, 2, 5, 3]]\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/contrib/game_theory_taha.ipynb b/examples/notebook/contrib/game_theory_taha.ipynb new file mode 100644 index 0000000000..9c4a6cb4c7 --- /dev/null +++ b/examples/notebook/contrib/game_theory_taha.ipynb @@ -0,0 +1,121 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2011 Hakan Kjellerstrand hakank@gmail.com\n", + "#\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"\n", + "\n", + " Game theory in Google or-tools.\n", + "\n", + " 2 player zero sum game.\n", + "\n", + " From Taha, Operations Research (8'th edition), page 528.\n", + "\n", + " This model was created by Hakan Kjellerstrand (hakank@gmail.com)\n", + " Also see my other Google CP Solver models:\n", + " http://www.hakank.org/google_or_tools/\n", + "\"\"\"\n", + "from __future__ import print_function\n", + "import sys\n", + "from ortools.linear_solver import pywraplp\n", + "\n", + "\n", + "\n", + "# Create the solver.\n", + "\n", + "# using GLPK\n", + "if sol == 'GLPK':\n", + " solver = pywraplp.Solver('CoinsGridGLPK',\n", + " pywraplp.Solver.GLPK_LINEAR_PROGRAMMING)\n", + "else:\n", + " # Using CLP\n", + " solver = pywraplp.Solver('CoinsGridCLP',\n", + " pywraplp.Solver.CLP_LINEAR_PROGRAMMING)\n", + "\n", + "# data\n", + "rows = 3\n", + "cols = 3\n", + "\n", + "game = [[3.0, -1.0, -3.0], [-2.0, 4.0, -1.0], [-5.0, -6.0, 2.0]]\n", + "\n", + "#\n", + "# declare variables\n", + "#\n", + "\n", + "#\n", + "# row player\n", + "#\n", + "x1 = [solver.NumVar(0, 1, 'x1[%i]' % i) for i in range(rows)]\n", + "\n", + "v = solver.NumVar(-2, 2, 'v')\n", + "\n", + "for i in range(rows):\n", + " solver.Add(v - solver.Sum([x1[j] * game[j][i] for j in range(cols)]) <= 0)\n", + "\n", + "solver.Add(solver.Sum(x1) == 1)\n", + "\n", + "objective = solver.Maximize(v)\n", + "\n", + "solver.Solve()\n", + "\n", + "print()\n", + "print('row player:')\n", + "print('v = ', solver.Objective().Value())\n", + "print('Strategies: ')\n", + "for i in range(rows):\n", + " print(x1[i].SolutionValue(), end=' ')\n", + "print()\n", + "print()\n", + "\n", + "#\n", + "# For column player:\n", + "#\n", + "x2 = [solver.NumVar(0, 1, 'x2[%i]' % i) for i in range(cols)]\n", + "\n", + "v2 = solver.NumVar(-2, 2, 'v2')\n", + "\n", + "for i in range(cols):\n", + " solver.Add(v2 - solver.Sum([x2[j] * game[i][j] for j in range(rows)]) >= 0)\n", + "\n", + "solver.Add(solver.Sum(x2) == 1)\n", + "\n", + "objective = solver.Minimize(v2)\n", + "\n", + "solver.Solve()\n", + "\n", + "print()\n", + "print('column player:')\n", + "print('v2 = ', solver.Objective().Value())\n", + "print('Strategies: ')\n", + "for i in range(rows):\n", + " print(x2[i].SolutionValue(), end=' ')\n", + "print()\n", + "\n", + "print()\n", + "print('walltime :', solver.WallTime(), 'ms')\n", + "print('iterations:', solver.Iterations())\n", + "print()\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/contrib/grocery.ipynb b/examples/notebook/contrib/grocery.ipynb new file mode 100644 index 0000000000..314a631f4e --- /dev/null +++ b/examples/notebook/contrib/grocery.ipynb @@ -0,0 +1,104 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010 Hakan Kjellerstrand hakank@gmail.com\n", + "#\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"\n", + "\n", + " Grocery problem in Google CP Solver.\n", + "\n", + " From Christian Schulte, Gert Smolka, Finite Domain\n", + " http://www.mozart-oz.org/documentation/fdt/\n", + " Constraint Programming in Oz. A Tutorial. 2001.\n", + " '''\n", + " A kid goes into a grocery store and buys four items. The cashier\n", + " charges $7.11, the kid pays and is about to leave when the cashier\n", + " calls the kid back, and says 'Hold on, I multiplied the four items\n", + " instead of adding them; I'll try again; Hah, with adding them the\n", + " price still comes to $7.11'. What were the prices of the four items?\n", + " '''\n", + "\n", + " Compare with the following models:\n", + " * MiniZinc: http://hakank.org/minizinc/grocery.mzn\n", + " * Comet: http://hakank.org/comet/grocery.co\n", + " * Zinc: http://hakank.org/minizinc/grocery.zinc\n", + "\n", + " This model was created by Hakan Kjellerstrand (hakank@gmail.com)\n", + " Also see my other Google CP Solver models:\n", + " http://www.hakank.org/google_or_tools/\n", + "\"\"\"\n", + "from __future__ import print_function\n", + "import sys\n", + "\n", + "from ortools.constraint_solver import pywrapcp\n", + "from functools import reduce\n", + "\n", + "\n", + "\n", + "# Create the solver.\n", + "solver = pywrapcp.Solver(\"Grocery\")\n", + "\n", + "#\n", + "# data\n", + "#\n", + "n = 4\n", + "c = 711\n", + "\n", + "#\n", + "# declare variables\n", + "#\n", + "item = [solver.IntVar(0, c, \"item[%i]\" % i) for i in range(n)]\n", + "\n", + "#\n", + "# constraints\n", + "#\n", + "solver.Add(solver.Sum(item) == c)\n", + "solver.Add(reduce(lambda x, y: x * y, item) == c * 100**3)\n", + "\n", + "# symmetry breaking\n", + "for i in range(1, n):\n", + " solver.Add(item[i - 1] < item[i])\n", + "\n", + "#\n", + "# search and result\n", + "#\n", + "db = solver.Phase(item, solver.INT_VAR_SIMPLE, solver.INT_VALUE_SIMPLE)\n", + "\n", + "solver.NewSearch(db)\n", + "num_solutions = 0\n", + "while solver.NextSolution():\n", + " print(\"item:\", [item[i].Value() for i in range(n)])\n", + " print()\n", + " num_solutions += 1\n", + "\n", + "solver.EndSearch()\n", + "\n", + "print()\n", + "print(\"num_solutions:\", num_solutions)\n", + "print(\"failures:\", solver.Failures())\n", + "print(\"branches:\", solver.Branches())\n", + "print(\"WallTime:\", solver.WallTime())\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/contrib/hidato.ipynb b/examples/notebook/contrib/hidato.ipynb new file mode 100644 index 0000000000..ef68520ccc --- /dev/null +++ b/examples/notebook/contrib/hidato.ipynb @@ -0,0 +1,218 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010 Hakan Kjellerstrand hakank@gmail.com\n", + "#\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"\n", + " Hidato puzzle in Google CP Solver.\n", + "\n", + " http://www.shockwave.com/gamelanding/hidato.jsp\n", + " http://www.hidato.com/\n", + " '''\n", + " Puzzles start semi-filled with numbered tiles.\n", + " The first and last numbers are circled.\n", + " Connect the numbers together to win. Consecutive\n", + " number must touch horizontally, vertically, or\n", + " diagonally.\n", + " '''\n", + "\n", + " Compare with the following models:\n", + " * MiniZinc: http://www.hakank.org/minizinc/hidato.mzn\n", + " * Gecode : http://www.hakank.org/gecode/hidato.cpp\n", + " * Comet : http://www.hakank.org/comet/hidato.co\n", + " * Tailopr/Essence': http://hakank.org/tailor/hidato.eprime\n", + " * ECLiPSe: http://hakank.org/eclipse/hidato.ecl\n", + " * SICStus: http://hakank.org/sicstus/hidato.pl\n", + "\n", + " Note: This model is very slow. Please see Laurent Perron's much faster\n", + " (and more elegant) model: hidato_table.py .\n", + "\n", + " This model was created by Hakan Kjellerstrand (hakank@gmail.com)\n", + " Also see my other Google CP Solver models:\n", + " http://www.hakank.org/google_or_tools/\n", + "\"\"\"\n", + "from __future__ import print_function\n", + "import sys\n", + "from ortools.constraint_solver import pywrapcp\n", + "\n", + "\n", + "# Create the solver.\n", + "solver = pywrapcp.Solver(\"hidato\")\n", + "\n", + "# data\n", + "# Simple problem\n", + "if r == 3 and c == 3:\n", + " puzzle = [[6, 0, 9], [0, 2, 8], [1, 0, 0]]\n", + "\n", + "if r == 7 and c == 7:\n", + " puzzle = [[0, 44, 41, 0, 0, 0, 0], [0, 43, 0, 28, 29, 0, 0],\n", + " [0, 1, 0, 0, 0, 33, 0], [0, 2, 25, 4, 34, 0, 36],\n", + " [49, 16, 0, 23, 0, 0, 0], [0, 19, 0, 0, 12, 7, 0],\n", + " [0, 0, 0, 14, 0, 0, 0]]\n", + "\n", + "# Problems from the book:\n", + "# Gyora Bededek: \"Hidato: 2000 Pure Logic Puzzles\"\n", + "\n", + "# Problem 1 (Practice)\n", + "# r = 5\n", + "# c = r\n", + "# puzzle = [\n", + "# [ 0, 0,20, 0, 0],\n", + "# [ 0, 0, 0,16,18],\n", + "# [22, 0,15, 0, 0],\n", + "# [23, 0, 1,14,11],\n", + "# [ 0,25, 0, 0,12],\n", + "# ]\n", + "\n", + "# Problem 2 (Practice)\n", + "if r == 5 and c == 5:\n", + " puzzle = [\n", + " [0, 0, 0, 0, 14],\n", + " [0, 18, 12, 0, 0],\n", + " [0, 0, 17, 4, 5],\n", + " [0, 0, 7, 0, 0],\n", + " [9, 8, 25, 1, 0],\n", + " ]\n", + "\n", + "# Problem 3 (Beginner)\n", + "if r == 6 and c == 6:\n", + " puzzle = [[0, 26, 0, 0, 0, 18], [0, 0, 27, 0, 0, 19], [31, 23, 0, 0, 14, 0],\n", + " [0, 33, 8, 0, 15, 1], [0, 0, 0, 5, 0, 0], [35, 36, 0, 10, 0, 0]]\n", + "\n", + "# Problem 15 (Intermediate)\n", + "# Note: This takes very long time to solve...\n", + "if r == 8 and c == 8:\n", + " puzzle = [[64, 0, 0, 0, 0, 0, 0, 0], [1, 63, 0, 59, 15, 57, 53, 0],\n", + " [0, 4, 0, 14, 0, 0, 0, 0], [3, 0, 11, 0, 20, 19, 0, 50],\n", + " [0, 0, 0, 0, 22, 0, 48, 40], [9, 0, 0, 32, 23, 0, 0, 41],\n", + " [27, 0, 0, 0, 36, 0, 46, 0], [28, 30, 0, 35, 0, 0, 0, 0]]\n", + "\n", + "print_game(puzzle, r, c)\n", + "\n", + "#\n", + "# declare variables\n", + "#\n", + "x = {}\n", + "for i in range(r):\n", + " for j in range(c):\n", + " x[(i, j)] = solver.IntVar(1, r * c, \"dice(%i,%i)\" % (i, j))\n", + "x_flat = [x[(i, j)] for i in range(r) for j in range(c)]\n", + "\n", + "#\n", + "# constraints\n", + "#\n", + "solver.Add(solver.AllDifferent(x_flat))\n", + "\n", + "#\n", + "# Fill in the clues\n", + "#\n", + "for i in range(r):\n", + " for j in range(c):\n", + " if puzzle[i][j] > 0:\n", + " solver.Add(x[(i, j)] == puzzle[i][j])\n", + "\n", + "# From the numbers k = 1 to r*c-1, find this position,\n", + "# and then the position of k+1\n", + "for k in range(1, r * c):\n", + " i = solver.IntVar(0, r)\n", + " j = solver.IntVar(0, c)\n", + " a = solver.IntVar(-1, 1)\n", + " b = solver.IntVar(-1, 1)\n", + "\n", + " # 1) First: fix \"this\" k\n", + " # 2) and then find the position of the next value (k+1)\n", + " # solver.Add(k == x[(i,j)])\n", + " solver.Add(k == solver.Element(x_flat, i * c + j))\n", + " # solver.Add(k + 1 == x[(i+a,j+b)])\n", + " solver.Add(k + 1 == solver.Element(x_flat, (i + a) * c + (j + b)))\n", + "\n", + " solver.Add(i + a >= 0)\n", + " solver.Add(j + b >= 0)\n", + " solver.Add(i + a < r)\n", + " solver.Add(j + b < c)\n", + "\n", + " # solver.Add(((a != 0) | (b != 0)))\n", + " a_nz = solver.BoolVar()\n", + " b_nz = solver.BoolVar()\n", + " solver.Add(a_nz == solver.IsDifferentCstVar(a, 0))\n", + " solver.Add(b_nz == solver.IsDifferentCstVar(b, 0))\n", + " solver.Add(a_nz + b_nz >= 1)\n", + "\n", + "#\n", + "# solution and search\n", + "#\n", + "solution = solver.Assignment()\n", + "solution.Add(x_flat)\n", + "\n", + "# db: DecisionBuilder\n", + "db = solver.Phase(\n", + " x_flat,\n", + " # solver.INT_VAR_DEFAULT\n", + " # solver.INT_VAR_SIMPLE\n", + " # solver.CHOOSE_RANDOM\n", + " # solver.CHOOSE_MIN_SIZE_LOWEST_MIN\n", + " # solver.CHOOSE_MIN_SIZE_HIGHEST_MIN\n", + " # solver.CHOOSE_MIN_SIZE_LOWEST_MAX\n", + " # solver.CHOOSE_MIN_SIZE_HIGHEST_MAX\n", + " # solver.CHOOSE_PATH\n", + " solver.CHOOSE_FIRST_UNBOUND,\n", + " # solver.INT_VALUE_DEFAULT\n", + " # solver.INT_VALUE_SIMPLE\n", + " # solver.ASSIGN_MAX_VALUE\n", + " # solver.ASSIGN_RANDOM_VALUE\n", + " # solver.ASSIGN_CENTER_VALUE\n", + " solver.ASSIGN_MIN_VALUE)\n", + "\n", + "solver.NewSearch(db)\n", + "num_solutions = 0\n", + "while solver.NextSolution():\n", + " num_solutions += 1\n", + " print(\"\\nSolution:\", num_solutions)\n", + " print_board(x, r, c)\n", + " print()\n", + "\n", + "solver.EndSearch()\n", + "\n", + "print()\n", + "print(\"num_solutions:\", num_solutions)\n", + "print(\"failures:\", solver.Failures())\n", + "print(\"branches:\", solver.Branches())\n", + "print(\"WallTime:\", solver.WallTime())\n", + "\n", + "def print_board(x, rows, cols):\n", + " for i in range(rows):\n", + " for j in range(cols):\n", + " print(\"% 2s\" % x[i, j].Value(), end=\" \")\n", + " print(\"\")\n", + "\n", + "\n", + "def print_game(game, rows, cols):\n", + " for i in range(rows):\n", + " for j in range(cols):\n", + " print(\"% 2s\" % game[i][j], end=\" \")\n", + " print(\"\")\n", + "\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/contrib/just_forgotten.ipynb b/examples/notebook/contrib/just_forgotten.ipynb new file mode 100644 index 0000000000..f88d4c55a6 --- /dev/null +++ b/examples/notebook/contrib/just_forgotten.ipynb @@ -0,0 +1,125 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010 Hakan Kjellerstrand hakank@gmail.com\n", + "#\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"\n", + "\n", + " Just forgotten puzzle (Enigma 1517) in Google CP Solver.\n", + "\n", + " From http://www.f1compiler.com/samples/Enigma 201517.f1.html\n", + " '''\n", + " Enigma 1517 Bob Walker, New Scientist magazine, October 25, 2008.\n", + "\n", + " Joe was furious when he forgot one of his bank account numbers.\n", + " He remembered that it had all the digits 0 to 9 in some order,\n", + " so he tried the following four sets without success:\n", + "\n", + " 9 4 6 2 1 5 7 8 3 0\n", + " 8 6 0 4 3 9 1 2 5 7\n", + " 1 6 4 0 2 9 7 8 5 3\n", + " 6 8 2 4 3 1 9 0 7 5\n", + "\n", + " When Joe finally remembered his account number, he realised that\n", + " in each set just four of the digits were in their correct position\n", + " and that, if one knew that, it was possible to work out his\n", + " account number. What was it?\n", + " '''\n", + "\n", + " Compare with the following models:\n", + " * MiniZinc: http://www.hakank.org/minizinc/just_forgotten.mzn\n", + " * SICStus Prolog: http://www.hakank.org/sicstis/just_forgotten.pl\n", + " * ECLiPSe: http://hakank.org/eclipse/just_forgotten.ecl\n", + " * Gecpde: http://hakank.org/gecode/just_forgotten.cpp\n", + "\n", + " This model was created by Hakan Kjellerstrand (hakank@gmail.com)\n", + " Also see my other Google CP Solver models:\n", + " http://www.hakank.org/google_or_tools/\n", + "\"\"\"\n", + "from __future__ import print_function\n", + "from ortools.constraint_solver import pywrapcp\n", + "\n", + "\n", + "\n", + "# Create the solver.\n", + "solver = pywrapcp.Solver(\"Just forgotten\")\n", + "\n", + "#\n", + "# data\n", + "#\n", + "rows = 4\n", + "cols = 10\n", + "# The four tries\n", + "a = [[9, 4, 6, 2, 1, 5, 7, 8, 3, 0], [8, 6, 0, 4, 3, 9, 1, 2, 5, 7],\n", + " [1, 6, 4, 0, 2, 9, 7, 8, 5, 3], [6, 8, 2, 4, 3, 1, 9, 0, 7, 5]]\n", + "\n", + "#\n", + "# variables\n", + "#\n", + "x = [solver.IntVar(0, 9, \"x[%i]\" % j) for j in range(cols)]\n", + "\n", + "#\n", + "# constraints\n", + "#\n", + "solver.Add(solver.AllDifferent(x))\n", + "\n", + "for r in range(rows):\n", + " b = [solver.IsEqualCstVar(x[c], a[r][c]) for c in range(cols)]\n", + " solver.Add(solver.Sum(b) == 4)\n", + "\n", + "#\n", + "# search and result\n", + "#\n", + "db = solver.Phase(x, solver.INT_VAR_SIMPLE, solver.INT_VALUE_DEFAULT)\n", + "\n", + "solver.NewSearch(db)\n", + "\n", + "num_solutions = 0\n", + "while solver.NextSolution():\n", + " num_solutions += 1\n", + " xval = [x[j].Value() for j in range(cols)]\n", + " print(\"Account number:\")\n", + " for j in range(cols):\n", + " print(\"%i \" % xval[j], end=\" \")\n", + " print()\n", + " print(\"\\nThe four tries, where '!' represents a correct digit:\")\n", + " for i in range(rows):\n", + " for j in range(cols):\n", + " check = \" \"\n", + " if a[i][j] == xval[j]:\n", + " check = \"!\"\n", + " print(\"%i%s\" % (a[i][j], check), end=\" \")\n", + " print()\n", + " print()\n", + "print()\n", + "\n", + "solver.EndSearch()\n", + "\n", + "print(\"num_solutions:\", num_solutions)\n", + "print(\"failures:\", solver.Failures())\n", + "print(\"branches:\", solver.Branches())\n", + "print(\"WallTime:\", solver.WallTime())\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/contrib/kakuro.ipynb b/examples/notebook/contrib/kakuro.ipynb new file mode 100644 index 0000000000..8d286c0a92 --- /dev/null +++ b/examples/notebook/contrib/kakuro.ipynb @@ -0,0 +1,190 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010 Hakan Kjellerstrand hakank@gmail.com\n", + "#\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"\n", + "\n", + " Kakuru puzzle in Google CP Solver.\n", + "\n", + " http://en.wikipedia.org/wiki/Kakuro\n", + " '''\n", + " The object of the puzzle is to insert a digit from 1 to 9 inclusive\n", + " into each white cell such that the sum of the numbers in each entry\n", + " matches the clue associated with it and that no digit is duplicated in\n", + " any entry. It is that lack of duplication that makes creating Kakuro\n", + " puzzles with unique solutions possible, and which means solving a Kakuro\n", + " puzzle involves investigating combinations more, compared to Sudoku in\n", + " which the focus is on permutations. There is an unwritten rule for\n", + " making Kakuro puzzles that each clue must have at least two numbers\n", + " that add up to it. This is because including one number is mathematically\n", + " trivial when solving Kakuro puzzles; one can simply disregard the\n", + " number entirely and subtract it from the clue it indicates.\n", + " '''\n", + "\n", + " This model solves the problem at the Wikipedia page.\n", + " For a larger picture, see\n", + " http://en.wikipedia.org/wiki/File:Kakuro_black_box.svg\n", + "\n", + " The solution:\n", + " 9 7 0 0 8 7 9\n", + " 8 9 0 8 9 5 7\n", + " 6 8 5 9 7 0 0\n", + " 0 6 1 0 2 6 0\n", + " 0 0 4 6 1 3 2\n", + " 8 9 3 1 0 1 4\n", + " 3 1 2 0 0 2 1\n", + "\n", + " Compare with the following models:\n", + " * Comet : http://www.hakank.org/comet/kakuro.co\n", + " * MiniZinc: http://www.hakank.org/minizinc/kakuro.mzn\n", + " * SICStus : http://www.hakank.org/sicstus/kakuro.pl\n", + " * ECLiPSe: http://www.hakank.org/eclipse/kakuro.ecl\n", + " * Gecode: http://www.hakank.org/gecode/kenken2.cpp\n", + "\n", + " This model was created by Hakan Kjellerstrand (hakank@gmail.com)\n", + " Also see my other Google CP Solver models:\n", + " http://www.hakank.org/google_or_tools/\n", + "\"\"\"\n", + "from __future__ import print_function\n", + "import sys\n", + "\n", + "from ortools.constraint_solver import pywrapcp\n", + "\n", + "#\n", + "# Ensure that the sum of the segments\n", + "# in cc == res\n", + "#\n", + "\n", + "\n", + "def calc(cc, x, res):\n", + "\n", + " solver = list(x.values())[0].solver()\n", + "\n", + " # ensure that the values are positive\n", + " for i in cc:\n", + " solver.Add(x[i[0] - 1, i[1] - 1] >= 1)\n", + "\n", + " # sum the numbers\n", + " solver.Add(solver.Sum([x[i[0] - 1, i[1] - 1] for i in cc]) == res)\n", + "\n", + "\n", + "\n", + "# Create the solver.\n", + "solver = pywrapcp.Solver(\"Kakuro\")\n", + "\n", + "#\n", + "# data\n", + "#\n", + "\n", + "# size of matrix\n", + "n = 7\n", + "\n", + "# segments\n", + "# [sum, [segments]]\n", + "# Note: 1-based\n", + "problem = [[16, [1, 1], [1, 2]], [24, [1, 5], [1, 6], [1, 7]],\n", + " [17, [2, 1], [2, 2]], [29, [2, 4], [2, 5], [2, 6], [2, 7]],\n", + " [35, [3, 1], [3, 2], [3, 3], [3, 4], [3, 5]], [7, [4, 2], [4, 3]],\n", + " [8, [4, 5], [4, 6]], [16, [5, 3], [5, 4], [5, 5], [5, 6], [5, 7]],\n", + " [21, [6, 1], [6, 2], [6, 3], [6, 4]], [5, [6, 6], [6, 7]],\n", + " [6, [7, 1], [7, 2], [7, 3]], [3, [7, 6], [7, 7]],\n", + " [23, [1, 1], [2, 1], [3, 1]], [30, [1, 2], [2, 2], [3, 2], [4, 2]],\n", + " [27, [1, 5], [2, 5], [3, 5], [4, 5], [5, 5]], [12, [1, 6], [2, 6]],\n", + " [16, [1, 7], [2, 7]], [17, [2, 4], [3, 4]],\n", + " [15, [3, 3], [4, 3], [5, 3], [6, 3], [7, 3]],\n", + " [12, [4, 6], [5, 6], [6, 6], [7, 6]], [7, [5, 4], [6, 4]],\n", + " [7, [5, 7], [6, 7], [7, 7]], [11, [6, 1], [7, 1]],\n", + " [10, [6, 2], [7, 2]]]\n", + "\n", + "num_p = len(problem)\n", + "\n", + "# The blanks\n", + "# Note: 1-based\n", + "blanks = [[1, 3], [1, 4], [2, 3], [3, 6], [3, 7], [4, 1], [4, 4], [4, 7],\n", + " [5, 1], [5, 2], [6, 5], [7, 4], [7, 5]]\n", + "num_blanks = len(blanks)\n", + "\n", + "#\n", + "# variables\n", + "#\n", + "\n", + "# the set\n", + "x = {}\n", + "for i in range(n):\n", + " for j in range(n):\n", + " x[i, j] = solver.IntVar(0, 9, \"x[%i,%i]\" % (i, j))\n", + "\n", + "x_flat = [x[i, j] for i in range(n) for j in range(n)]\n", + "\n", + "#\n", + "# constraints\n", + "#\n", + "\n", + "# fill the blanks with 0\n", + "for i in range(num_blanks):\n", + " solver.Add(x[blanks[i][0] - 1, blanks[i][1] - 1] == 0)\n", + "\n", + "for i in range(num_p):\n", + " segment = problem[i][1::]\n", + " res = problem[i][0]\n", + "\n", + " # sum this segment\n", + " calc(segment, x, res)\n", + "\n", + " # all numbers in this segment must be distinct\n", + " segment = [x[p[0] - 1, p[1] - 1] for p in segment]\n", + " solver.Add(solver.AllDifferent(segment))\n", + "\n", + "#\n", + "# search and solution\n", + "#\n", + "db = solver.Phase(x_flat, solver.INT_VAR_DEFAULT, solver.INT_VALUE_DEFAULT)\n", + "\n", + "solver.NewSearch(db)\n", + "\n", + "num_solutions = 0\n", + "while solver.NextSolution():\n", + " for i in range(n):\n", + " for j in range(n):\n", + " val = x[i, j].Value()\n", + " if val > 0:\n", + " print(val, end=\" \")\n", + " else:\n", + " print(\" \", end=\" \")\n", + " print()\n", + "\n", + " print()\n", + " num_solutions += 1\n", + "\n", + "solver.EndSearch()\n", + "\n", + "print()\n", + "print(\"num_solutions:\", num_solutions)\n", + "print(\"failures:\", solver.Failures())\n", + "print(\"branches:\", solver.Branches())\n", + "print(\"WallTime:\", solver.WallTime())\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/contrib/kenken2.ipynb b/examples/notebook/contrib/kenken2.ipynb new file mode 100644 index 0000000000..b8a7cc99ac --- /dev/null +++ b/examples/notebook/contrib/kenken2.ipynb @@ -0,0 +1,205 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010 Hakan Kjellerstrand hakank@gmail.com\n", + "#\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"\n", + "\n", + " KenKen puzzle in Google CP Solver.\n", + "\n", + " http://en.wikipedia.org/wiki/KenKen\n", + " '''\n", + " KenKen or KEN-KEN is a style of arithmetic and logical puzzle sharing\n", + " several characteristics with sudoku. The name comes from Japanese and\n", + " is translated as 'square wisdom' or 'cleverness squared'.\n", + " ...\n", + " The objective is to fill the grid in with the digits 1 through 6 such that:\n", + "\n", + " * Each row contains exactly one of each digit\n", + " * Each column contains exactly one of each digit\n", + " * Each bold-outlined group of cells is a cage containing digits which\n", + " achieve the specified result using the specified mathematical operation:\n", + " addition (+),\n", + " subtraction (-),\n", + " multiplication (x),\n", + " and division (/).\n", + " (Unlike in Killer sudoku, digits may repeat within a group.)\n", + "\n", + " ...\n", + " More complex KenKen problems are formed using the principles described\n", + " above but omitting the symbols +, -, x and /, thus leaving them as\n", + " yet another unknown to be determined.\n", + " '''\n", + "\n", + "\n", + " The solution is:\n", + "\n", + " 5 6 3 4 1 2\n", + " 6 1 4 5 2 3\n", + " 4 5 2 3 6 1\n", + " 3 4 1 2 5 6\n", + " 2 3 6 1 4 5\n", + " 1 2 5 6 3 4\n", + "\n", + "\n", + "\n", + " This model was created by Hakan Kjellerstrand (hakank@gmail.com)\n", + " Also see my other Google CP Solver models:\n", + " http://www.hakank.org/google_or_tools/\n", + "\"\"\"\n", + "from __future__ import print_function\n", + "import sys\n", + "\n", + "from ortools.constraint_solver import pywrapcp\n", + "from functools import reduce\n", + "\n", + "#\n", + "# Ensure that the sum of the segments\n", + "# in cc == res\n", + "#\n", + "\n", + "\n", + "def calc(cc, x, res):\n", + "\n", + " solver = list(x.values())[0].solver()\n", + "\n", + " if len(cc) == 2:\n", + "\n", + " # for two operands there may be\n", + " # a lot of variants\n", + "\n", + " c00, c01 = cc[0]\n", + " c10, c11 = cc[1]\n", + " a = x[c00 - 1, c01 - 1]\n", + " b = x[c10 - 1, c11 - 1]\n", + "\n", + " r1 = solver.IsEqualCstVar(a + b, res)\n", + " r2 = solver.IsEqualCstVar(a * b, res)\n", + " r3 = solver.IsEqualVar(a * res, b)\n", + " r4 = solver.IsEqualVar(b * res, a)\n", + " r5 = solver.IsEqualCstVar(a - b, res)\n", + " r6 = solver.IsEqualCstVar(b - a, res)\n", + " solver.Add(r1 + r2 + r3 + r4 + r5 + r6 >= 1)\n", + "\n", + " else:\n", + "\n", + " # res is either sum or product of the segment\n", + "\n", + " xx = [x[i[0] - 1, i[1] - 1] for i in cc]\n", + "\n", + " # Sum\n", + " # # SumEquality don't work:\n", + " # this_sum = solver.SumEquality(xx, res)\n", + " this_sum = solver.IsEqualCstVar(solver.Sum(xx), res)\n", + "\n", + " # Product\n", + " # # Prod (or MakeProd) don't work:\n", + " # this_prod = solver.IsEqualCstVar(solver.Prod(xx), res)\n", + " this_prod = solver.IsEqualCstVar(reduce(lambda a, b: a * b, xx), res)\n", + " solver.Add(this_sum + this_prod >= 1)\n", + "\n", + "\n", + "\n", + "# Create the solver.\n", + "solver = pywrapcp.Solver(\"KenKen\")\n", + "\n", + "#\n", + "# data\n", + "#\n", + "\n", + "# size of matrix\n", + "n = 6\n", + "\n", + "# For a better view of the problem, see\n", + "# http://en.wikipedia.org/wiki/File:KenKenProblem.svg\n", + "\n", + "# hints\n", + "# [sum, [segments]]\n", + "# Note: 1-based\n", + "problem = [[11, [[1, 1], [2, 1]]], [2, [[1, 2], [1, 3]]],\n", + " [20, [[1, 4], [2, 4]]], [6, [[1, 5], [1, 6], [2, 6], [3, 6]]],\n", + " [3, [[2, 2], [2, 3]]], [3, [[2, 5], [3, 5]]],\n", + " [240, [[3, 1], [3, 2], [4, 1], [4, 2]]], [6, [[3, 3], [3, 4]]],\n", + " [6, [[4, 3], [5, 3]]], [7, [[4, 4], [5, 4], [5, 5]]],\n", + " [30, [[4, 5], [4, 6]]], [6, [[5, 1], [5, 2]]],\n", + " [9, [[5, 6], [6, 6]]], [8, [[6, 1], [6, 2], [6, 3]]],\n", + " [2, [[6, 4], [6, 5]]]]\n", + "\n", + "num_p = len(problem)\n", + "\n", + "#\n", + "# variables\n", + "#\n", + "\n", + "# the set\n", + "x = {}\n", + "for i in range(n):\n", + " for j in range(n):\n", + " x[i, j] = solver.IntVar(1, n, \"x[%i,%i]\" % (i, j))\n", + "\n", + "x_flat = [x[i, j] for i in range(n) for j in range(n)]\n", + "\n", + "#\n", + "# constraints\n", + "#\n", + "\n", + "# all rows and columns must be unique\n", + "for i in range(n):\n", + " row = [x[i, j] for j in range(n)]\n", + " solver.Add(solver.AllDifferent(row))\n", + "\n", + " col = [x[j, i] for j in range(n)]\n", + " solver.Add(solver.AllDifferent(col))\n", + "\n", + "# calculate the segments\n", + "for (res, segment) in problem:\n", + " calc(segment, x, res)\n", + "\n", + "#\n", + "# search and solution\n", + "#\n", + "db = solver.Phase(x_flat, solver.INT_VAR_DEFAULT, solver.INT_VALUE_DEFAULT)\n", + "\n", + "solver.NewSearch(db)\n", + "\n", + "num_solutions = 0\n", + "while solver.NextSolution():\n", + " for i in range(n):\n", + " for j in range(n):\n", + " print(x[i, j].Value(), end=\" \")\n", + " print()\n", + "\n", + " print()\n", + " num_solutions += 1\n", + "\n", + "solver.EndSearch()\n", + "\n", + "print()\n", + "print(\"num_solutions:\", num_solutions)\n", + "print(\"failures:\", solver.Failures())\n", + "print(\"branches:\", solver.Branches())\n", + "print(\"WallTime:\", solver.WallTime())\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/contrib/killer_sudoku.ipynb b/examples/notebook/contrib/killer_sudoku.ipynb new file mode 100644 index 0000000000..63711e0cb0 --- /dev/null +++ b/examples/notebook/contrib/killer_sudoku.ipynb @@ -0,0 +1,200 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010 Hakan Kjellerstrand hakank@gmail.com\n", + "#\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"\n", + "\n", + " Killer Sudoku in Google CP Solver.\n", + "\n", + " http://en.wikipedia.org/wiki/Killer_Sudoku\n", + " '''\n", + " Killer sudoku (also killer su doku, sumdoku, sum doku, addoku, or\n", + " samunamupure) is a puzzle that combines elements of sudoku and kakuro.\n", + " Despite the name, the simpler killer sudokus can be easier to solve\n", + " than regular sudokus, depending on the solver's skill at mental arithmetic;\n", + " the hardest ones, however, can take hours to crack.\n", + "\n", + " ...\n", + "\n", + " The objective is to fill the grid with numbers from 1 to 9 in a way that\n", + " the following conditions are met:\n", + "\n", + " * Each row, column, and nonet contains each number exactly once.\n", + " * The sum of all numbers in a cage must match the small number printed\n", + " in its corner.\n", + " * No number appears more than once in a cage. (This is the standard rule\n", + " for killer sudokus, and implies that no cage can include more\n", + " than 9 cells.)\n", + "\n", + " In 'Killer X', an additional rule is that each of the long diagonals\n", + " contains each number once.\n", + " '''\n", + "\n", + " Here we solve the problem from the Wikipedia page, also shown here\n", + " http://en.wikipedia.org/wiki/File:Killersudoku_color.svg\n", + "\n", + " The output is:\n", + " 2 1 5 6 4 7 3 9 8\n", + " 3 6 8 9 5 2 1 7 4\n", + " 7 9 4 3 8 1 6 5 2\n", + " 5 8 6 2 7 4 9 3 1\n", + " 1 4 2 5 9 3 8 6 7\n", + " 9 7 3 8 1 6 4 2 5\n", + " 8 2 1 7 3 9 5 4 6\n", + " 6 5 9 4 2 8 7 1 3\n", + " 4 3 7 1 6 5 2 8 9\n", + "\n", + "\n", + " Compare with the following models:\n", + " * Comet : http://www.hakank.org/comet/killer_sudoku.co\n", + " * MiniZinc: http://www.hakank.org/minizinc/killer_sudoku.mzn\n", + " * SICStus: http://www.hakank.org/sicstus/killer_sudoku.pl\n", + " * ECLiPSE: http://www.hakank.org/eclipse/killer_sudoku.ecl\n", + " * Gecode: http://www.hakank.org/gecode/killer_sudoku.cpp\n", + "\n", + "\n", + " This model was created by Hakan Kjellerstrand (hakank@gmail.com)\n", + " Also see my other Google CP Solver models:\n", + " http://www.hakank.org/google_or_tools/\n", + "\"\"\"\n", + "\n", + "from __future__ import print_function\n", + "import sys\n", + "from ortools.constraint_solver import pywrapcp\n", + "\n", + "#\n", + "# Ensure that the sum of the segments\n", + "# in cc == res\n", + "#\n", + "\n", + "\n", + "def calc(cc, x, res):\n", + "\n", + " solver = list(x.values())[0].solver()\n", + "\n", + " # sum the numbers\n", + " cage = [x[i[0] - 1, i[1] - 1] for i in cc]\n", + " solver.Add(solver.Sum(cage) == res)\n", + " solver.Add(solver.AllDifferent(cage))\n", + "\n", + "\n", + "\n", + "# Create the solver.\n", + "solver = pywrapcp.Solver(\"Killer Sudoku\")\n", + "\n", + "#\n", + "# data\n", + "#\n", + "\n", + "# size of matrix\n", + "n = 9\n", + "\n", + "# For a better view of the problem, see\n", + "# http://en.wikipedia.org/wiki/File:Killersudoku_color.svg\n", + "\n", + "# hints\n", + "# [sum, [segments]]\n", + "# Note: 1-based\n", + "problem = [[3, [[1, 1], [1, 2]]], [15, [[1, 3], [1, 4], [1, 5]]],\n", + " [22, [[1, 6], [2, 5], [2, 6], [3, 5]]], [4, [[1, 7], [2, 7]]],\n", + " [16, [[1, 8], [2, 8]]], [15, [[1, 9], [2, 9], [3, 9], [4, 9]]],\n", + " [25, [[2, 1], [2, 2], [3, 1], [3, 2]]], [17, [[2, 3], [2, 4]]],\n", + " [9, [[3, 3], [3, 4], [4, 4]]], [8, [[3, 6], [4, 6], [5, 6]]],\n", + " [20, [[3, 7], [3, 8], [4, 7]]], [6, [[4, 1], [5, 1]]],\n", + " [14, [[4, 2], [4, 3]]], [17, [[4, 5], [5, 5], [6, 5]]],\n", + " [17, [[4, 8], [5, 7], [5, 8]]], [13, [[5, 2], [5, 3], [6, 2]]],\n", + " [20, [[5, 4], [6, 4], [7, 4]]], [12, [[5, 9], [6, 9]]],\n", + " [27, [[6, 1], [7, 1], [8, 1], [9, 1]]],\n", + " [6, [[6, 3], [7, 2], [7, 3]]], [20, [[6, 6], [7, 6], [7, 7]]],\n", + " [6, [[6, 7], [6, 8]]], [10, [[7, 5], [8, 4], [8, 5], [9, 4]]],\n", + " [14, [[7, 8], [7, 9], [8, 8], [8, 9]]], [8, [[8, 2], [9, 2]]],\n", + " [16, [[8, 3], [9, 3]]], [15, [[8, 6], [8, 7]]],\n", + " [13, [[9, 5], [9, 6], [9, 7]]], [17, [[9, 8], [9, 9]]]]\n", + "\n", + "#\n", + "# variables\n", + "#\n", + "\n", + "# the set\n", + "x = {}\n", + "for i in range(n):\n", + " for j in range(n):\n", + " x[i, j] = solver.IntVar(1, n, \"x[%i,%i]\" % (i, j))\n", + "\n", + "x_flat = [x[i, j] for i in range(n) for j in range(n)]\n", + "\n", + "#\n", + "# constraints\n", + "#\n", + "\n", + "# all rows and columns must be unique\n", + "for i in range(n):\n", + " row = [x[i, j] for j in range(n)]\n", + " solver.Add(solver.AllDifferent(row))\n", + "\n", + " col = [x[j, i] for j in range(n)]\n", + " solver.Add(solver.AllDifferent(col))\n", + "\n", + "# cells\n", + "for i in range(2):\n", + " for j in range(2):\n", + " cell = [\n", + " x[r, c]\n", + " for r in range(i * 3, i * 3 + 3)\n", + " for c in range(j * 3, j * 3 + 3)\n", + " ]\n", + " solver.Add(solver.AllDifferent(cell))\n", + "\n", + "# calculate the segments\n", + "for (res, segment) in problem:\n", + " calc(segment, x, res)\n", + "\n", + "#\n", + "# search and solution\n", + "#\n", + "db = solver.Phase(x_flat, solver.INT_VAR_DEFAULT, solver.INT_VALUE_DEFAULT)\n", + "\n", + "solver.NewSearch(db)\n", + "\n", + "num_solutions = 0\n", + "while solver.NextSolution():\n", + " for i in range(n):\n", + " for j in range(n):\n", + " print(x[i, j].Value(), end=\" \")\n", + " print()\n", + "\n", + " print()\n", + " num_solutions += 1\n", + "\n", + "solver.EndSearch()\n", + "\n", + "print()\n", + "print(\"num_solutions:\", num_solutions)\n", + "print(\"failures:\", solver.Failures())\n", + "print(\"branches:\", solver.Branches())\n", + "print(\"WallTime:\", solver.WallTime())\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/contrib/knapsack_cp.ipynb b/examples/notebook/contrib/knapsack_cp.ipynb new file mode 100644 index 0000000000..85f49d7355 --- /dev/null +++ b/examples/notebook/contrib/knapsack_cp.ipynb @@ -0,0 +1,102 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010 Hakan Kjellerstrand hakank@gmail.com\n", + "#\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"\n", + "\n", + " Knapsack problem in Google CP Solver.\n", + "\n", + " Simple knapsack problem.\n", + "\n", + " This model was created by Hakan Kjellerstrand (hakank@gmail.com)\n", + " Also see my other Google CP Solver models:\n", + " http://www.hakank.org/google_or_tools/\n", + "\"\"\"\n", + "from __future__ import print_function\n", + "from ortools.constraint_solver import pywrapcp\n", + "\n", + "\n", + "def knapsack(solver, values, weights, n):\n", + " z = solver.IntVar(0, 10000)\n", + " x = [solver.IntVar(0, 1, \"x(%i)\" % i) for i in range(len(values))]\n", + " solver.Add(z >= 0)\n", + " solver.Add(z == solver.ScalProd(x, values))\n", + " solver.Add(solver.ScalProd(x, weights) <= n)\n", + "\n", + " return [x, z]\n", + "\n", + "\n", + "# Create the solver.\n", + "solver = pywrapcp.Solver(\"knapsack_cp\")\n", + "\n", + "#\n", + "# data\n", + "#\n", + "print(\"values:\", values)\n", + "print(\"weights:\", weights)\n", + "print(\"n:\", n)\n", + "print()\n", + "\n", + "# declare variables\n", + "\n", + "#\n", + "# constraints\n", + "#\n", + "[x, z] = knapsack(solver, values, weights, n)\n", + "\n", + "# objective\n", + "objective = solver.Maximize(z, 1)\n", + "\n", + "#\n", + "# solution and search\n", + "#\n", + "solution = solver.Assignment()\n", + "solution.Add(x)\n", + "solution.Add(z)\n", + "\n", + "# db: DecisionBuilder\n", + "db = solver.Phase(x, solver.CHOOSE_FIRST_UNBOUND, solver.ASSIGN_MAX_VALUE)\n", + "\n", + "solver.NewSearch(db, [objective])\n", + "num_solutions = 0\n", + "while solver.NextSolution():\n", + " print(\"x:\", [x[i].Value() for i in range(len(values))])\n", + " print(\"z:\", z.Value())\n", + " print()\n", + " num_solutions += 1\n", + "solver.EndSearch()\n", + "\n", + "print()\n", + "print(\"num_solutions:\", num_solutions)\n", + "print(\"failures:\", solver.Failures())\n", + "print(\"branches:\", solver.Branches())\n", + "print(\"WallTime:\", solver.WallTime())\n", + "\n", + "values = [15, 100, 90, 60, 40, 15, 10, 1, 12, 12, 100]\n", + "weights = [2, 20, 20, 30, 40, 30, 60, 10, 21, 12, 2]\n", + "n = 102\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/contrib/knapsack_mip.ipynb b/examples/notebook/contrib/knapsack_mip.ipynb new file mode 100644 index 0000000000..9e0fbd7417 --- /dev/null +++ b/examples/notebook/contrib/knapsack_mip.ipynb @@ -0,0 +1,112 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2011 Hakan Kjellerstrand hakank@gmail.com\n", + "#\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"\n", + "\n", + " Knapsack problem using MIP in Google or-tools.\n", + "\n", + " From the OPL model knapsack.mod\n", + "\n", + " This model was created by Hakan Kjellerstrand (hakank@gmail.com)\n", + " Also see my other Google CP Solver models:\n", + " http://www.hakank.org/google_or_tools/\n", + "\"\"\"\n", + "from __future__ import print_function\n", + "import sys\n", + "from ortools.linear_solver import pywraplp\n", + "\n", + "\n", + "\n", + "# Create the solver.\n", + "\n", + "print('Solver: ', sol)\n", + "\n", + "# using GLPK\n", + "if sol == 'GLPK':\n", + " solver = pywraplp.Solver('CoinsGridGLPK',\n", + " pywraplp.Solver.GLPK_MIXED_INTEGER_PROGRAMMING)\n", + "else:\n", + " # Using CBC\n", + " solver = pywraplp.Solver('CoinsGridCBC',\n", + " pywraplp.Solver.CBC_MIXED_INTEGER_PROGRAMMING)\n", + "\n", + "#\n", + "# data\n", + "#\n", + "nb_items = 12\n", + "nb_resources = 7\n", + "items = list(range(nb_items))\n", + "resources = list(range(nb_resources))\n", + "\n", + "capacity = [18209, 7692, 1333, 924, 26638, 61188, 13360]\n", + "value = [96, 76, 56, 11, 86, 10, 66, 86, 83, 12, 9, 81]\n", + "use = [[19, 1, 10, 1, 1, 14, 152, 11, 1, 1, 1, 1],\n", + " [0, 4, 53, 0, 0, 80, 0, 4, 5, 0, 0, 0],\n", + " [4, 660, 3, 0, 30, 0, 3, 0, 4, 90, 0, 0],\n", + " [7, 0, 18, 6, 770, 330, 7, 0, 0, 6, 0, 0],\n", + " [0, 20, 0, 4, 52, 3, 0, 0, 0, 5, 4, 0],\n", + " [0, 0, 40, 70, 4, 63, 0, 0, 60, 0, 4, 0],\n", + " [0, 32, 0, 0, 0, 5, 0, 3, 0, 660, 0, 9]]\n", + "\n", + "max_value = max(capacity)\n", + "\n", + "#\n", + "# variables\n", + "#\n", + "take = [solver.IntVar(0, max_value, 'take[%i]' % j) for j in items]\n", + "\n", + "# total cost, to be maximized\n", + "z = solver.Sum([value[i] * take[i] for i in items])\n", + "\n", + "#\n", + "# constraints\n", + "#\n", + "for r in resources:\n", + " solver.Add(solver.Sum([use[r][i] * take[i] for i in items]) <= capacity[r])\n", + "\n", + "# objective\n", + "objective = solver.Maximize(z)\n", + "\n", + "#\n", + "# solution and search\n", + "#\n", + "solver.Solve()\n", + "\n", + "print()\n", + "print('z: ', int(solver.Objective().Value()))\n", + "\n", + "print('take:', end=' ')\n", + "for i in items:\n", + " print(int(take[i].SolutionValue()), end=' ')\n", + "print()\n", + "\n", + "print()\n", + "print('walltime :', solver.WallTime(), 'ms')\n", + "if sol == 'CBC':\n", + " print('iterations:', solver.Iterations())\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/contrib/labeled_dice.ipynb b/examples/notebook/contrib/labeled_dice.ipynb new file mode 100644 index 0000000000..e674544374 --- /dev/null +++ b/examples/notebook/contrib/labeled_dice.ipynb @@ -0,0 +1,147 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010 Hakan Kjellerstrand hakank@gmail.com\n", + "#\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"\n", + "\n", + " Labeled dice problem in Google CP Solver.\n", + "\n", + " From Jim Orlin 'Colored letters, labeled dice: a logic puzzle'\n", + " http://jimorlin.wordpress.com/2009/02/17/colored-letters-labeled-dice-a-logic-puzzle/\n", + " '''\n", + " My daughter Jenn bough a puzzle book, and showed me a cute puzzle. There\n", + " are 13 words as follows: BUOY, CAVE, CELT, FLUB, FORK, HEMP, JUDY,\n", + " JUNK, LIMN, QUIP, SWAG, VISA, WISH.\n", + "\n", + " There are 24 different letters that appear in the 13 words. The question\n", + " is: can one assign the 24 letters to 4 different cubes so that the\n", + " four letters of each word appears on different cubes. (There is one\n", + " letter from each word on each cube.) It might be fun for you to try\n", + " it. I'll give a small hint at the end of this post. The puzzle was\n", + " created by Humphrey Dudley.\n", + " '''\n", + "\n", + " Jim Orlin's followup 'Update on Logic Puzzle':\n", + " http://jimorlin.wordpress.com/2009/02/21/update-on-logic-puzzle/\n", + "\n", + "\n", + " Compare with the following models:\n", + " * ECLiPSe: http://hakank.org/eclipse/labeled_dice.ecl\n", + " * Comet : http://www.hakank.org/comet/labeled_dice.co\n", + " * Gecode : http://hakank.org/gecode/labeled_dice.cpp\n", + " * SICStus: http://hakank.org/sicstus/labeled_dice.pl\n", + " * Zinc : http://hakank.org/minizinc/labeled_dice.zinc\n", + "\n", + "\n", + " This model was created by Hakan Kjellerstrand (hakank@gmail.com)\n", + " Also see my other Google CP Solver models:\n", + " http://www.hakank.org/google_or_tools/\n", + "\"\"\"\n", + "from __future__ import print_function\n", + "from ortools.constraint_solver import pywrapcp\n", + "\n", + "\n", + "\n", + "# Create the solver.\n", + "solver = pywrapcp.Solver(\"Labeled dice\")\n", + "\n", + "#\n", + "# data\n", + "#\n", + "n = 4\n", + "m = 24\n", + "A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, Y = (\n", + " list(range(m)))\n", + "letters = [\n", + " \"A\", \"B\", \"C\", \"D\", \"E\", \"F\", \"G\", \"H\", \"I\", \"J\", \"K\", \"L\", \"M\", \"N\", \"O\",\n", + " \"P\", \"Q\", \"R\", \"S\", \"T\", \"U\", \"V\", \"W\", \"Y\"\n", + "]\n", + "\n", + "num_words = 13\n", + "words = [[B, U, O, Y], [C, A, V, E], [C, E, L, T], [F, L, U, B], [F, O, R, K],\n", + " [H, E, M, P], [J, U, D, Y], [J, U, N, K], [L, I, M, N], [Q, U, I, P],\n", + " [S, W, A, G], [V, I, S, A], [W, I, S, H]]\n", + "\n", + "#\n", + "# declare variables\n", + "#\n", + "dice = [solver.IntVar(0, n - 1, \"dice[%i]\" % i) for i in range(m)]\n", + "\n", + "#\n", + "# constraints\n", + "#\n", + "\n", + "# the letters in a word must be on a different die\n", + "for i in range(num_words):\n", + " solver.Add(solver.AllDifferent([dice[words[i][j]] for j in range(n)]))\n", + "\n", + "# there must be exactly 6 letters of each die\n", + "for i in range(n):\n", + " b = [solver.IsEqualCstVar(dice[j], i) for j in range(m)]\n", + " solver.Add(solver.Sum(b) == 6)\n", + "\n", + "#\n", + "# solution and search\n", + "#\n", + "solution = solver.Assignment()\n", + "solution.Add(dice)\n", + "\n", + "db = solver.Phase(dice, solver.CHOOSE_FIRST_UNBOUND, solver.ASSIGN_MIN_VALUE)\n", + "\n", + "#\n", + "# result\n", + "#\n", + "solver.NewSearch(db)\n", + "num_solutions = 0\n", + "while solver.NextSolution():\n", + " num_solutions += 1\n", + " # print \"dice:\", [(letters[i],dice[i].Value()) for i in range(m)]\n", + " for d in range(n):\n", + " print(\"die %i:\" % d, end=\" \")\n", + " for i in range(m):\n", + " if dice[i].Value() == d:\n", + " print(letters[i], end=\" \")\n", + " print()\n", + "\n", + " print(\"The words with the cube label:\")\n", + " for i in range(num_words):\n", + " for j in range(n):\n", + " print(\n", + " \"%s (%i)\" % (letters[words[i][j]], dice[words[i][j]].Value()),\n", + " end=\" \")\n", + " print()\n", + "\n", + " print()\n", + "\n", + "solver.EndSearch()\n", + "\n", + "print()\n", + "print(\"num_solutions:\", num_solutions)\n", + "print(\"failures:\", solver.Failures())\n", + "print(\"branches:\", solver.Branches())\n", + "print(\"WallTime:\", solver.WallTime())\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/contrib/langford.ipynb b/examples/notebook/contrib/langford.ipynb new file mode 100644 index 0000000000..b2bef1022f --- /dev/null +++ b/examples/notebook/contrib/langford.ipynb @@ -0,0 +1,119 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010 Hakan Kjellerstrand hakank@gmail.com\n", + "#\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"\n", + "\n", + " Langford's number problem in Google CP Solver.\n", + "\n", + " Langford's number problem (CSP lib problem 24)\n", + " http://www.csplib.org/prob/prob024/\n", + " '''\n", + " Arrange 2 sets of positive integers 1..k to a sequence,\n", + " such that, following the first occurence of an integer i,\n", + " each subsequent occurrence of i, appears i+1 indices later\n", + " than the last.\n", + " For example, for k=4, a solution would be 41312432\n", + " '''\n", + "\n", + " * John E. Miller: Langford's Problem\n", + " http://www.lclark.edu/~miller/langford.html\n", + "\n", + " * Encyclopedia of Integer Sequences for the number of solutions for each k\n", + " http://www.research.att.com/cgi-bin/access.cgi/as/njas/sequences/eisA.cgi?Anum=014552\n", + "\n", + "\n", + " Also, see the following models:\n", + " * MiniZinc: http://www.hakank.org/minizinc/langford2.mzn\n", + " * Gecode/R: http://www.hakank.org/gecode_r/langford.rb\n", + " * ECLiPSe: http://hakank.org/eclipse/langford.ecl\n", + " * SICStus: http://hakank.org/sicstus/langford.pl\n", + "\n", + "\n", + " This model was created by Hakan Kjellerstrand (hakank@gmail.com)\n", + " Also see my other Google CP Solver models:\n", + " http://www.hakank.org/google_or_tools/\n", + "\"\"\"\n", + "from __future__ import print_function\n", + "import sys\n", + "\n", + "from ortools.constraint_solver import pywrapcp\n", + "\n", + "\n", + "\n", + "# Create the solver.\n", + "solver = pywrapcp.Solver(\"Langford\")\n", + "\n", + "#\n", + "# data\n", + "#\n", + "print(\"k:\", k)\n", + "p = list(range(2 * k))\n", + "\n", + "#\n", + "# declare variables\n", + "#\n", + "position = [solver.IntVar(0, 2 * k - 1, \"position[%i]\" % i) for i in p]\n", + "solution = [solver.IntVar(1, k, \"position[%i]\" % i) for i in p]\n", + "\n", + "#\n", + "# constraints\n", + "#\n", + "solver.Add(solver.AllDifferent(position))\n", + "\n", + "for i in range(1, k + 1):\n", + " solver.Add(position[i + k - 1] == position[i - 1] + i + 1)\n", + " solver.Add(solver.Element(solution, position[i - 1]) == i)\n", + " solver.Add(solver.Element(solution, position[k + i - 1]) == i)\n", + "\n", + "# symmetry breaking\n", + "solver.Add(solution[0] < solution[2 * k - 1])\n", + "\n", + "#\n", + "# search and result\n", + "#\n", + "db = solver.Phase(position, solver.CHOOSE_FIRST_UNBOUND,\n", + " solver.ASSIGN_MIN_VALUE)\n", + "\n", + "solver.NewSearch(db)\n", + "num_solutions = 0\n", + "while solver.NextSolution():\n", + " print(\"solution:\", \",\".join([str(solution[i].Value()) for i in p]))\n", + " num_solutions += 1\n", + " if num_sol > 0 and num_solutions >= num_sol:\n", + " break\n", + "\n", + "solver.EndSearch()\n", + "\n", + "print()\n", + "print(\"num_solutions:\", num_solutions)\n", + "print(\"failures:\", solver.Failures())\n", + "print(\"branches:\", solver.Branches())\n", + "print(\"WallTime:\", solver.WallTime())\n", + "\n", + "k = 8\n", + "num_sol = 0\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/contrib/least_diff.ipynb b/examples/notebook/contrib/least_diff.ipynb new file mode 100644 index 0000000000..1386488066 --- /dev/null +++ b/examples/notebook/contrib/least_diff.ipynb @@ -0,0 +1,126 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010 Hakan Kjellerstrand hakank@gmail.com\n", + "#\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"\n", + "\n", + " Least diff problem in Google CP Solver.\n", + "\n", + " This model solves the following problem:\n", + "\n", + " What is the smallest difference between two numbers X - Y\n", + " if you must use all the digits (0..9) exactly once.\n", + "\n", + " Compare with the following models:\n", + " * Choco : http://www.hakank.org/choco/LeastDiff2.java\n", + " * ECLiPSE : http://www.hakank.org/eclipse/least_diff2.ecl\n", + " * Comet : http://www.hakank.org/comet/least_diff.co\n", + " * Tailor/Essence': http://www.hakank.org/tailor/leastDiff.eprime\n", + " * Gecode : http://www.hakank.org/gecode/least_diff.cpp\n", + " * Gecode/R: http://www.hakank.org/gecode_r/least_diff.rb\n", + " * JaCoP : http://www.hakank.org/JaCoP/LeastDiff.java\n", + " * MiniZinc: http://www.hakank.org/minizinc/least_diff.mzn\n", + " * SICStus : http://www.hakank.org/sicstus/least_diff.pl\n", + " * Zinc : http://hakank.org/minizinc/least_diff.zinc\n", + "\n", + " This model was created by Hakan Kjellerstrand (hakank@gmail.com)\n", + " Also see my other Google CP Solver models:\n", + " http://www.hakank.org/google_cp_solver/\n", + "\"\"\"\n", + "from __future__ import print_function\n", + "from ortools.constraint_solver import pywrapcp\n", + "\n", + "\n", + "# Create the solver.\n", + "solver = pywrapcp.Solver(\"Least diff\")\n", + "\n", + "#\n", + "# declare variables\n", + "#\n", + "digits = list(range(0, 10))\n", + "a = solver.IntVar(digits, \"a\")\n", + "b = solver.IntVar(digits, \"b\")\n", + "c = solver.IntVar(digits, \"c\")\n", + "d = solver.IntVar(digits, \"d\")\n", + "e = solver.IntVar(digits, \"e\")\n", + "\n", + "f = solver.IntVar(digits, \"f\")\n", + "g = solver.IntVar(digits, \"g\")\n", + "h = solver.IntVar(digits, \"h\")\n", + "i = solver.IntVar(digits, \"i\")\n", + "j = solver.IntVar(digits, \"j\")\n", + "\n", + "letters = [a, b, c, d, e, f, g, h, i, j]\n", + "\n", + "digit_vector = [10000, 1000, 100, 10, 1]\n", + "x = solver.ScalProd(letters[0:5], digit_vector)\n", + "y = solver.ScalProd(letters[5:], digit_vector)\n", + "diff = x - y\n", + "\n", + "#\n", + "# constraints\n", + "#\n", + "solver.Add(diff > 0)\n", + "solver.Add(solver.AllDifferent(letters))\n", + "\n", + "# objective\n", + "objective = solver.Minimize(diff, 1)\n", + "\n", + "#\n", + "# solution\n", + "#\n", + "solution = solver.Assignment()\n", + "solution.Add(letters)\n", + "solution.Add(x)\n", + "solution.Add(y)\n", + "solution.Add(diff)\n", + "\n", + "# last solution since it's a minimization problem\n", + "collector = solver.LastSolutionCollector(solution)\n", + "search_log = solver.SearchLog(100, diff)\n", + "# Note: I'm not sure what CHOOSE_PATH do, but it is fast:\n", + "# find the solution in just 4 steps\n", + "solver.Solve(\n", + " solver.Phase(letters, solver.CHOOSE_PATH, solver.ASSIGN_MIN_VALUE),\n", + " [objective, search_log, collector])\n", + "\n", + "# get the first (and only) solution\n", + "\n", + "xval = collector.Value(0, x)\n", + "yval = collector.Value(0, y)\n", + "diffval = collector.Value(0, diff)\n", + "print(\"x:\", xval)\n", + "print(\"y:\", yval)\n", + "print(\"diff:\", diffval)\n", + "print(xval, \"-\", yval, \"=\", diffval)\n", + "print([(\"abcdefghij\" [i], collector.Value(0, letters[i])) for i in range(10)])\n", + "print()\n", + "print(\"failures:\", solver.Failures())\n", + "print(\"branches:\", solver.Branches())\n", + "print(\"WallTime:\", solver.WallTime())\n", + "print()\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/contrib/least_square.ipynb b/examples/notebook/contrib/least_square.ipynb new file mode 100644 index 0000000000..b7bd8ace59 --- /dev/null +++ b/examples/notebook/contrib/least_square.ipynb @@ -0,0 +1,104 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2011 Hakan Kjellerstrand hakank@gmail.com\n", + "#\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"\n", + "\n", + " Least square optimization problem in Google or-tools.\n", + "\n", + " Solving a fourth grade least square equation.\n", + "\n", + " From the Swedish book 'Optimeringslara' [Optimization Theory],\n", + " page 286f.\n", + "\n", + " This model was created by Hakan Kjellerstrand (hakank@gmail.com)\n", + " Also see my other Google CP Solver models:\n", + " http://www.hakank.org/google_or_tools/\n", + "\"\"\"\n", + "from __future__ import print_function\n", + "import sys\n", + "from ortools.linear_solver import pywraplp\n", + "\n", + "\n", + "\n", + "# Create the solver.\n", + "\n", + "# using GLPK\n", + "if sol == 'GLPK':\n", + " solver = pywraplp.Solver('CoinsGridGLPK',\n", + " pywraplp.Solver.GLPK_LINEAR_PROGRAMMING)\n", + "else:\n", + " # Using CLP\n", + " solver = pywraplp.Solver('CoinsGridCLP',\n", + " pywraplp.Solver.CLP_LINEAR_PROGRAMMING)\n", + "\n", + "# data\n", + "# number of points\n", + "num = 14\n", + "\n", + "# temperature\n", + "t = [20, 30, 80, 125, 175, 225, 275, 325, 360, 420, 495, 540, 630, 700]\n", + "\n", + "# percentage gas\n", + "F = [\n", + " 0.0, 5.8, 14.7, 31.6, 43.2, 58.3, 78.4, 89.4, 96.4, 99.1, 99.5, 99.9,\n", + " 100.0, 100.0\n", + "]\n", + "\n", + "p = 4\n", + "\n", + "#\n", + "# declare variables\n", + "#\n", + "a = [solver.NumVar(-100, 100, 'a[%i]' % i) for i in range(p + 1)]\n", + "\n", + "# to minimize\n", + "z = solver.Sum([\n", + " (F[i] - (sum([a[j] * t[i]**j for j in range(p + 1)]))) for i in range(num)\n", + "])\n", + "\n", + "#\n", + "# constraints\n", + "#\n", + "solver.Add(solver.Sum([20**i * a[i] for i in range(p + 1)]) == 0)\n", + "\n", + "solver.Add((a[0] + sum([700.0**j * a[j] for j in range(1, p + 1)])) == 100.0)\n", + "\n", + "for i in range(num):\n", + " solver.Add(\n", + " solver.Sum([j * a[j] * t[i]**(j - 1) for j in range(p + 1)]) >= 0)\n", + "\n", + "objective = solver.Minimize(z)\n", + "\n", + "solver.Solve()\n", + "\n", + "print()\n", + "print('z = ', solver.Objective().Value())\n", + "for i in range(p + 1):\n", + " print(a[i].SolutionValue(), end=' ')\n", + "print()\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/contrib/lectures.ipynb b/examples/notebook/contrib/lectures.ipynb new file mode 100644 index 0000000000..cb355012a6 --- /dev/null +++ b/examples/notebook/contrib/lectures.ipynb @@ -0,0 +1,134 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010 Hakan Kjellerstrand hakank@gmail.com\n", + "#\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"\n", + "\n", + " Lectures problem in Google CP Solver.\n", + "\n", + " Biggs: Discrete Mathematics (2nd ed), page 187.\n", + " '''\n", + " Suppose we wish to schedule six one-hour lectures, v1, v2, v3, v4, v5, v6.\n", + " Among the the potential audience there are people who wish to hear both\n", + "\n", + " - v1 and v2\n", + " - v1 and v4\n", + " - v3 and v5\n", + " - v2 and v6\n", + " - v4 and v5\n", + " - v5 and v6\n", + " - v1 and v6\n", + "\n", + " How many hours are necessary in order that the lectures can be given\n", + " without clashes?\n", + " '''\n", + "\n", + " Compare with the following models:\n", + " * MiniZinc: http://www.hakank.org/minizinc/lectures.mzn\n", + " * SICstus: http://hakank.org/sicstus/lectures.pl\n", + " * ECLiPSe: http://hakank.org/eclipse/lectures.ecl\n", + " * Gecode: http://hakank.org/gecode/lectures.cpp\n", + "\n", + "\n", + " This model was created by Hakan Kjellerstrand (hakank@gmail.com)\n", + " Also see my other Google CP Solver models:\n", + " http://www.hakank.org/google_or_tools/\n", + "\"\"\"\n", + "from __future__ import print_function\n", + "import sys\n", + "from ortools.constraint_solver import pywrapcp\n", + "\n", + "\n", + "\n", + "# Create the solver.\n", + "solver = pywrapcp.Solver('Lectures')\n", + "\n", + "#\n", + "# data\n", + "#\n", + "\n", + "#\n", + "# The schedule requirements:\n", + "# lecture a cannot be held at the same time as b\n", + "# Note: 1-based\n", + "g = [[1, 2], [1, 4], [3, 5], [2, 6], [4, 5], [5, 6], [1, 6]]\n", + "\n", + "# number of nodes\n", + "n = 6\n", + "\n", + "# number of edges\n", + "edges = len(g)\n", + "\n", + "#\n", + "# declare variables\n", + "#\n", + "v = [solver.IntVar(0, n - 1, 'v[%i]' % i) for i in range(n)]\n", + "\n", + "# maximum color, to minimize\n", + "# Note: since Python is 0-based, the\n", + "# number of colors is +1\n", + "max_c = solver.IntVar(0, n - 1, 'max_c')\n", + "\n", + "#\n", + "# constraints\n", + "#\n", + "solver.Add(max_c == solver.Max(v))\n", + "\n", + "# ensure that there are no clashes\n", + "# also, adjust to 0-base\n", + "for i in range(edges):\n", + " solver.Add(v[g[i][0] - 1] != v[g[i][1] - 1])\n", + "\n", + "# symmetry breaking:\n", + "# - v0 has the color 0,\n", + "# - v1 has either color 0 or 1\n", + "solver.Add(v[0] == 0)\n", + "solver.Add(v[1] <= 1)\n", + "\n", + "# objective\n", + "objective = solver.Minimize(max_c, 1)\n", + "\n", + "#\n", + "# solution and search\n", + "#\n", + "db = solver.Phase(v, solver.CHOOSE_MIN_SIZE_LOWEST_MIN,\n", + " solver.ASSIGN_CENTER_VALUE)\n", + "\n", + "solver.NewSearch(db, [objective])\n", + "\n", + "num_solutions = 0\n", + "while solver.NextSolution():\n", + " num_solutions += 1\n", + " print('max_c:', max_c.Value() + 1, 'colors')\n", + " print('v:', [v[i].Value() for i in range(n)])\n", + " print()\n", + "\n", + "print('num_solutions:', num_solutions)\n", + "print('failures:', solver.Failures())\n", + "print('branches:', solver.Branches())\n", + "print('WallTime:', solver.WallTime(), 'ms')\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/contrib/magic_sequence_sat.ipynb b/examples/notebook/contrib/magic_sequence_sat.ipynb new file mode 100644 index 0000000000..3dd902f022 --- /dev/null +++ b/examples/notebook/contrib/magic_sequence_sat.ipynb @@ -0,0 +1,65 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2018 Gergo Rozner\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http:#www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"Solve the magic sequence problem with the CP-SAT solver.\"\"\"\n", + "\n", + "from __future__ import absolute_import\n", + "from __future__ import division\n", + "from __future__ import print_function\n", + "\n", + "from ortools.sat.python import cp_model\n", + "\n", + "\n", + "\"\"\"Magic sequence problem.\"\"\"\n", + "n = 100\n", + "values = range(n)\n", + "\n", + "model = cp_model.CpModel()\n", + "\n", + "x = [model.NewIntVar(0, n, 'x%i' % i) for i in values]\n", + "\n", + "for k in values:\n", + " tmp_array = []\n", + " for i in values:\n", + " tmp_var = model.NewBoolVar('')\n", + " model.Add(x[i] == k).OnlyEnforceIf(tmp_var)\n", + " model.Add(x[i] != k).OnlyEnforceIf(tmp_var.Not())\n", + " tmp_array.append(tmp_var)\n", + " model.Add(sum(tmp_array) == x[k])\n", + "\n", + "# Redundant constraint.\n", + "model.Add(sum(x) == n)\n", + "\n", + "solver = cp_model.CpSolver()\n", + "# No solution printer, this problem has only 1 solution.\n", + "solver.parameters.log_search_progress = True\n", + "solver.Solve(model)\n", + "print(solver.ResponseStats())\n", + "for k in values:\n", + " print('x[%i] = %i ' % (k, solver.Value(x[k])), end='')\n", + "print()\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/contrib/magic_square.ipynb b/examples/notebook/contrib/magic_square.ipynb new file mode 100644 index 0000000000..f99235c511 --- /dev/null +++ b/examples/notebook/contrib/magic_square.ipynb @@ -0,0 +1,119 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010 Hakan Kjellerstrand hakank@gmail.com\n", + "#\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"\n", + "\n", + " Magic squares in Google CP Solver.\n", + "\n", + " Magic square problem.\n", + "\n", + " This model was created by Hakan Kjellerstrand (hakank@gmail.com)\n", + " Also see my other Google CP Solver models:\n", + " http://www.hakank.org/google_or_tools/\n", + "\"\"\"\n", + "from __future__ import print_function\n", + "import sys\n", + "from ortools.constraint_solver import pywrapcp\n", + "\n", + "\n", + "# Create the solver.\n", + "solver = pywrapcp.Solver(\"n-queens\")\n", + "\n", + "#\n", + "# data\n", + "#\n", + "\n", + "#\n", + "# declare variables\n", + "#\n", + "x = {}\n", + "for i in range(n):\n", + " for j in range(n):\n", + " x[(i, j)] = solver.IntVar(1, n * n, \"x(%i,%i)\" % (i, j))\n", + "x_flat = [x[(i, j)] for i in range(n) for j in range(n)]\n", + "\n", + "# the sum\n", + "# s = ( n * (n*n + 1)) / 2\n", + "s = solver.IntVar(1, n * n * n, \"s\")\n", + "\n", + "#\n", + "# constraints\n", + "#\n", + "# solver.Add(s == ( n * (n*n + 1)) / 2)\n", + "\n", + "solver.Add(solver.AllDifferent(x_flat))\n", + "\n", + "[solver.Add(solver.Sum([x[(i, j)] for j in range(n)]) == s) for i in range(n)]\n", + "[solver.Add(solver.Sum([x[(i, j)] for i in range(n)]) == s) for j in range(n)]\n", + "\n", + "solver.Add(solver.Sum([x[(i, i)] for i in range(n)]) == s) # diag 1\n", + "solver.Add(solver.Sum([x[(i, n - i - 1)] for i in range(n)]) == s) # diag 2\n", + "\n", + "# symmetry breaking\n", + "# solver.Add(x[(0,0)] == 1)\n", + "\n", + "#\n", + "# solution and search\n", + "#\n", + "solution = solver.Assignment()\n", + "solution.Add(x_flat)\n", + "solution.Add(s)\n", + "\n", + "# db: DecisionBuilder\n", + "db = solver.Phase(\n", + " x_flat,\n", + " # solver.INT_VAR_DEFAULT,\n", + " solver.CHOOSE_FIRST_UNBOUND,\n", + " # solver.CHOOSE_MIN_SIZE_LOWEST_MAX,\n", + "\n", + " # solver.ASSIGN_MIN_VALUE\n", + " solver.ASSIGN_CENTER_VALUE)\n", + "\n", + "solver.NewSearch(db)\n", + "num_solutions = 0\n", + "while solver.NextSolution():\n", + " print(\"s:\", s.Value())\n", + " for i in range(n):\n", + " for j in range(n):\n", + " print(\"%2i\" % x[(i, j)].Value(), end=\" \")\n", + " print()\n", + "\n", + " print()\n", + " num_solutions += 1\n", + " if num_solutions > limit:\n", + " break\n", + "solver.EndSearch()\n", + "\n", + "print()\n", + "print(\"num_solutions:\", num_solutions)\n", + "print(\"failures:\", solver.Failures())\n", + "print(\"branches:\", solver.Branches())\n", + "print(\"WallTime:\", solver.WallTime())\n", + "\n", + "n = 4\n", + "limit=100\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/contrib/magic_square_and_cards.ipynb b/examples/notebook/contrib/magic_square_and_cards.ipynb new file mode 100644 index 0000000000..88fc970e51 --- /dev/null +++ b/examples/notebook/contrib/magic_square_and_cards.ipynb @@ -0,0 +1,120 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010 Hakan Kjellerstrand hakank@gmail.com\n", + "#\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"\n", + "\n", + " Magic squares and cards problem in Google CP Solver.\n", + "\n", + " Martin Gardner (July 1971)\n", + " '''\n", + " Allowing duplicates values, what is the largest constant sum for an order-3\n", + " magic square that can be formed with nine cards from the deck.\n", + " '''\n", + "\n", + "\n", + "\n", + " This model was created by Hakan Kjellerstrand (hakank@gmail.com)\n", + " Also see my other Google CP Solver models:\n", + " http://www.hakank.org/google_or_tools/\n", + "\"\"\"\n", + "from __future__ import print_function\n", + "import sys\n", + "from ortools.constraint_solver import pywrapcp\n", + "\n", + "\n", + "# Create the solver.\n", + "solver = pywrapcp.Solver(\"n-queens\")\n", + "\n", + "#\n", + "# data\n", + "#\n", + "# n = 3\n", + "\n", + "#\n", + "# declare variables\n", + "#\n", + "x = {}\n", + "for i in range(n):\n", + " for j in range(n):\n", + " x[(i, j)] = solver.IntVar(1, 13, \"x(%i,%i)\" % (i, j))\n", + "x_flat = [x[(i, j)] for i in range(n) for j in range(n)]\n", + "\n", + "s = solver.IntVar(1, 13 * 4, \"s\")\n", + "counts = [solver.IntVar(0, 4, \"counts(%i)\" % i) for i in range(14)]\n", + "\n", + "#\n", + "# constraints\n", + "#\n", + "solver.Add(solver.Distribute(x_flat, list(range(14)), counts))\n", + "\n", + "# the standard magic square constraints (sans all_different)\n", + "[solver.Add(solver.Sum([x[(i, j)] for j in range(n)]) == s) for i in range(n)]\n", + "[solver.Add(solver.Sum([x[(i, j)] for i in range(n)]) == s) for j in range(n)]\n", + "\n", + "solver.Add(solver.Sum([x[(i, i)] for i in range(n)]) == s) # diag 1\n", + "solver.Add(solver.Sum([x[(i, n - i - 1)] for i in range(n)]) == s) # diag 2\n", + "\n", + "# redundant constraint\n", + "solver.Add(solver.Sum(counts) == n * n)\n", + "\n", + "# objective\n", + "objective = solver.Maximize(s, 1)\n", + "\n", + "#\n", + "# solution and search\n", + "#\n", + "solution = solver.Assignment()\n", + "solution.Add(x_flat)\n", + "solution.Add(s)\n", + "solution.Add(counts)\n", + "\n", + "# db: DecisionBuilder\n", + "db = solver.Phase(x_flat, solver.CHOOSE_FIRST_UNBOUND,\n", + " solver.ASSIGN_MAX_VALUE)\n", + "\n", + "solver.NewSearch(db, [objective])\n", + "num_solutions = 0\n", + "while solver.NextSolution():\n", + " print(\"s:\", s.Value())\n", + " print(\"counts:\", [counts[i].Value() for i in range(14)])\n", + " for i in range(n):\n", + " for j in range(n):\n", + " print(x[(i, j)].Value(), end=\" \")\n", + " print()\n", + "\n", + " print()\n", + " num_solutions += 1\n", + "solver.EndSearch()\n", + "\n", + "print()\n", + "print(\"num_solutions:\", num_solutions)\n", + "print(\"failures:\", solver.Failures())\n", + "print(\"branches:\", solver.Branches())\n", + "print(\"WallTime:\", solver.WallTime())\n", + "\n", + "n = 3\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/contrib/magic_square_mip.ipynb b/examples/notebook/contrib/magic_square_mip.ipynb new file mode 100644 index 0000000000..bc14a59cfa --- /dev/null +++ b/examples/notebook/contrib/magic_square_mip.ipynb @@ -0,0 +1,199 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2011 Hakan Kjellerstrand hakank@gmail.com\n", + "#\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"\n", + "\n", + " Magic square (integer programming) in Google or-tools.\n", + "\n", + " Translated from GLPK:s example magic.mod\n", + " '''\n", + " MAGIC, Magic Square\n", + "\n", + " Written in GNU MathProg by Andrew Makhorin \n", + "\n", + " In recreational mathematics, a magic square of order n is an\n", + " arrangement of n^2 numbers, usually distinct integers, in a square,\n", + " such that n numbers in all rows, all columns, and both diagonals sum\n", + " to the same constant. A normal magic square contains the integers\n", + " from 1 to n^2.\n", + "\n", + " (From Wikipedia, the free encyclopedia.)\n", + " '''\n", + "\n", + " Compare to the CP version:\n", + " http://www.hakank.org/google_or_tools/magic_square.py\n", + "\n", + " Here we also experiment with how long it takes when\n", + " using an output_matrix (much longer).\n", + "\n", + "\n", + " This model was created by Hakan Kjellerstrand (hakank@gmail.com)\n", + " Also see my other Google CP Solver models:\n", + " http://www.hakank.org/google_or_tools/\n", + "\"\"\"\n", + "from __future__ import print_function\n", + "import sys\n", + "from ortools.linear_solver import pywraplp\n", + "\n", + "#\n", + "# main(n, use_output_matrix)\n", + "# n: size of matrix\n", + "# use_output_matrix: use the output_matrix\n", + "#\n", + "\n", + "\n", + "\n", + "# Create the solver.\n", + "\n", + "print('Solver: ', sol)\n", + "\n", + "# using GLPK\n", + "if sol == 'GLPK':\n", + " solver = pywraplp.Solver('CoinsGridGLPK',\n", + " pywraplp.Solver.GLPK_MIXED_INTEGER_PROGRAMMING)\n", + "else:\n", + " # Using CLP\n", + " solver = pywraplp.Solver('CoinsGridCLP',\n", + " pywraplp.Solver.CBC_MIXED_INTEGER_PROGRAMMING)\n", + "\n", + "#\n", + "# data\n", + "#\n", + "print('n = ', n)\n", + "\n", + "# range_n = range(1, n+1)\n", + "range_n = list(range(0, n))\n", + "\n", + "N = n * n\n", + "range_N = list(range(1, N + 1))\n", + "\n", + "#\n", + "# variables\n", + "#\n", + "\n", + "# x[i,j,k] = 1 means that cell (i,j) contains integer k\n", + "x = {}\n", + "for i in range_n:\n", + " for j in range_n:\n", + " for k in range_N:\n", + " x[i, j, k] = solver.IntVar(0, 1, 'x[%i,%i,%i]' % (i, j, k))\n", + "\n", + "# For output. Much slower....\n", + "if use_output_matrix == 1:\n", + " print('Using an output matrix')\n", + " square = {}\n", + " for i in range_n:\n", + " for j in range_n:\n", + " square[i, j] = solver.IntVar(1, n * n, 'square[%i,%i]' % (i, j))\n", + "\n", + "# the magic sum\n", + "s = solver.IntVar(1, n * n * n, 's')\n", + "\n", + "#\n", + "# constraints\n", + "#\n", + "\n", + "# each cell must be assigned exactly one integer\n", + "for i in range_n:\n", + " for j in range_n:\n", + " solver.Add(solver.Sum([x[i, j, k] for k in range_N]) == 1)\n", + "\n", + "# each integer must be assigned exactly to one cell\n", + "for k in range_N:\n", + " solver.Add(solver.Sum([x[i, j, k] for i in range_n for j in range_n]) == 1)\n", + "\n", + "# # the sum in each row must be the magic sum\n", + "for i in range_n:\n", + " solver.Add(\n", + " solver.Sum([k * x[i, j, k] for j in range_n for k in range_N]) == s)\n", + "\n", + "# # the sum in each column must be the magic sum\n", + "for j in range_n:\n", + " solver.Add(\n", + " solver.Sum([k * x[i, j, k] for i in range_n for k in range_N]) == s)\n", + "\n", + "# # the sum in the diagonal must be the magic sum\n", + "solver.Add(\n", + " solver.Sum([k * x[i, i, k] for i in range_n for k in range_N]) == s)\n", + "\n", + "# # the sum in the co-diagonal must be the magic sum\n", + "if range_n[0] == 1:\n", + " # for range_n = 1..n\n", + " solver.Add(\n", + " solver.Sum([k * x[i, n - i + 1, k]\n", + " for i in range_n\n", + " for k in range_N]) == s)\n", + "else:\n", + " # for range_n = 0..n-1\n", + " solver.Add(\n", + " solver.Sum([k * x[i, n - i - 1, k]\n", + " for i in range_n\n", + " for k in range_N]) == s)\n", + "\n", + "# for output\n", + "if use_output_matrix == 1:\n", + " for i in range_n:\n", + " for j in range_n:\n", + " solver.Add(\n", + " square[i, j] == solver.Sum([k * x[i, j, k] for k in range_N]))\n", + "\n", + "#\n", + "# solution and search\n", + "#\n", + "solver.Solve()\n", + "\n", + "print()\n", + "\n", + "print('s: ', int(s.SolutionValue()))\n", + "if use_output_matrix == 1:\n", + " for i in range_n:\n", + " for j in range_n:\n", + " print(int(square[i, j].SolutionValue()), end=' ')\n", + " print()\n", + " print()\n", + "else:\n", + " for i in range_n:\n", + " for j in range_n:\n", + " print(\n", + " sum([int(k * x[i, j, k].SolutionValue()) for k in range_N]),\n", + " ' ',\n", + " end=' ')\n", + " print()\n", + "\n", + "print('\\nx:')\n", + "for i in range_n:\n", + " for j in range_n:\n", + " for k in range_N:\n", + " print(int(x[i, j, k].SolutionValue()), end=' ')\n", + " print()\n", + "\n", + "print()\n", + "print('walltime :', solver.WallTime(), 'ms')\n", + "if sol == 'CBC':\n", + " print('iterations:', solver.Iterations())\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/contrib/map.ipynb b/examples/notebook/contrib/map.ipynb new file mode 100644 index 0000000000..6997635c78 --- /dev/null +++ b/examples/notebook/contrib/map.ipynb @@ -0,0 +1,115 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010 Hakan Kjellerstrand hakank@gmail.com\n", + "#\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"\n", + "\n", + " Map coloring problem in Google CP Solver.\n", + "\n", + "\n", + " From Pascal Van Hentenryck 'The OPL Optimization Programming Language',\n", + " page 7, 42.\n", + "\n", + " Compare with the following models:\n", + " * Comet: http://www.hakank.org/comet/map.co\n", + " * Tailor/Essence': http://hakank.org/tailor/map_coloring.eprime\n", + " * SICStus: http://hakank.org/sicstus/map_coloring.pl\n", + " * ECLiPSe: http://hakank.org/eclipse/map.ecl\n", + " * Gecode: http://hakank.org/gecode/map.cpp\n", + " * MiniZinc: http://hakank.org/minizinc/map.mzn\n", + " * Zinc: http://hakank.org/minizinc/map.zinc\n", + "\n", + " This model was created by Hakan Kjellerstrand (hakank@gmail.com)\n", + " Also see my other Google CP Solver models:\n", + " http://www.hakank.org/google_or_tools/\n", + "\"\"\"\n", + "from __future__ import print_function\n", + "from ortools.constraint_solver import pywrapcp\n", + "\n", + "\n", + "# Create the solver.\n", + "solver = pywrapcp.Solver(\"Map coloring\")\n", + "\n", + "#\n", + "# data\n", + "#\n", + "Belgium = 0\n", + "Denmark = 1\n", + "France = 2\n", + "Germany = 3\n", + "Netherlands = 4\n", + "Luxembourg = 5\n", + "\n", + "n = 6\n", + "max_num_colors = 4\n", + "\n", + "# declare variables\n", + "color = [solver.IntVar(1, max_num_colors, \"x%i\" % i) for i in range(n)]\n", + "\n", + "#\n", + "# constraints\n", + "#\n", + "solver.Add(color[Belgium] == 1) # Symmetry breaking\n", + "solver.Add(color[France] != color[Belgium])\n", + "solver.Add(color[France] != color[Luxembourg])\n", + "solver.Add(color[France] != color[Germany])\n", + "solver.Add(color[Luxembourg] != color[Germany])\n", + "solver.Add(color[Luxembourg] != color[Belgium])\n", + "solver.Add(color[Belgium] != color[Netherlands])\n", + "solver.Add(color[Belgium] != color[Germany])\n", + "solver.Add(color[Germany] != color[Netherlands])\n", + "solver.Add(color[Germany] != color[Denmark])\n", + "\n", + "#\n", + "# solution and search\n", + "#\n", + "solution = solver.Assignment()\n", + "solution.Add([color[i] for i in range(n)])\n", + "\n", + "collector = solver.AllSolutionCollector(solution)\n", + "# collector = solver.FirstSolutionCollector(solution)\n", + "# search_log = solver.SearchLog(100, x[0])\n", + "solver.Solve(\n", + " solver.Phase([color[i] for i in range(n)], solver.INT_VAR_SIMPLE,\n", + " solver.ASSIGN_MIN_VALUE), [collector])\n", + "\n", + "num_solutions = collector.SolutionCount()\n", + "print(\"num_solutions: \", num_solutions)\n", + "if num_solutions > 0:\n", + " for s in range(num_solutions):\n", + " colorval = [collector.Value(s, color[i]) for i in range(n)]\n", + " print(\"color:\", colorval)\n", + "\n", + " print()\n", + " print(\"num_solutions:\", num_solutions)\n", + " print(\"failures:\", solver.Failures())\n", + " print(\"branches:\", solver.Branches())\n", + " print(\"WallTime:\", solver.WallTime())\n", + "\n", + "else:\n", + " print(\"No solutions found\")\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/contrib/marathon2.ipynb b/examples/notebook/contrib/marathon2.ipynb new file mode 100644 index 0000000000..c412c641eb --- /dev/null +++ b/examples/notebook/contrib/marathon2.ipynb @@ -0,0 +1,145 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010 Hakan Kjellerstrand hakank@gmail.com\n", + "#\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"\n", + "\n", + " Marathon puzzle in Google CP Solver.\n", + "\n", + " From Xpress example\n", + " http://www.dashoptimization.com/home/cgi-bin/example.pl?id=mosel_puzzle_5_3\n", + " '''\n", + " Dominique, Ignace, Naren, Olivier, Philippe, and Pascal\n", + " have arrived as the first six at the Paris marathon.\n", + " Reconstruct their arrival order from the following\n", + " information:\n", + " a) Olivier has not arrived last\n", + " b) Dominique, Pascal and Ignace have arrived before Naren\n", + " and Olivier\n", + " c) Dominique who was third last year has improved this year.\n", + " d) Philippe is among the first four.\n", + " e) Ignace has arrived neither in second nor third position.\n", + " f) Pascal has beaten Naren by three positions.\n", + " g) Neither Ignace nor Dominique are on the fourth position.\n", + "\n", + " (c) 2002 Dash Associates\n", + " author: S. Heipcke, Mar. 2002\n", + " '''\n", + "\n", + " Compare with the following models:\n", + " * MiniZinc: http://www.hakank.org/minizinc/marathon2.mzn\n", + " * SICStus Prolog: http://www.hakank.org/sicstus/marathon2.pl\n", + " * ECLiPSe: http://hakank.org/eclipse/marathon2.ecl\n", + " * Gecode: http://hakank.org/gecode/marathon2.cpp\n", + "\n", + "\n", + " This model was created by Hakan Kjellerstrand (hakank@gmail.com)\n", + " Also see my other Google CP Solver models:\n", + " http://www.hakank.org/google_or_tools/\n", + "\"\"\"\n", + "from __future__ import print_function\n", + "import sys\n", + "from ortools.constraint_solver import pywrapcp\n", + "\n", + "\n", + "\n", + "# Create the solver.\n", + "solver = pywrapcp.Solver('Marathon')\n", + "\n", + "#\n", + "# data\n", + "#\n", + "n = 6\n", + "\n", + "runners_str = [\n", + " 'Dominique', 'Ignace', 'Naren', 'Olivier', 'Philippe', 'Pascal'\n", + "]\n", + "\n", + "#\n", + "# declare variables\n", + "#\n", + "runners = [solver.IntVar(1, n, 'runners[%i]' % i) for i in range(n)]\n", + "Dominique, Ignace, Naren, Olivier, Philippe, Pascal = runners\n", + "\n", + "#\n", + "# constraints\n", + "#\n", + "solver.Add(solver.AllDifferent(runners))\n", + "\n", + "# a: Olivier not last\n", + "solver.Add(Olivier != n)\n", + "\n", + "# b: Dominique, Pascal and Ignace before Naren and Olivier\n", + "solver.Add(Dominique < Naren)\n", + "solver.Add(Dominique < Olivier)\n", + "solver.Add(Pascal < Naren)\n", + "solver.Add(Pascal < Olivier)\n", + "solver.Add(Ignace < Naren)\n", + "solver.Add(Ignace < Olivier)\n", + "\n", + "# c: Dominique better than third\n", + "solver.Add(Dominique < 3)\n", + "\n", + "# d: Philippe is among the first four\n", + "solver.Add(Philippe <= 4)\n", + "\n", + "# e: Ignace neither second nor third\n", + "solver.Add(Ignace != 2)\n", + "solver.Add(Ignace != 3)\n", + "\n", + "# f: Pascal three places earlier than Naren\n", + "solver.Add(Pascal + 3 == Naren)\n", + "\n", + "# g: Neither Ignace nor Dominique on fourth position\n", + "solver.Add(Ignace != 4)\n", + "solver.Add(Dominique != 4)\n", + "\n", + "#\n", + "# solution and search\n", + "#\n", + "db = solver.Phase(runners, solver.CHOOSE_MIN_SIZE_LOWEST_MIN,\n", + " solver.ASSIGN_CENTER_VALUE)\n", + "\n", + "solver.NewSearch(db)\n", + "\n", + "num_solutions = 0\n", + "while solver.NextSolution():\n", + " num_solutions += 1\n", + " runners_val = [runners[i].Value() for i in range(n)]\n", + " print('runners:', runners_val)\n", + " print('Places:')\n", + " for i in range(1, n + 1):\n", + " for j in range(n):\n", + " if runners_val[j] == i:\n", + " print('%i: %s' % (i, runners_str[j]))\n", + " print()\n", + "\n", + "print('num_solutions:', num_solutions)\n", + "print('failures:', solver.Failures())\n", + "print('branches:', solver.Branches())\n", + "print('WallTime:', solver.WallTime(), 'ms')\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/contrib/max_flow_taha.ipynb b/examples/notebook/contrib/max_flow_taha.ipynb new file mode 100644 index 0000000000..75592508a3 --- /dev/null +++ b/examples/notebook/contrib/max_flow_taha.ipynb @@ -0,0 +1,131 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010 Hakan Kjellerstrand hakank@gmail.com\n", + "#\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"\n", + "\n", + " Max flow problem in Google CP Solver.\n", + "\n", + " From Taha 'Introduction to Operations Research', Example 6.4-2\n", + "\n", + " Translated from the AMPL code at\n", + " http://taha.ineg.uark.edu/maxflo.txt\n", + "\n", + " Compare with the following model:\n", + " * MiniZinc: http://www.hakank.org/minizinc/max_flow_taha.mzn\n", + "\n", + " This model was created by Hakan Kjellerstrand (hakank@gmail.com)\n", + " Also see my other Google CP Solver models:\n", + " http://www.hakank.org/google_or_tools/\n", + "\"\"\"\n", + "from __future__ import print_function\n", + "from ortools.constraint_solver import pywrapcp\n", + "\n", + "\n", + "\n", + "# Create the solver.\n", + "solver = pywrapcp.Solver('Max flow problem, Taha')\n", + "\n", + "#\n", + "# data\n", + "#\n", + "n = 5\n", + "start = 0\n", + "end = n - 1\n", + "\n", + "nodes = list(range(n))\n", + "\n", + "# cost matrix\n", + "c = [[0, 20, 30, 10, 0], [0, 0, 40, 0, 30], [0, 0, 0, 10, 20],\n", + " [0, 0, 5, 0, 20], [0, 0, 0, 0, 0]]\n", + "\n", + "#\n", + "# declare variables\n", + "#\n", + "x = {}\n", + "for i in nodes:\n", + " for j in nodes:\n", + " x[i, j] = solver.IntVar(0, c[i][j], 'x[%i,%i]' % (i, j))\n", + "\n", + "x_flat = [x[i, j] for i in nodes for j in nodes]\n", + "out_flow = [solver.IntVar(0, 10000, 'out_flow[%i]' % i) for i in nodes]\n", + "in_flow = [solver.IntVar(0, 10000, 'in_flow[%i]' % i) for i in nodes]\n", + "\n", + "total = solver.IntVar(0, 10000, 'z')\n", + "\n", + "#\n", + "# constraints\n", + "#\n", + "cost_sum = solver.Sum([x[start, j] for j in nodes if c[start][j] > 0])\n", + "solver.Add(total == cost_sum)\n", + "\n", + "for i in nodes:\n", + " in_flow_sum = solver.Sum([x[j, i] for j in nodes if c[j][i] > 0])\n", + " solver.Add(in_flow[i] == in_flow_sum)\n", + "\n", + " out_flow_sum = solver.Sum([x[i, j] for j in nodes if c[i][j] > 0])\n", + " solver.Add(out_flow[i] == out_flow_sum)\n", + "\n", + "# in_flow == out_flow\n", + "for i in nodes:\n", + " if i != start and i != end:\n", + " solver.Add(out_flow[i] - in_flow[i] == 0)\n", + "\n", + "s1 = [x[i, start] for i in nodes if c[i][start] > 0]\n", + "if len(s1) > 0:\n", + " solver.Add(solver.Sum([x[i, start] for i in nodes if c[i][start] > 0] == 0))\n", + "\n", + "s2 = [x[end, j] for j in nodes if c[end][j] > 0]\n", + "if len(s2) > 0:\n", + " solver.Add(solver.Sum([x[end, j] for j in nodes if c[end][j] > 0]) == 0)\n", + "\n", + "# objective: maximize total cost\n", + "objective = solver.Maximize(total, 1)\n", + "\n", + "#\n", + "# solution and search\n", + "#\n", + "db = solver.Phase(x_flat, solver.INT_VAR_DEFAULT, solver.ASSIGN_MAX_VALUE)\n", + "\n", + "solver.NewSearch(db, [objective])\n", + "num_solutions = 0\n", + "while solver.NextSolution():\n", + " num_solutions += 1\n", + " print('total:', total.Value())\n", + " print('in_flow:', [in_flow[i].Value() for i in nodes])\n", + " print('out_flow:', [out_flow[i].Value() for i in nodes])\n", + " for i in nodes:\n", + " for j in nodes:\n", + " print('%2i' % x[i, j].Value(), end=' ')\n", + " print()\n", + " print()\n", + "\n", + "print('num_solutions:', num_solutions)\n", + "print('failures:', solver.Failures())\n", + "print('branches:', solver.Branches())\n", + "print('WallTime:', solver.WallTime(), 'ms')\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/contrib/max_flow_winston1.ipynb b/examples/notebook/contrib/max_flow_winston1.ipynb new file mode 100644 index 0000000000..e9b3f32eba --- /dev/null +++ b/examples/notebook/contrib/max_flow_winston1.ipynb @@ -0,0 +1,149 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010 Hakan Kjellerstrand hakank@gmail.com\n", + "#\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"\n", + "\n", + " Max flow problem in Google CP Solver.\n", + "\n", + " From Winston 'Operations Research', page 420f, 423f\n", + " Sunco Oil example.\n", + "\n", + " Compare with the following models:\n", + " * MiniZinc: http://www.hakank.org/minizinc/max_flow_winston1.mzn\n", + " * Comet: http://hakank.org/comet/max_flow_winston1.co\n", + "\n", + "\n", + " This model was created by Hakan Kjellerstrand (hakank@gmail.com)\n", + " Also see my other Google CP Solver models:\n", + " http://www.hakank.org/google_or_tools/\n", + "\"\"\"\n", + "from __future__ import print_function\n", + "import sys\n", + "from ortools.constraint_solver import pywrapcp\n", + "\n", + "\n", + "\n", + "# Create the solver.\n", + "solver = pywrapcp.Solver('Max flow problem, Winston')\n", + "\n", + "#\n", + "# data\n", + "#\n", + "n = 5\n", + "nodes = list(range(n))\n", + "\n", + "# the arcs\n", + "# Note:\n", + "# This is 1-based to be compatible with other\n", + "# implementations.\n", + "arcs1 = [[1, 2], [1, 3], [2, 3], [2, 4], [3, 5], [4, 5], [5, 1]]\n", + "\n", + "# convert arcs to 0-based\n", + "arcs = []\n", + "for (a_from, a_to) in arcs1:\n", + " a_from -= 1\n", + " a_to -= 1\n", + " arcs.append([a_from, a_to])\n", + "\n", + "num_arcs = len(arcs)\n", + "\n", + "# capacities\n", + "cap = [2, 3, 3, 4, 2, 1, 100]\n", + "\n", + "# convert arcs to matrix\n", + "# for sanity checking below\n", + "mat = {}\n", + "for i in nodes:\n", + " for j in nodes:\n", + " c = 0\n", + " for k in range(num_arcs):\n", + " if arcs[k][0] == i and arcs[k][1] == j:\n", + " c = 1\n", + " mat[i, j] = c\n", + "\n", + "#\n", + "# declare variables\n", + "#\n", + "flow = {}\n", + "for i in nodes:\n", + " for j in nodes:\n", + " flow[i, j] = solver.IntVar(0, 200, 'flow %i %i' % (i, j))\n", + "\n", + "flow_flat = [flow[i, j] for i in nodes for j in nodes]\n", + "\n", + "z = solver.IntVar(0, 10000, 'z')\n", + "\n", + "#\n", + "# constraints\n", + "#\n", + "solver.Add(z == flow[n - 1, 0])\n", + "\n", + "# capacity of arcs\n", + "for i in range(num_arcs):\n", + " solver.Add(flow[arcs[i][0], arcs[i][1]] <= cap[i])\n", + "\n", + "# inflows == outflows\n", + "for i in nodes:\n", + " s1 = solver.Sum([\n", + " flow[arcs[k][0], arcs[k][1]] for k in range(num_arcs) if arcs[k][1] == i\n", + " ])\n", + " s2 = solver.Sum([\n", + " flow[arcs[k][0], arcs[k][1]] for k in range(num_arcs) if arcs[k][0] == i\n", + " ])\n", + " solver.Add(s1 == s2)\n", + "\n", + "# sanity: just arcs with connections can have a flow\n", + "for i in nodes:\n", + " for j in nodes:\n", + " if mat[i, j] == 0:\n", + " solver.Add(flow[i, j] == 0)\n", + "\n", + "# objective: maximize z\n", + "objective = solver.Maximize(z, 1)\n", + "\n", + "#\n", + "# solution and search\n", + "#\n", + "db = solver.Phase(flow_flat, solver.INT_VAR_DEFAULT, solver.INT_VALUE_DEFAULT)\n", + "\n", + "solver.NewSearch(db, [objective])\n", + "num_solutions = 0\n", + "while solver.NextSolution():\n", + " num_solutions += 1\n", + " print('z:', z.Value())\n", + " for i in nodes:\n", + " for j in nodes:\n", + " print(flow[i, j].Value(), end=' ')\n", + " print()\n", + " print()\n", + "\n", + "print('num_solutions:', num_solutions)\n", + "print('failures:', solver.Failures())\n", + "print('branches:', solver.Branches())\n", + "print('WallTime:', solver.WallTime(), 'ms')\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/contrib/minesweeper.ipynb b/examples/notebook/contrib/minesweeper.ipynb new file mode 100644 index 0000000000..7751898cb9 --- /dev/null +++ b/examples/notebook/contrib/minesweeper.ipynb @@ -0,0 +1,235 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010 Hakan Kjellerstrand hakank@gmail.com\n", + "#\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"\n", + "\n", + " Minesweeper in Google CP Solver.\n", + "\n", + " From gecode/examples/minesweeper.cc:\n", + " '''\n", + " A specification is a square matrix of characters. Alphanumeric\n", + " characters represent the number of mines adjacent to that field.\n", + " Dots represent fields with an unknown number of mines adjacent to\n", + " it (or an actual mine).\n", + " '''\n", + "\n", + " E.g.\n", + " '..2.3.'\n", + " '2.....'\n", + " '..24.3'\n", + " '1.34..'\n", + " '.....3'\n", + " '.3.3..'\n", + "\n", + "\n", + " Also see:\n", + " * http://www.janko.at/Raetsel/Minesweeper/index.htm\n", + "\n", + " * http://en.wikipedia.org/wiki/Minesweeper_(computer_game)\n", + "\n", + " * Ian Stewart on Minesweeper:\n", + " http://www.claymath.org/Popular_Lectures/Minesweeper/\n", + "\n", + " * Richard Kaye's Minesweeper Pages\n", + " http://web.mat.bham.ac.uk/R.W.Kaye/minesw/minesw.htm\n", + "\n", + " * Some Minesweeper Configurations\n", + " http://web.mat.bham.ac.uk/R.W.Kaye/minesw/minesw.pdf\n", + "\n", + "\n", + " Compare with the following models:\n", + " * MiniZinc: http://www.hakank.org/minizinc/minesweeper.mzn\n", + " * Choco : http://www.hakank.org/choco/MineSweeper.java\n", + " * JaCoP : http://www.hakank.org/JaCoP/MineSweeper.java\n", + " * Gecode/R: http://www.hakank.org/gecode_r/minesweeper.rb\n", + " * Comet : http://www.hakank.org/comet/minesweeper.co\n", + " * ECLiPSe : http://www.hakank.org/eclipse/minesweeper.ecl\n", + " * SICStus : http://www.hakank.org/sicstus/minesweeper.pl\n", + " * Tailor/Essence': http://www.hakank.org/tailor/minesweeper.eprime\n", + " * Zinc: http://www.hakank.org/minizinc/minesweeper.zinc\n", + "\n", + " This model was created by Hakan Kjellerstrand (hakank@gmail.com)\n", + " Also see my other Google CP Solver models:\n", + " http://www.hakank.org/google_or_tools/\n", + "\"\"\"\n", + "from __future__ import print_function\n", + "import sys\n", + "from ortools.constraint_solver import pywrapcp\n", + "\n", + "default_r = 8\n", + "default_c = 8\n", + "X = -1\n", + "default_game = [[2, 3, X, 2, 2, X, 2, 1], [X, X, 4, X, X, 4, X, 2],\n", + " [X, X, X, X, X, X, 4, X], [X, 5, X, 6, X, X, X, 2],\n", + " [2, X, X, X, 5, 5, X, 2], [1, 3, 4, X, X, X, 4, X],\n", + " [0, 1, X, 4, X, X, X, 3], [0, 1, 2, X, 2, 3, X, 2]]\n", + "\n", + "\n", + "\n", + "# Create the solver.\n", + "solver = pywrapcp.Solver(\"Minesweeper\")\n", + "\n", + "#\n", + "# data\n", + "#\n", + "\n", + "# Set default problem\n", + "if game == \"\":\n", + " game = default_game\n", + " r = default_r\n", + " c = default_c\n", + "else:\n", + " print(\"rows:\", r, \" cols:\", c)\n", + "\n", + "#\n", + "# Default problem from \"Some Minesweeper Configurations\",page 3\n", + "# (same as problem instance minesweeper_config3.txt)\n", + "# It has 4 solutions\n", + "#\n", + "# r = 8\n", + "# c = 8\n", + "# X = -1\n", + "# game = [\n", + "# [2,3,X,2,2,X,2,1],\n", + "# [X,X,4,X,X,4,X,2],\n", + "# [X,X,X,X,X,X,4,X],\n", + "# [X,5,X,6,X,X,X,2],\n", + "# [2,X,X,X,5,5,X,2],\n", + "# [1,3,4,X,X,X,4,X],\n", + "# [0,1,X,4,X,X,X,3],\n", + "# [0,1,2,X,2,3,X,2]\n", + "# ]\n", + "\n", + "S = [-1, 0, 1] # for the neighbors of \"this\" cell\n", + "\n", + "# print problem instance\n", + "print(\"Problem:\")\n", + "for i in range(r):\n", + " for j in range(c):\n", + " if game[i][j] == X:\n", + " print(\"X\", end=\" \")\n", + " else:\n", + " print(game[i][j], end=\" \")\n", + " print()\n", + "print()\n", + "\n", + "# declare variables\n", + "mines = {}\n", + "for i in range(r):\n", + " for j in range(c):\n", + " mines[(i, j)] = solver.IntVar(0, 1, \"mines %i %i\" % (i, j))\n", + "\n", + "#\n", + "# constraints\n", + "#\n", + "for i in range(r):\n", + " for j in range(c):\n", + " if game[i][j] >= 0:\n", + " solver.Add(mines[i, j] == 0)\n", + " # this cell is the sum of all the surrounding cells\n", + " solver.Add(game[i][j] == solver.Sum([\n", + " mines[i + a, j + b]\n", + " for a in S\n", + " for b in S\n", + " if i + a >= 0 and j + b >= 0 and i + a < r and j + b < c\n", + " ]))\n", + " if game[i][j] > X:\n", + " # This cell cannot be a mine\n", + " solver.Add(mines[i, j] == 0)\n", + "\n", + "#\n", + "# solution and search\n", + "#\n", + "solution = solver.Assignment()\n", + "solution.Add([mines[(i, j)] for i in range(r) for j in range(c)])\n", + "\n", + "collector = solver.AllSolutionCollector(solution)\n", + "solver.Solve(\n", + " solver.Phase([mines[(i, j)] for i in range(r) for j in range(c)],\n", + " solver.INT_VAR_SIMPLE, solver.ASSIGN_MIN_VALUE), [collector])\n", + "\n", + "num_solutions = collector.SolutionCount()\n", + "print(\"num_solutions: \", num_solutions)\n", + "if num_solutions > 0:\n", + " for s in range(num_solutions):\n", + " minesval = [\n", + " collector.Value(s, mines[(i, j)]) for i in range(r) for j in range(c)\n", + " ]\n", + " for i in range(r):\n", + " for j in range(c):\n", + " print(minesval[i * c + j], end=\" \")\n", + " print()\n", + " print()\n", + "\n", + " print()\n", + " print(\"num_solutions:\", num_solutions)\n", + " print(\"failures:\", solver.Failures())\n", + " print(\"branches:\", solver.Branches())\n", + " print(\"WallTime:\", solver.WallTime())\n", + "\n", + "else:\n", + " print(\"No solutions found\")\n", + "\n", + "\n", + "#\n", + "# Read a problem instance from a file\n", + "#def read_problem(file):\n", + " f = open(file, \"r\")\n", + " rows = int(f.readline())\n", + " cols = int(f.readline())\n", + " game = []\n", + " for i in range(rows):\n", + " x = f.readline()\n", + " row = [0] * cols\n", + " for j in range(cols):\n", + " if x[j] == \".\":\n", + " tmp = -1\n", + " else:\n", + " tmp = int(x[j])\n", + " row[j] = tmp\n", + " game.append(row)\n", + " return [game, rows, cols]\n", + "\n", + "\n", + "#\n", + "# Print the mines\n", + "#\n", + "def print_mines(mines, rows, cols):\n", + " for i in range(rows):\n", + " for j in range(cols):\n", + " print(mines[i, j], end=\" \")\n", + " print(\"\")\n", + "\n", + "\n", + "def print_game(game, rows, cols):\n", + " for i in range(rows):\n", + " for j in range(cols):\n", + " print(game[i][j], end=\" \")\n", + " print(\"\")\n", + "\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/contrib/mr_smith.ipynb b/examples/notebook/contrib/mr_smith.ipynb new file mode 100644 index 0000000000..a765441709 --- /dev/null +++ b/examples/notebook/contrib/mr_smith.ipynb @@ -0,0 +1,131 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010 Hakan Kjellerstrand hakank@gmail.com\n", + "#\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"\n", + "\n", + " Mr Smith in Google CP Solver.\n", + "\n", + " From an IF Prolog example (http://www.ifcomputer.de/)\n", + " '''\n", + " The Smith family and their three children want to pay a visit but they\n", + " do not all have the time to do so. Following are few hints who will go\n", + " and who will not:\n", + " o If Mr Smith comes, his wife will come too.\n", + " o At least one of their two sons Matt and John will come.\n", + " o Either Mrs Smith or Tim will come, but not both.\n", + " o Either Tim and John will come, or neither will come.\n", + " o If Matt comes, then John and his father will\n", + " also come.\n", + " '''\n", + "\n", + " The answer should be:\n", + " Mr_Smith_comes = 0\n", + " Mrs_Smith_comes = 0\n", + " Matt_comes = 0\n", + " John_comes = 1\n", + " Tim_comes = 1\n", + "\n", + " Compare with the following models:\n", + " * ECLiPSe: http://www.hakank.org/eclipse/mr_smith.ecl\n", + " * SICStus Prolog: http://www.hakank.org/sicstus/mr_smith.pl\n", + " * Gecode: http://www.hakank.org/gecode/mr_smith.cpp\n", + " * MiniZinc: http://www.hakank.org/minizinc/mr_smith.mzn\n", + "\n", + "\n", + " This model was created by Hakan Kjellerstrand (hakank@gmail.com)\n", + " Also see my other Google CP Solver models:\n", + " http://www.hakank.org/google_or_tools/\n", + "\"\"\"\n", + "from __future__ import print_function\n", + "import sys\n", + "from ortools.constraint_solver import pywrapcp\n", + "\n", + "\n", + "\n", + "# Create the solver.\n", + "solver = pywrapcp.Solver('Mr Smith problem')\n", + "\n", + "#\n", + "# data\n", + "#\n", + "n = 5\n", + "\n", + "#\n", + "# declare variables\n", + "#\n", + "x = [solver.IntVar(0, 1, 'x[%i]' % i) for i in range(n)]\n", + "Mr_Smith, Mrs_Smith, Matt, John, Tim = x\n", + "\n", + "#\n", + "# constraints\n", + "#\n", + "\n", + "#\n", + "# I've kept the MiniZinc constraints for clarity\n", + "# and debugging.\n", + "#\n", + "\n", + "# If Mr Smith comes then his wife will come too.\n", + "# (Mr_Smith -> Mrs_Smith)\n", + "solver.Add(Mr_Smith - Mrs_Smith <= 0)\n", + "\n", + "# At least one of their two sons Matt and John will come.\n", + "# (Matt \\/ John)\n", + "solver.Add(Matt + John >= 1)\n", + "\n", + "# Either Mrs Smith or Tim will come but not both.\n", + "# bool2int(Mrs_Smith) + bool2int(Tim) = 1 /\\\n", + "# (Mrs_Smith xor Tim)\n", + "solver.Add(Mrs_Smith + Tim == 1)\n", + "\n", + "# Either Tim and John will come or neither will come.\n", + "# (Tim = John)\n", + "solver.Add(Tim == John)\n", + "\n", + "# If Matt comes /\\ then John and his father will also come.\n", + "# (Matt -> (John /\\ Mr_Smith))\n", + "solver.Add(Matt - (John * Mr_Smith) <= 0)\n", + "\n", + "#\n", + "# solution and search\n", + "#\n", + "db = solver.Phase(x, solver.INT_VAR_DEFAULT, solver.INT_VALUE_DEFAULT)\n", + "\n", + "solver.NewSearch(db)\n", + "\n", + "num_solutions = 0\n", + "while solver.NextSolution():\n", + " num_solutions += 1\n", + " print('x:', [x[i].Value() for i in range(n)])\n", + "\n", + "print()\n", + "print('num_solutions:', num_solutions)\n", + "print('failures:', solver.Failures())\n", + "print('branches:', solver.Branches())\n", + "print('WallTime:', solver.WallTime(), 'ms')\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/contrib/nonogram_default_search.ipynb b/examples/notebook/contrib/nonogram_default_search.ipynb new file mode 100644 index 0000000000..c7216cf303 --- /dev/null +++ b/examples/notebook/contrib/nonogram_default_search.ipynb @@ -0,0 +1,209 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010 Hakan Kjellerstrand hakank@gmail.com, lperron@google.com\n", + "#\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"\n", + "\n", + " Nonogram (Painting by numbers) in Google CP Solver.\n", + "\n", + " http://en.wikipedia.org/wiki/Nonogram\n", + " '''\n", + " Nonograms or Paint by Numbers are picture logic puzzles in which cells in a\n", + " grid have to be colored or left blank according to numbers given at the\n", + " side of the grid to reveal a hidden picture. In this puzzle type, the\n", + " numbers measure how many unbroken lines of filled-in squares there are\n", + " in any given row or column. For example, a clue of '4 8 3' would mean\n", + " there are sets of four, eight, and three filled squares, in that order,\n", + " with at least one blank square between successive groups.\n", + "\n", + " '''\n", + "\n", + " See problem 12 at http://www.csplib.org/.\n", + "\n", + " http://www.puzzlemuseum.com/nonogram.htm\n", + "\n", + " Haskell solution:\n", + " http://twan.home.fmf.nl/blog/haskell/Nonograms.details\n", + "\n", + " Brunetti, Sara & Daurat, Alain (2003)\n", + " 'An algorithm reconstructing convex lattice sets'\n", + " http://geodisi.u-strasbg.fr/~daurat/papiers/tomoqconv.pdf\n", + "\n", + "\"\"\"\n", + "from __future__ import print_function\n", + "import sys\n", + "\n", + "from ortools.constraint_solver import pywrapcp\n", + "\n", + "\n", + "#\n", + "# Make a transition (automaton) list of tuples from a\n", + "# single pattern, e.g. [3,2,1]\n", + "#\n", + "def make_transition_tuples(pattern):\n", + " p_len = len(pattern)\n", + " num_states = p_len + sum(pattern)\n", + "\n", + " tuples = []\n", + "\n", + " # this is for handling 0-clues. It generates\n", + " # just the minimal state\n", + " if num_states == 0:\n", + " tuples.append((1, 0, 1))\n", + " return (tuples, 1)\n", + "\n", + " # convert pattern to a 0/1 pattern for easy handling of\n", + " # the states\n", + " tmp = [0]\n", + " c = 0\n", + " for pattern_index in range(p_len):\n", + " tmp.extend([1] * pattern[pattern_index])\n", + " tmp.append(0)\n", + "\n", + " for i in range(num_states):\n", + " state = i + 1\n", + " if tmp[i] == 0:\n", + " tuples.append((state, 0, state))\n", + " tuples.append((state, 1, state + 1))\n", + " else:\n", + " if i < num_states - 1:\n", + " if tmp[i + 1] == 1:\n", + " tuples.append((state, 1, state + 1))\n", + " else:\n", + " tuples.append((state, 0, state + 1))\n", + " tuples.append((num_states, 0, num_states))\n", + " return (tuples, num_states)\n", + "\n", + "\n", + "#\n", + "# check each rule by creating an automaton and transition constraint.\n", + "#\n", + "def check_rule(rules, y):\n", + " cleaned_rule = [rules[i] for i in range(len(rules)) if rules[i] > 0]\n", + " (transition_tuples, last_state) = make_transition_tuples(cleaned_rule)\n", + "\n", + " initial_state = 1\n", + " accepting_states = [last_state]\n", + "\n", + " solver = y[0].solver()\n", + " solver.Add(\n", + " solver.TransitionConstraint(y, transition_tuples, initial_state,\n", + " accepting_states))\n", + "\n", + "\n", + "\n", + "# Create the solver.\n", + "solver = pywrapcp.Solver('Nonogram')\n", + "\n", + "#\n", + "# variables\n", + "#\n", + "board = {}\n", + "for i in range(rows):\n", + " for j in range(cols):\n", + " board[i, j] = solver.IntVar(0, 1, 'board[%i, %i]' % (i, j))\n", + "\n", + "board_flat = [board[i, j] for i in range(rows) for j in range(cols)]\n", + "\n", + "# Flattened board for labeling.\n", + "# This labeling was inspired by a suggestion from\n", + "# Pascal Van Hentenryck about my (hakank's) Comet\n", + "# nonogram model.\n", + "board_label = []\n", + "if rows * row_rule_len < cols * col_rule_len:\n", + " for i in range(rows):\n", + " for j in range(cols):\n", + " board_label.append(board[i, j])\n", + "else:\n", + " for j in range(cols):\n", + " for i in range(rows):\n", + " board_label.append(board[i, j])\n", + "\n", + "#\n", + "# constraints\n", + "#\n", + "for i in range(rows):\n", + " check_rule(row_rules[i], [board[i, j] for j in range(cols)])\n", + "\n", + "for j in range(cols):\n", + " check_rule(col_rules[j], [board[i, j] for i in range(rows)])\n", + "\n", + "#\n", + "# solution and search\n", + "#\n", + "parameters = pywrapcp.DefaultPhaseParameters()\n", + "parameters.heuristic_period = 200000\n", + "\n", + "db = solver.DefaultPhase(board_label, parameters)\n", + "\n", + "print('before solver, wall time = ', solver.WallTime(), 'ms')\n", + "solver.NewSearch(db)\n", + "\n", + "num_solutions = 0\n", + "while solver.NextSolution():\n", + " print()\n", + " num_solutions += 1\n", + " for i in range(rows):\n", + " row = [board[i, j].Value() for j in range(cols)]\n", + " row_pres = []\n", + " for j in row:\n", + " if j == 1:\n", + " row_pres.append('#')\n", + " else:\n", + " row_pres.append(' ')\n", + " print(' ', ''.join(row_pres))\n", + "\n", + " print()\n", + " print(' ', '-' * cols)\n", + "\n", + " if num_solutions >= 2:\n", + " print('2 solutions is enough...')\n", + " break\n", + "\n", + "solver.EndSearch()\n", + "print()\n", + "print('num_solutions:', num_solutions)\n", + "print('failures:', solver.Failures())\n", + "print('branches:', solver.Branches())\n", + "print('WallTime:', solver.WallTime(), 'ms')\n", + "\n", + "\n", + "#\n", + "# Default problem\n", + "#\n", + "# From http://twan.home.fmf.nl/blog/haskell/Nonograms.details\n", + "# The lambda picture\n", + "#rows = 12\n", + "row_rule_len = 3\n", + "row_rules = [[0, 0, 2], [0, 1, 2], [0, 1, 1], [0, 0, 2], [0, 0, 1], [0, 0, 3],\n", + " [0, 0, 3], [0, 2, 2], [0, 2, 1], [2, 2, 1], [0, 2, 3], [0, 2, 2]]\n", + "\n", + "cols = 10\n", + "col_rule_len = 2\n", + "col_rules = [[2, 1], [1, 3], [2, 4], [3, 4], [0, 4], [0, 3], [0, 3], [0, 3],\n", + " [0, 2], [0, 2]]\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/contrib/nonogram_regular.ipynb b/examples/notebook/contrib/nonogram_regular.ipynb new file mode 100644 index 0000000000..d8f9a2148f --- /dev/null +++ b/examples/notebook/contrib/nonogram_regular.ipynb @@ -0,0 +1,335 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010 Hakan Kjellerstrand hakank@gmail.com\n", + "#\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"\n", + "\n", + " Nonogram (Painting by numbers) in Google CP Solver.\n", + "\n", + " http://en.wikipedia.org/wiki/Nonogram\n", + " '''\n", + " Nonograms or Paint by Numbers are picture logic puzzles in which cells in a\n", + " grid have to be colored or left blank according to numbers given at the\n", + " side of the grid to reveal a hidden picture. In this puzzle type, the\n", + " numbers measure how many unbroken lines of filled-in squares there are\n", + " in any given row or column. For example, a clue of '4 8 3' would mean\n", + " there are sets of four, eight, and three filled squares, in that order,\n", + " with at least one blank square between successive groups.\n", + "\n", + " '''\n", + "\n", + " See problem 12 at http://www.csplib.org/.\n", + "\n", + " http://www.puzzlemuseum.com/nonogram.htm\n", + "\n", + " Haskell solution:\n", + " http://twan.home.fmf.nl/blog/haskell/Nonograms.details\n", + "\n", + " Brunetti, Sara & Daurat, Alain (2003)\n", + " 'An algorithm reconstructing convex lattice sets'\n", + " http://geodisi.u-strasbg.fr/~daurat/papiers/tomoqconv.pdf\n", + "\n", + "\n", + " The Comet model (http://www.hakank.org/comet/nonogram_regular.co)\n", + " was a major influence when writing this Google CP solver model.\n", + "\n", + " I have also blogged about the development of a Nonogram solver in Comet\n", + " using the regular constraint.\n", + " * 'Comet: Nonogram improved: solving problem P200 from 1:30 minutes\n", + " to about 1 second'\n", + " http://www.hakank.org/constraint_programming_blog/2009/03/comet_nonogram_improved_solvin_1.html\n", + "\n", + " * 'Comet: regular constraint, a much faster Nonogram with the regular\n", + " constraint,\n", + " some OPL models, and more'\n", + " http://www.hakank.org/constraint_programming_blog/2009/02/comet_regular_constraint_a_muc_1.html\n", + "\n", + " Compare with the other models:\n", + " * Gecode/R: http://www.hakank.org/gecode_r/nonogram.rb (using 'regexps')\n", + " * MiniZinc: http://www.hakank.org/minizinc/nonogram_regular.mzn\n", + " * MiniZinc: http://www.hakank.org/minizinc/nonogram_create_automaton.mzn\n", + " * MiniZinc: http://www.hakank.org/minizinc/nonogram_create_automaton2.mzn\n", + " Note: nonogram_create_automaton2.mzn is the preferred model\n", + "\n", + " This model was created by Hakan Kjellerstrand (hakank@gmail.com)\n", + " Also see my other Google CP Solver models:\n", + " http://www.hakank.org/google_or_tools/\n", + "\n", + "\"\"\"\n", + "from __future__ import print_function\n", + "import sys\n", + "\n", + "from ortools.constraint_solver import pywrapcp\n", + "\n", + "\n", + "#\n", + "# Global constraint regular\n", + "#\n", + "# This is a translation of MiniZinc's regular constraint (defined in\n", + "# lib/zinc/globals.mzn), via the Comet code refered above.\n", + "# All comments are from the MiniZinc code.\n", + "# '''\n", + "# The sequence of values in array 'x' (which must all be in the range 1..S)\n", + "# is accepted by the DFA of 'Q' states with input 1..S and transition\n", + "# function 'd' (which maps (1..Q, 1..S) -> 0..Q)) and initial state 'q0'\n", + "# (which must be in 1..Q) and accepting states 'F' (which all must be in\n", + "# 1..Q). We reserve state 0 to be an always failing state.\n", + "# '''\n", + "#\n", + "# x : IntVar array\n", + "# Q : number of states\n", + "# S : input_max\n", + "# d : transition matrix\n", + "# q0: initial state\n", + "# F : accepting states\n", + "def regular(x, Q, S, d, q0, F):\n", + "\n", + " solver = x[0].solver()\n", + "\n", + " assert Q > 0, 'regular: \"Q\" must be greater than zero'\n", + " assert S > 0, 'regular: \"S\" must be greater than zero'\n", + "\n", + " # d2 is the same as d, except we add one extra transition for\n", + " # each possible input; each extra transition is from state zero\n", + " # to state zero. This allows us to continue even if we hit a\n", + " # non-accepted input.\n", + "\n", + " # int d2[0..Q, 1..S]\n", + " d2 = []\n", + " for i in range(Q + 1):\n", + " row = []\n", + " for j in range(S):\n", + " if i == 0:\n", + " row.append(0)\n", + " else:\n", + " row.append(d[i - 1][j])\n", + " d2.append(row)\n", + "\n", + " d2_flatten = [d2[i][j] for i in range(Q + 1) for j in range(S)]\n", + "\n", + " # If x has index set m..n, then a[m-1] holds the initial state\n", + " # (q0), and a[i+1] holds the state we're in after processing\n", + " # x[i]. If a[n] is in F, then we succeed (ie. accept the\n", + " # string).\n", + " x_range = list(range(0, len(x)))\n", + " m = 0\n", + " n = len(x)\n", + "\n", + " a = [solver.IntVar(0, Q + 1, 'a[%i]' % i) for i in range(m, n + 1)]\n", + "\n", + " # Check that the final state is in F\n", + " solver.Add(solver.MemberCt(a[-1], F))\n", + " # First state is q0\n", + " solver.Add(a[m] == q0)\n", + " for i in x_range:\n", + " solver.Add(x[i] >= 1)\n", + " solver.Add(x[i] <= S)\n", + " # Determine a[i+1]: a[i+1] == d2[a[i], x[i]]\n", + " solver.Add(\n", + " a[i + 1] == solver.Element(d2_flatten, ((a[i]) * S) + (x[i] - 1)))\n", + "\n", + "\n", + "#\n", + "# Make a transition (automaton) matrix from a\n", + "# single pattern, e.g. [3,2,1]\n", + "#\n", + "def make_transition_matrix(pattern):\n", + "\n", + " p_len = len(pattern)\n", + " num_states = p_len + sum(pattern)\n", + "\n", + " # this is for handling 0-clues. It generates\n", + " # just the state 1,2\n", + " if num_states == 0:\n", + " num_states = 1\n", + "\n", + " t_matrix = []\n", + " for i in range(num_states):\n", + " row = []\n", + " for j in range(2):\n", + " row.append(0)\n", + " t_matrix.append(row)\n", + "\n", + " # convert pattern to a 0/1 pattern for easy handling of\n", + " # the states\n", + " tmp = [0 for i in range(num_states)]\n", + " c = 0\n", + " tmp[c] = 0\n", + " for i in range(p_len):\n", + " for j in range(pattern[i]):\n", + " c += 1\n", + " tmp[c] = 1\n", + " if c < num_states - 1:\n", + " c += 1\n", + " tmp[c] = 0\n", + "\n", + " t_matrix[num_states - 1][0] = num_states\n", + " t_matrix[num_states - 1][1] = 0\n", + "\n", + " for i in range(num_states):\n", + " if tmp[i] == 0:\n", + " t_matrix[i][0] = i + 1\n", + " t_matrix[i][1] = i + 2\n", + " else:\n", + " if i < num_states - 1:\n", + " if tmp[i + 1] == 1:\n", + " t_matrix[i][0] = 0\n", + " t_matrix[i][1] = i + 2\n", + " else:\n", + " t_matrix[i][0] = i + 2\n", + " t_matrix[i][1] = 0\n", + "\n", + " # print 'The states:'\n", + " # for i in range(num_states):\n", + " # for j in range(2):\n", + " # print t_matrix[i][j],\n", + " # print\n", + " # print\n", + "\n", + " return t_matrix\n", + "\n", + "\n", + "#\n", + "# check each rule by creating an automaton\n", + "# and regular\n", + "#\n", + "\n", + "\n", + "def check_rule(rules, y):\n", + " solver = y[0].solver()\n", + "\n", + " r_len = sum([1 for i in range(len(rules)) if rules[i] > 0])\n", + " rules_tmp = []\n", + " for i in range(len(rules)):\n", + " if rules[i] > 0:\n", + " rules_tmp.append(rules[i])\n", + "\n", + " transition_fn = make_transition_matrix(rules_tmp)\n", + " n_states = len(transition_fn)\n", + " input_max = 2\n", + "\n", + " # Note: we cannot use 0 since it's the failing state\n", + " initial_state = 1\n", + " accepting_states = [n_states] # This is the last state\n", + "\n", + " regular(y, n_states, input_max, transition_fn, initial_state,\n", + " accepting_states)\n", + "\n", + "\n", + "\n", + "# Create the solver.\n", + "solver = pywrapcp.Solver('Regular test')\n", + "\n", + "#\n", + "# data\n", + "#\n", + "\n", + "#\n", + "# variables\n", + "#\n", + "board = {}\n", + "for i in range(rows):\n", + " for j in range(cols):\n", + " board[i, j] = solver.IntVar(1, 2, 'board[%i,%i]' % (i, j))\n", + "board_flat = [board[i, j] for i in range(rows) for j in range(cols)]\n", + "\n", + "# Flattened board for labeling.\n", + "# This labeling was inspired by a suggestion from\n", + "# Pascal Van Hentenryck about my Comet nonogram model.\n", + "board_label = []\n", + "if rows * row_rule_len < cols * col_rule_len:\n", + " for i in range(rows):\n", + " for j in range(cols):\n", + " board_label.append(board[i, j])\n", + "else:\n", + " for j in range(cols):\n", + " for i in range(rows):\n", + " board_label.append(board[i, j])\n", + "\n", + "#\n", + "# constraints\n", + "#\n", + "for i in range(rows):\n", + " check_rule([row_rules[i][j] for j in range(row_rule_len)],\n", + " [board[i, j] for j in range(cols)])\n", + "\n", + "for j in range(cols):\n", + " check_rule([col_rules[j][k] for k in range(col_rule_len)],\n", + " [board[i, j] for i in range(rows)])\n", + "\n", + "#\n", + "# solution and search\n", + "#\n", + "db = solver.Phase(board_label, solver.CHOOSE_FIRST_UNBOUND,\n", + " solver.ASSIGN_MIN_VALUE)\n", + "\n", + "solver.NewSearch(db)\n", + "\n", + "num_solutions = 0\n", + "while solver.NextSolution():\n", + " print()\n", + " num_solutions += 1\n", + " for i in range(rows):\n", + " row = [board[i, j].Value() - 1 for j in range(cols)]\n", + " row_pres = []\n", + " for j in row:\n", + " if j == 1:\n", + " row_pres.append('#')\n", + " else:\n", + " row_pres.append(' ')\n", + " print(' ', ''.join(row_pres))\n", + "\n", + " print()\n", + " print(' ', '-' * cols)\n", + "\n", + " if num_solutions >= 2:\n", + " print('2 solutions is enough...')\n", + " break\n", + "\n", + "solver.EndSearch()\n", + "print()\n", + "print('num_solutions:', num_solutions)\n", + "print('failures:', solver.Failures())\n", + "print('branches:', solver.Branches())\n", + "print('WallTime:', solver.WallTime(), 'ms')\n", + "\n", + "\n", + "#\n", + "# Default problem\n", + "#\n", + "# From http://twan.home.fmf.nl/blog/haskell/Nonograms.details\n", + "# The lambda picture\n", + "#rows = 12\n", + "row_rule_len = 3\n", + "row_rules = [[0, 0, 2], [0, 1, 2], [0, 1, 1], [0, 0, 2], [0, 0, 1], [0, 0, 3],\n", + " [0, 0, 3], [0, 2, 2], [0, 2, 1], [2, 2, 1], [0, 2, 3], [0, 2, 2]]\n", + "\n", + "cols = 10\n", + "col_rule_len = 2\n", + "col_rules = [[2, 1], [1, 3], [2, 4], [3, 4], [0, 4], [0, 3], [0, 3], [0, 3],\n", + " [0, 2], [0, 2]]\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/contrib/nonogram_table.ipynb b/examples/notebook/contrib/nonogram_table.ipynb new file mode 100644 index 0000000000..b21aefee57 --- /dev/null +++ b/examples/notebook/contrib/nonogram_table.ipynb @@ -0,0 +1,331 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010 Hakan Kjellerstrand hakank@gmail.com\n", + "#\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"\n", + "\n", + " Nonogram (Painting by numbers) in Google CP Solver.\n", + "\n", + " http://en.wikipedia.org/wiki/Nonogram\n", + " '''\n", + " Nonograms or Paint by Numbers are picture logic puzzles in which cells in a\n", + " grid have to be colored or left blank according to numbers given at the\n", + " side of the grid to reveal a hidden picture. In this puzzle type, the\n", + " numbers measure how many unbroken lines of filled-in squares there are\n", + " in any given row or column. For example, a clue of '4 8 3' would mean\n", + " there are sets of four, eight, and three filled squares, in that order,\n", + " with at least one blank square between successive groups.\n", + "\n", + " '''\n", + "\n", + " See problem 12 at http://www.csplib.org/.\n", + "\n", + " http://www.puzzlemuseum.com/nonogram.htm\n", + "\n", + " Haskell solution:\n", + " http://twan.home.fmf.nl/blog/haskell/Nonograms.details\n", + "\n", + " Brunetti, Sara & Daurat, Alain (2003)\n", + " 'An algorithm reconstructing convex lattice sets'\n", + " http://geodisi.u-strasbg.fr/~daurat/papiers/tomoqconv.pdf\n", + "\n", + "\n", + " The Comet model (http://www.hakank.org/comet/nonogram_regular.co)\n", + " was a major influence when writing this Google CP solver model.\n", + "\n", + " I have also blogged about the development of a Nonogram solver in Comet\n", + " using the regular constraint.\n", + " * 'Comet: Nonogram improved: solving problem P200 from 1:30 minutes\n", + " to about 1 second'\n", + " http://www.hakank.org/constraint_programming_blog/2009/03/comet_nonogram_improved_solvin_1.html\n", + "\n", + " * 'Comet: regular constraint, a much faster Nonogram with the regular\n", + " constraint,\n", + " some OPL models, and more'\n", + " http://www.hakank.org/constraint_programming_blog/2009/02/comet_regular_constraint_a_muc_1.html\n", + "\n", + " Compare with the other models:\n", + " * Gecode/R: http://www.hakank.org/gecode_r/nonogram.rb (using 'regexps')\n", + " * MiniZinc: http://www.hakank.org/minizinc/nonogram_regular.mzn\n", + " * MiniZinc: http://www.hakank.org/minizinc/nonogram_create_automaton.mzn\n", + " * MiniZinc: http://www.hakank.org/minizinc/nonogram_create_automaton2.mzn\n", + " Note: nonogram_create_automaton2.mzn is the preferred model\n", + "\n", + " This model was created by Hakan Kjellerstrand (hakank@gmail.com)\n", + " Also see my other Google CP Solver models:\n", + " http://www.hakank.org/google_or_tools/\n", + "\n", + "\"\"\"\n", + "from __future__ import print_function\n", + "import sys\n", + "\n", + "from ortools.constraint_solver import pywrapcp\n", + "\n", + "\n", + "#\n", + "# Global constraint regular\n", + "#\n", + "# This is a translation of MiniZinc's regular constraint (defined in\n", + "# lib/zinc/globals.mzn), via the Comet code refered above.\n", + "# All comments are from the MiniZinc code.\n", + "# '''\n", + "# The sequence of values in array 'x' (which must all be in the range 1..S)\n", + "# is accepted by the DFA of 'Q' states with input 1..S and transition\n", + "# function 'd' (which maps (1..Q, 1..S) -> 0..Q)) and initial state 'q0'\n", + "# (which must be in 1..Q) and accepting states 'F' (which all must be in\n", + "# 1..Q). We reserve state 0 to be an always failing state.\n", + "# '''\n", + "#\n", + "# x : IntVar array\n", + "# Q : number of states\n", + "# S : input_max\n", + "# d : transition matrix\n", + "# q0: initial state\n", + "# F : accepting states\n", + "def regular(x, Q, S, d, q0, F):\n", + "\n", + " solver = x[0].solver()\n", + "\n", + " assert Q > 0, 'regular: \"Q\" must be greater than zero'\n", + " assert S > 0, 'regular: \"S\" must be greater than zero'\n", + "\n", + " # d2 is the same as d, except we add one extra transition for\n", + " # each possible input; each extra transition is from state zero\n", + " # to state zero. This allows us to continue even if we hit a\n", + " # non-accepted input.\n", + "\n", + " d2 = []\n", + " for i in range(Q + 1):\n", + " for j in range(S):\n", + " if i == 0:\n", + " d2.append((0, j, 0))\n", + " else:\n", + " d2.append((i, j, d[i - 1][j]))\n", + "\n", + " # If x has index set m..n, then a[m-1] holds the initial state\n", + " # (q0), and a[i+1] holds the state we're in after processing\n", + " # x[i]. If a[n] is in F, then we succeed (ie. accept the\n", + " # string).\n", + " x_range = list(range(0, len(x)))\n", + " m = 0\n", + " n = len(x)\n", + "\n", + " a = [solver.IntVar(0, Q + 1, 'a[%i]' % i) for i in range(m, n + 1)]\n", + "\n", + " # Check that the final state is in F\n", + " solver.Add(solver.MemberCt(a[-1], F))\n", + " # First state is q0\n", + " solver.Add(a[m] == q0)\n", + " for i in x_range:\n", + " solver.Add(x[i] >= 1)\n", + " solver.Add(x[i] <= S)\n", + " # Determine a[i+1]: a[i+1] == d2[a[i], x[i]]\n", + " solver.Add(solver.AllowedAssignments((a[i], x[i] - 1, a[i + 1]), d2))\n", + "\n", + "\n", + "#\n", + "# Make a transition (automaton) matrix from a\n", + "# single pattern, e.g. [3,2,1]\n", + "#\n", + "\n", + "\n", + "def make_transition_matrix(pattern):\n", + "\n", + " p_len = len(pattern)\n", + " num_states = p_len + sum(pattern)\n", + "\n", + " # this is for handling 0-clues. It generates\n", + " # just the state 1,2\n", + " if num_states == 0:\n", + " num_states = 1\n", + "\n", + " t_matrix = []\n", + " for i in range(num_states):\n", + " row = []\n", + " for j in range(2):\n", + " row.append(0)\n", + " t_matrix.append(row)\n", + "\n", + " # convert pattern to a 0/1 pattern for easy handling of\n", + " # the states\n", + " tmp = [0 for i in range(num_states)]\n", + " c = 0\n", + " tmp[c] = 0\n", + " for i in range(p_len):\n", + " for j in range(pattern[i]):\n", + " c += 1\n", + " tmp[c] = 1\n", + " if c < num_states - 1:\n", + " c += 1\n", + " tmp[c] = 0\n", + "\n", + " t_matrix[num_states - 1][0] = num_states\n", + " t_matrix[num_states - 1][1] = 0\n", + "\n", + " for i in range(num_states):\n", + " if tmp[i] == 0:\n", + " t_matrix[i][0] = i + 1\n", + " t_matrix[i][1] = i + 2\n", + " else:\n", + " if i < num_states - 1:\n", + " if tmp[i + 1] == 1:\n", + " t_matrix[i][0] = 0\n", + " t_matrix[i][1] = i + 2\n", + " else:\n", + " t_matrix[i][0] = i + 2\n", + " t_matrix[i][1] = 0\n", + "\n", + " # print 'The states:'\n", + " # for i in range(num_states):\n", + " # for j in range(2):\n", + " # print t_matrix[i][j],\n", + " # print\n", + " # print\n", + "\n", + " return t_matrix\n", + "\n", + "\n", + "#\n", + "# check each rule by creating an automaton\n", + "# and regular\n", + "#\n", + "\n", + "\n", + "def check_rule(rules, y):\n", + " solver = y[0].solver()\n", + "\n", + " r_len = sum([1 for i in range(len(rules)) if rules[i] > 0])\n", + " rules_tmp = []\n", + " for i in range(len(rules)):\n", + " if rules[i] > 0:\n", + " rules_tmp.append(rules[i])\n", + "\n", + " transition_fn = make_transition_matrix(rules_tmp)\n", + " n_states = len(transition_fn)\n", + " input_max = 2\n", + "\n", + " # Note: we cannot use 0 since it's the failing state\n", + " initial_state = 1\n", + " accepting_states = [n_states] # This is the last state\n", + "\n", + " regular(y, n_states, input_max, transition_fn, initial_state,\n", + " accepting_states)\n", + "\n", + "\n", + "\n", + "# Create the solver.\n", + "solver = pywrapcp.Solver('Regular test')\n", + "\n", + "#\n", + "# data\n", + "#\n", + "\n", + "#\n", + "# variables\n", + "#\n", + "board = {}\n", + "for i in range(rows):\n", + " for j in range(cols):\n", + " board[i, j] = solver.IntVar(1, 2, 'board[%i, %i]' % (i, j))\n", + "board_flat = [board[i, j] for i in range(rows) for j in range(cols)]\n", + "\n", + "# Flattened board for labeling.\n", + "# This labeling was inspired by a suggestion from\n", + "# Pascal Van Hentenryck about my Comet nonogram model.\n", + "board_label = []\n", + "if rows * row_rule_len < cols * col_rule_len:\n", + " for i in range(rows):\n", + " for j in range(cols):\n", + " board_label.append(board[i, j])\n", + "else:\n", + " for j in range(cols):\n", + " for i in range(rows):\n", + " board_label.append(board[i, j])\n", + "\n", + "#\n", + "# constraints\n", + "#\n", + "for i in range(rows):\n", + " check_rule([row_rules[i][j] for j in range(row_rule_len)],\n", + " [board[i, j] for j in range(cols)])\n", + "\n", + "for j in range(cols):\n", + " check_rule([col_rules[j][k] for k in range(col_rule_len)],\n", + " [board[i, j] for i in range(rows)])\n", + "\n", + "#\n", + "# solution and search\n", + "#\n", + "db = solver.Phase(board_label, solver.CHOOSE_FIRST_UNBOUND,\n", + " solver.ASSIGN_MIN_VALUE)\n", + "\n", + "solver.NewSearch(db)\n", + "\n", + "num_solutions = 0\n", + "while solver.NextSolution():\n", + " print()\n", + " num_solutions += 1\n", + " for i in range(rows):\n", + " row = [board[i, j].Value() - 1 for j in range(cols)]\n", + " row_pres = []\n", + " for j in row:\n", + " if j == 1:\n", + " row_pres.append('#')\n", + " else:\n", + " row_pres.append(' ')\n", + " print(' ', ''.join(row_pres))\n", + "\n", + " print()\n", + " print(' ', '-' * cols)\n", + "\n", + " if num_solutions >= 2:\n", + " print('2 solutions is enough...')\n", + " break\n", + "\n", + "solver.EndSearch()\n", + "print()\n", + "print('num_solutions:', num_solutions)\n", + "print('failures:', solver.Failures())\n", + "print('branches:', solver.Branches())\n", + "print('WallTime:', solver.WallTime(), 'ms')\n", + "\n", + "\n", + "#\n", + "# Default problem\n", + "#\n", + "# From http://twan.home.fmf.nl/blog/haskell/Nonograms.details\n", + "# The lambda picture\n", + "#rows = 12\n", + "row_rule_len = 3\n", + "row_rules = [[0, 0, 2], [0, 1, 2], [0, 1, 1], [0, 0, 2], [0, 0, 1], [0, 0, 3],\n", + " [0, 0, 3], [0, 2, 2], [0, 2, 1], [2, 2, 1], [0, 2, 3], [0, 2, 2]]\n", + "\n", + "cols = 10\n", + "col_rule_len = 2\n", + "col_rules = [[2, 1], [1, 3], [2, 4], [3, 4], [0, 4], [0, 3], [0, 3], [0, 3],\n", + " [0, 2], [0, 2]]\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/contrib/nonogram_table2.ipynb b/examples/notebook/contrib/nonogram_table2.ipynb new file mode 100644 index 0000000000..e3e562cc3a --- /dev/null +++ b/examples/notebook/contrib/nonogram_table2.ipynb @@ -0,0 +1,231 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010 Hakan Kjellerstrand hakank@gmail.com\n", + "#\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"\n", + "\n", + " Nonogram (Painting by numbers) in Google CP Solver.\n", + "\n", + " http://en.wikipedia.org/wiki/Nonogram\n", + " '''\n", + " Nonograms or Paint by Numbers are picture logic puzzles in which cells in a\n", + " grid have to be colored or left blank according to numbers given at the\n", + " side of the grid to reveal a hidden picture. In this puzzle type, the\n", + " numbers measure how many unbroken lines of filled-in squares there are\n", + " in any given row or column. For example, a clue of '4 8 3' would mean\n", + " there are sets of four, eight, and three filled squares, in that order,\n", + " with at least one blank square between successive groups.\n", + "\n", + " '''\n", + "\n", + " See problem 12 at http://www.csplib.org/.\n", + "\n", + " http://www.puzzlemuseum.com/nonogram.htm\n", + "\n", + " Haskell solution:\n", + " http://twan.home.fmf.nl/blog/haskell/Nonograms.details\n", + "\n", + " Brunetti, Sara & Daurat, Alain (2003)\n", + " 'An algorithm reconstructing convex lattice sets'\n", + " http://geodisi.u-strasbg.fr/~daurat/papiers/tomoqconv.pdf\n", + "\n", + "\n", + " The Comet model (http://www.hakank.org/comet/nonogram_regular.co)\n", + " was a major influence when writing this Google CP solver model.\n", + "\n", + " I have also blogged about the development of a Nonogram solver in Comet\n", + " using the regular constraint.\n", + " * 'Comet: Nonogram improved: solving problem P200 from 1:30 minutes\n", + " to about 1 second'\n", + " http://www.hakank.org/constraint_programming_blog/2009/03/comet_nonogram_improved_solvin_1.html\n", + "\n", + " * 'Comet: regular constraint, a much faster Nonogram with the regular\n", + " constraint,\n", + " some OPL models, and more'\n", + " http://www.hakank.org/constraint_programming_blog/2009/02/comet_regular_constraint_a_muc_1.html\n", + "\n", + " Compare with the other models:\n", + " * Gecode/R: http://www.hakank.org/gecode_r/nonogram.rb (using 'regexps')\n", + " * MiniZinc: http://www.hakank.org/minizinc/nonogram_regular.mzn\n", + " * MiniZinc: http://www.hakank.org/minizinc/nonogram_create_automaton.mzn\n", + " * MiniZinc: http://www.hakank.org/minizinc/nonogram_create_automaton2.mzn\n", + " Note: nonogram_create_automaton2.mzn is the preferred model\n", + "\n", + " This model was created by Hakan Kjellerstrand (hakank@gmail.com)\n", + " Also see my other Google CP Solver models:\n", + " http://www.hakank.org/google_or_tools/\n", + "\n", + "\"\"\"\n", + "from __future__ import print_function\n", + "import sys\n", + "\n", + "from ortools.constraint_solver import pywrapcp\n", + "\n", + "\n", + "#\n", + "# Make a transition (automaton) list of tuples from a\n", + "# single pattern, e.g. [3,2,1]\n", + "#\n", + "def make_transition_tuples(pattern):\n", + " p_len = len(pattern)\n", + " num_states = p_len + sum(pattern)\n", + "\n", + " tuples = []\n", + "\n", + " # this is for handling 0-clues. It generates\n", + " # just the minimal state\n", + " if num_states == 0:\n", + " tuples.append((1, 0, 1))\n", + " return (tuples, 1)\n", + "\n", + " # convert pattern to a 0/1 pattern for easy handling of\n", + " # the states\n", + " tmp = [0]\n", + " c = 0\n", + " for pattern_index in range(p_len):\n", + " tmp.extend([1] * pattern[pattern_index])\n", + " tmp.append(0)\n", + "\n", + " for i in range(num_states):\n", + " state = i + 1\n", + " if tmp[i] == 0:\n", + " tuples.append((state, 0, state))\n", + " tuples.append((state, 1, state + 1))\n", + " else:\n", + " if i < num_states - 1:\n", + " if tmp[i + 1] == 1:\n", + " tuples.append((state, 1, state + 1))\n", + " else:\n", + " tuples.append((state, 0, state + 1))\n", + " tuples.append((num_states, 0, num_states))\n", + " return (tuples, num_states)\n", + "\n", + "\n", + "#\n", + "# check each rule by creating an automaton and transition constraint.\n", + "#\n", + "def check_rule(rules, y):\n", + " cleaned_rule = [rules[i] for i in range(len(rules)) if rules[i] > 0]\n", + " (transition_tuples, last_state) = make_transition_tuples(cleaned_rule)\n", + "\n", + " initial_state = 1\n", + " accepting_states = [last_state]\n", + "\n", + " solver = y[0].solver()\n", + " solver.Add(\n", + " solver.TransitionConstraint(y, transition_tuples, initial_state,\n", + " accepting_states))\n", + "\n", + "\n", + "\n", + "# Create the solver.\n", + "solver = pywrapcp.Solver('Regular test')\n", + "\n", + "#\n", + "# variables\n", + "#\n", + "board = {}\n", + "for i in range(rows):\n", + " for j in range(cols):\n", + " board[i, j] = solver.IntVar(0, 1, 'board[%i, %i]' % (i, j))\n", + "board_flat = [board[i, j] for i in range(rows) for j in range(cols)]\n", + "\n", + "# Flattened board for labeling.\n", + "# This labeling was inspired by a suggestion from\n", + "# Pascal Van Hentenryck about my Comet nonogram model.\n", + "board_label = []\n", + "if rows * row_rule_len < cols * col_rule_len:\n", + " for i in range(rows):\n", + " for j in range(cols):\n", + " board_label.append(board[i, j])\n", + "else:\n", + " for j in range(cols):\n", + " for i in range(rows):\n", + " board_label.append(board[i, j])\n", + "\n", + "#\n", + "# constraints\n", + "#\n", + "for i in range(rows):\n", + " check_rule(row_rules[i], [board[i, j] for j in range(cols)])\n", + "\n", + "for j in range(cols):\n", + " check_rule(col_rules[j], [board[i, j] for i in range(rows)])\n", + "\n", + "#\n", + "# solution and search\n", + "#\n", + "db = solver.Phase(board_label, solver.CHOOSE_FIRST_UNBOUND,\n", + " solver.ASSIGN_MIN_VALUE)\n", + "\n", + "print('before solver, wall time = ', solver.WallTime(), 'ms')\n", + "solver.NewSearch(db)\n", + "\n", + "num_solutions = 0\n", + "while solver.NextSolution():\n", + " print()\n", + " num_solutions += 1\n", + " for i in range(rows):\n", + " row = [board[i, j].Value() for j in range(cols)]\n", + " row_pres = []\n", + " for j in row:\n", + " if j == 1:\n", + " row_pres.append('#')\n", + " else:\n", + " row_pres.append(' ')\n", + " print(' ', ''.join(row_pres))\n", + "\n", + " print()\n", + " print(' ', '-' * cols)\n", + "\n", + " if num_solutions >= 2:\n", + " print('2 solutions is enough...')\n", + " break\n", + "\n", + "solver.EndSearch()\n", + "print()\n", + "print('num_solutions:', num_solutions)\n", + "print('failures:', solver.Failures())\n", + "print('branches:', solver.Branches())\n", + "print('WallTime:', solver.WallTime(), 'ms')\n", + "\n", + "\n", + "#\n", + "# Default problem\n", + "#\n", + "# From http://twan.home.fmf.nl/blog/haskell/Nonograms.details\n", + "# The lambda picture\n", + "#rows = 12\n", + "row_rule_len = 3\n", + "row_rules = [[0, 0, 2], [0, 1, 2], [0, 1, 1], [0, 0, 2], [0, 0, 1], [0, 0, 3],\n", + " [0, 0, 3], [0, 2, 2], [0, 2, 1], [2, 2, 1], [0, 2, 3], [0, 2, 2]]\n", + "\n", + "cols = 10\n", + "col_rule_len = 2\n", + "col_rules = [[2, 1], [1, 3], [2, 4], [3, 4], [0, 4], [0, 3], [0, 3], [0, 3],\n", + " [0, 2], [0, 2]]\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/contrib/nontransitive_dice.ipynb b/examples/notebook/contrib/nontransitive_dice.ipynb new file mode 100644 index 0000000000..8c0da9a87e --- /dev/null +++ b/examples/notebook/contrib/nontransitive_dice.ipynb @@ -0,0 +1,217 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010 Hakan Kjellerstrand hakank@gmail.com\n", + "#\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"\n", + "\n", + " Nontransitive dice in Google CP Solver.\n", + "\n", + " From\n", + " http://en.wikipedia.org/wiki/Nontransitive_dice\n", + " '''\n", + " A set of nontransitive dice is a set of dice for which the relation\n", + " 'is more likely to roll a higher number' is not transitive. See also\n", + " intransitivity.\n", + "\n", + " This situation is similar to that in the game Rock, Paper, Scissors,\n", + " in which each element has an advantage over one choice and a\n", + " disadvantage to the other.\n", + " '''\n", + "\n", + " I start with the 3 dice version\n", + " '''\n", + " * die A has sides {2,2,4,4,9,9},\n", + " * die B has sides {1,1,6,6,8,8}, and\n", + " * die C has sides {3,3,5,5,7,7}.\n", + " '''\n", + "\n", + " 3 dice:\n", + " Maximum winning: 27\n", + " comp: [19, 27, 19]\n", + " dice:\n", + " [[0, 0, 3, 6, 6, 6],\n", + " [2, 5, 5, 5, 5, 5],\n", + " [1, 1, 4, 4, 4, 7]]\n", + " max_win: 27\n", + "\n", + " Number of solutions: 1\n", + " Nodes: 1649873 Time: 25.94\n", + " getFailures: 1649853\n", + " getBacktracks: 1649873\n", + " getPropags: 98105090\n", + "\n", + " Max winnings where they are the same: 21\n", + " comp: [21, 21, 21]\n", + " dice:\n", + " [[0, 0, 3, 3, 3, 6],\n", + " [2, 2, 2, 2, 2, 5],\n", + " [1, 1, 1, 4, 4, 4]]\n", + " max_win: 21\n", + "\n", + " Compare with these models:\n", + " * MiniZinc: http://hakank.org/minizinc/nontransitive_dice.mzn\n", + " * Comet: http://hakank.org/comet/nontransitive_dice.co\n", + "\n", + "\n", + " This model was created by Hakan Kjellerstrand (hakank@gmail.com)\n", + " Also see my other Google CP Solver models:\n", + " http://www.hakank.org/google_or_tools/\n", + "\"\"\"\n", + "from __future__ import print_function\n", + "import sys\n", + "from ortools.constraint_solver import pywrapcp\n", + "\n", + "\n", + "\n", + "# Create the solver.\n", + "solver = pywrapcp.Solver(\"Nontransitive dice\")\n", + "\n", + "#\n", + "# data\n", + "#\n", + "print(\"number of dice:\", m)\n", + "print(\"number of sides:\", n)\n", + "\n", + "#\n", + "# declare variables\n", + "#\n", + "\n", + "dice = {}\n", + "for i in range(m):\n", + " for j in range(n):\n", + " dice[(i, j)] = solver.IntVar(1, n * 2, \"dice(%i,%i)\" % (i, j))\n", + "dice_flat = [dice[(i, j)] for i in range(m) for j in range(n)]\n", + "\n", + "comp = {}\n", + "for i in range(m):\n", + " for j in range(2):\n", + " comp[(i, j)] = solver.IntVar(0, n * n, \"comp(%i,%i)\" % (i, j))\n", + "comp_flat = [comp[(i, j)] for i in range(m) for j in range(2)]\n", + "\n", + "# The following variables are for summaries or objectives\n", + "gap = [solver.IntVar(0, n * n, \"gap(%i)\" % i) for i in range(m)]\n", + "gap_sum = solver.IntVar(0, m * n * n, \"gap_sum\")\n", + "\n", + "max_val = solver.IntVar(0, n * 2, \"max_val\")\n", + "max_win = solver.IntVar(0, n * n, \"max_win\")\n", + "\n", + "# number of occurrences of each value of the dice\n", + "counts = [solver.IntVar(0, n * m, \"counts(%i)\" % i) for i in range(n * 2 + 1)]\n", + "\n", + "#\n", + "# constraints\n", + "#\n", + "\n", + "# number of occurrences for each number\n", + "solver.Add(solver.Distribute(dice_flat, list(range(n * 2 + 1)), counts))\n", + "\n", + "solver.Add(max_win == solver.Max(comp_flat))\n", + "solver.Add(max_val == solver.Max(dice_flat))\n", + "\n", + "# order of the number of each die, lowest first\n", + "[\n", + " solver.Add(dice[(i, j)] <= dice[(i, j + 1)])\n", + " for i in range(m)\n", + " for j in range(n - 1)\n", + "]\n", + "\n", + "# nontransitivity\n", + "[comp[i, 0] > comp[i, 1] for i in range(m)],\n", + "\n", + "# probability gap\n", + "[solver.Add(gap[i] == comp[i, 0] - comp[i, 1]) for i in range(m)]\n", + "[solver.Add(gap[i] > 0) for i in range(m)]\n", + "solver.Add(gap_sum == solver.Sum(gap))\n", + "\n", + "# and now we roll...\n", + "# Number of wins for [A vs B, B vs A]\n", + "for d in range(m):\n", + " b1 = [\n", + " solver.IsGreaterVar(dice[d % m, r1], dice[(d + 1) % m, r2])\n", + " for r1 in range(n)\n", + " for r2 in range(n)\n", + " ]\n", + " solver.Add(comp[d % m, 0] == solver.Sum(b1))\n", + "\n", + " b2 = [\n", + " solver.IsGreaterVar(dice[(d + 1) % m, r1], dice[d % m, r2])\n", + " for r1 in range(n)\n", + " for r2 in range(n)\n", + " ]\n", + " solver.Add(comp[d % m, 1] == solver.Sum(b2))\n", + "\n", + "# objective\n", + "if minimize_val != 0:\n", + " print(\"Minimizing max_val\")\n", + " objective = solver.Minimize(max_val, 1)\n", + " # other experiments\n", + " # objective = solver.Maximize(max_win, 1)\n", + " # objective = solver.Maximize(gap_sum, 1)\n", + "\n", + "#\n", + "# solution and search\n", + "#\n", + "db = solver.Phase(dice_flat + comp_flat, solver.INT_VAR_DEFAULT,\n", + " solver.ASSIGN_MIN_VALUE)\n", + "\n", + "if minimize_val:\n", + " solver.NewSearch(db, [objective])\n", + "else:\n", + " solver.NewSearch(db)\n", + "\n", + "num_solutions = 0\n", + "while solver.NextSolution():\n", + " print(\"gap_sum:\", gap_sum.Value())\n", + " print(\"gap:\", [gap[i].Value() for i in range(m)])\n", + " print(\"max_val:\", max_val.Value())\n", + " print(\"max_win:\", max_win.Value())\n", + " print(\"dice:\")\n", + " for i in range(m):\n", + " for j in range(n):\n", + " print(dice[(i, j)].Value(), end=\" \")\n", + " print()\n", + " print(\"comp:\")\n", + " for i in range(m):\n", + " for j in range(2):\n", + " print(comp[(i, j)].Value(), end=\" \")\n", + " print()\n", + " print(\"counts:\", [counts[i].Value() for i in range(n * 2 + 1)])\n", + " print()\n", + "\n", + " num_solutions += 1\n", + "\n", + "solver.EndSearch()\n", + "\n", + "print()\n", + "print(\"num_solutions:\", num_solutions)\n", + "print(\"failures:\", solver.Failures())\n", + "print(\"branches:\", solver.Branches())\n", + "print(\"WallTime:\", solver.WallTime())\n", + "\n", + "m = 3 # number of dice\n", + "n = 6 # number of sides of each die\n", + "minimize_val = 0 # Minimizing max value (0: no, 1: yes)\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/contrib/nqueens.ipynb b/examples/notebook/contrib/nqueens.ipynb new file mode 100644 index 0000000000..27ac9c75b9 --- /dev/null +++ b/examples/notebook/contrib/nqueens.ipynb @@ -0,0 +1,108 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010 Hakan Kjellerstrand hakank@gmail.com\n", + "#\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"\n", + "\n", + " n-queens problem in Google CP Solver.\n", + "\n", + " N queens problem.\n", + "\n", + " This model was created by Hakan Kjellerstrand (hakank@gmail.com)\n", + " Also see my other Google CP Solver models:\n", + " http://www.hakank.org/google_or_tools/\n", + "\"\"\"\n", + "from __future__ import print_function\n", + "from ortools.constraint_solver import pywrapcp\n", + "\n", + "\n", + "# Create the solver.\n", + "solver = pywrapcp.Solver(\"n-queens\")\n", + "\n", + "#\n", + "# data\n", + "#\n", + "# n = 8 # size of board (n x n)\n", + "\n", + "# declare variables\n", + "q = [solver.IntVar(0, n - 1, \"x%i\" % i) for i in range(n)]\n", + "\n", + "#\n", + "# constraints\n", + "#\n", + "solver.Add(solver.AllDifferent(q))\n", + "for i in range(n):\n", + " for j in range(i):\n", + " solver.Add(q[i] != q[j])\n", + " solver.Add(q[i] + i != q[j] + j)\n", + " solver.Add(q[i] - i != q[j] - j)\n", + "\n", + "# for i in range(n):\n", + "# for j in range(i):\n", + "# solver.Add(abs(q[i]-q[j]) != abs(i-j))\n", + "\n", + "# symmetry breaking\n", + "# solver.Add(q[0] == 0)\n", + "\n", + "#\n", + "# solution and search\n", + "#\n", + "solution = solver.Assignment()\n", + "solution.Add([q[i] for i in range(n)])\n", + "\n", + "collector = solver.AllSolutionCollector(solution)\n", + "# collector = solver.FirstSolutionCollector(solution)\n", + "# search_log = solver.SearchLog(100, x[0])\n", + "solver.Solve(\n", + " solver.Phase([q[i] for i in range(n)], solver.INT_VAR_SIMPLE,\n", + " solver.ASSIGN_MIN_VALUE), [collector])\n", + "\n", + "num_solutions = collector.SolutionCount()\n", + "print(\"num_solutions: \", num_solutions)\n", + "if num_solutions > 0:\n", + " for s in range(num_solutions):\n", + " qval = [collector.Value(s, q[i]) for i in range(n)]\n", + " print(\"q:\", qval)\n", + " for i in range(n):\n", + " for j in range(n):\n", + " if qval[i] == j:\n", + " print(\"Q\", end=\" \")\n", + " else:\n", + " print(\"_\", end=\" \")\n", + " print()\n", + " print()\n", + "\n", + " print()\n", + " print(\"num_solutions:\", num_solutions)\n", + " print(\"failures:\", solver.Failures())\n", + " print(\"branches:\", solver.Branches())\n", + " print(\"WallTime:\", solver.WallTime())\n", + "\n", + "else:\n", + " print(\"No solutions found\")\n", + "\n", + "n = 8\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/contrib/nqueens2.ipynb b/examples/notebook/contrib/nqueens2.ipynb new file mode 100644 index 0000000000..a75293253a --- /dev/null +++ b/examples/notebook/contrib/nqueens2.ipynb @@ -0,0 +1,110 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010 Hakan Kjellerstrand hakank@gmail.com\n", + "#\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"\n", + "\n", + " n-queens problem in Google CP Solver.\n", + "\n", + " N queens problem.\n", + "\n", + " This version use NewSearch()/NextSolution() for looping through\n", + " the solutions.\n", + "\n", + " This model was created by Hakan Kjellerstrand (hakank@gmail.com)\n", + " Also see my other Google CP Solver models:\n", + " http://www.hakank.org/google_or_tools/\n", + "\"\"\"\n", + "from __future__ import print_function\n", + "import sys\n", + "from ortools.constraint_solver import pywrapcp\n", + "\n", + "\n", + "# Create the solver.\n", + "solver = pywrapcp.Solver(\"n-queens\")\n", + "\n", + "#\n", + "# data\n", + "#\n", + "# n = 8 # size of board (n x n)\n", + "\n", + "# declare variables\n", + "q = [solver.IntVar(0, n - 1, \"x%i\" % i) for i in range(n)]\n", + "\n", + "#\n", + "# constraints\n", + "#\n", + "solver.Add(solver.AllDifferent(q))\n", + "for i in range(n):\n", + " for j in range(i):\n", + " solver.Add(q[i] != q[j])\n", + " solver.Add(q[i] + i != q[j] + j)\n", + " solver.Add(q[i] - i != q[j] - j)\n", + "\n", + "# for i in range(n):\n", + "# for j in range(i):\n", + "# solver.Add(abs(q[i]-q[j]) != abs(i-j))\n", + "\n", + "# symmetry breaking\n", + "# solver.Add(q[0] == 0)\n", + "\n", + "#\n", + "# solution and search\n", + "#\n", + "solution = solver.Assignment()\n", + "solution.Add([q[i] for i in range(n)])\n", + "\n", + "# db: DecisionBuilder\n", + "db = solver.Phase(\n", + " [q[i] for i in range(n)],\n", + " # solver.CHOOSE_FIRST_UNBOUND,\n", + " solver.CHOOSE_MIN_SIZE_LOWEST_MAX,\n", + " solver.ASSIGN_CENTER_VALUE)\n", + "\n", + "solver.NewSearch(db)\n", + "num_solutions = 0\n", + "while solver.NextSolution():\n", + " qval = [q[i].Value() for i in range(n)]\n", + " print(\"q:\", qval)\n", + " for i in range(n):\n", + " for j in range(n):\n", + " if qval[i] == j:\n", + " print(\"Q\", end=\" \")\n", + " else:\n", + " print(\"_\", end=\" \")\n", + " print()\n", + " print()\n", + " num_solutions += 1\n", + "solver.EndSearch()\n", + "\n", + "print()\n", + "print(\"num_solutions:\", num_solutions)\n", + "print(\"failures:\", solver.Failures())\n", + "print(\"branches:\", solver.Branches())\n", + "print(\"WallTime:\", solver.WallTime())\n", + "\n", + "n = 8\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/contrib/nqueens3.ipynb b/examples/notebook/contrib/nqueens3.ipynb new file mode 100644 index 0000000000..4feccbe994 --- /dev/null +++ b/examples/notebook/contrib/nqueens3.ipynb @@ -0,0 +1,107 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010 Hakan Kjellerstrand hakank@gmail.com\n", + "#\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"\n", + "\n", + " n-queens problem in Google CP Solver.\n", + "\n", + " N queens problem.\n", + "\n", + " Faster than the previous versions:\n", + " - http://www.hakank.org/gogle_cp_solver/nqueens.py\n", + " - http://www.hakank.org/gogle_cp_solver/nqueens2.py\n", + "\n", + "\n", + " This model was created by Hakan Kjellerstrand (hakank@gmail.com)\n", + " Also see my other Google CP Solver models:\n", + " http://www.hakank.org/google_or_tools/\n", + "\"\"\"\n", + "from __future__ import print_function\n", + "import sys\n", + "from ortools.constraint_solver import pywrapcp\n", + "\n", + "\n", + "# Create the solver.\n", + "solver = pywrapcp.Solver(\"n-queens\")\n", + "\n", + "#\n", + "# data\n", + "#\n", + "print(\"n:\", n)\n", + "print(\"num_sol:\", num_sol)\n", + "print(\"print_sol:\", print_sol)\n", + "\n", + "# declare variables\n", + "q = [solver.IntVar(0, n - 1, \"x%i\" % i) for i in range(n)]\n", + "\n", + "#\n", + "# constraints\n", + "#\n", + "solver.Add(solver.AllDifferent(q))\n", + "solver.Add(solver.AllDifferent([q[i] + i for i in range(n)]))\n", + "solver.Add(solver.AllDifferent([q[i] - i for i in range(n)]))\n", + "\n", + "# symmetry breaking\n", + "# solver.Add(q[0] == 0)\n", + "\n", + "#\n", + "# search\n", + "#\n", + "\n", + "db = solver.Phase(q, solver.CHOOSE_MIN_SIZE_LOWEST_MAX,\n", + " solver.ASSIGN_CENTER_VALUE)\n", + "\n", + "solver.NewSearch(db)\n", + "num_solutions = 0\n", + "while solver.NextSolution():\n", + " if print_sol:\n", + " qval = [q[i].Value() for i in range(n)]\n", + " print(\"q:\", qval)\n", + " for i in range(n):\n", + " for j in range(n):\n", + " if qval[i] == j:\n", + " print(\"Q\", end=\" \")\n", + " else:\n", + " print(\"_\", end=\" \")\n", + " print()\n", + " print()\n", + " num_solutions += 1\n", + " if num_sol > 0 and num_solutions >= num_sol:\n", + " break\n", + "\n", + "solver.EndSearch()\n", + "\n", + "print()\n", + "print(\"num_solutions:\", num_solutions)\n", + "print(\"failures:\", solver.Failures())\n", + "print(\"branches:\", solver.Branches())\n", + "print(\"WallTime:\", solver.WallTime(), \"ms\")\n", + "\n", + "n = 8\n", + "num_sol = 0\n", + "print_sol = 1\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/contrib/nurse_rostering.ipynb b/examples/notebook/contrib/nurse_rostering.ipynb new file mode 100644 index 0000000000..f19a138618 --- /dev/null +++ b/examples/notebook/contrib/nurse_rostering.ipynb @@ -0,0 +1,278 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010 Hakan Kjellerstrand hakank@gmail.com\n", + "#\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"\n", + "\n", + " Nurse rostering in Google CP Solver.\n", + "\n", + " This is a simple nurse rostering model using a DFA and\n", + " my decomposition of regular constraint.\n", + "\n", + " The DFA is from MiniZinc Tutorial, Nurse Rostering example:\n", + " - one day off every 4 days\n", + " - no 3 nights in a row.\n", + "\n", + "\n", + " This model was created by Hakan Kjellerstrand (hakank@gmail.com)\n", + " Also see my other Google CP Solver models:\n", + " http://www.hakank.org/google_or_tools/\n", + "\n", + "\"\"\"\n", + "from __future__ import print_function\n", + "from ortools.constraint_solver import pywrapcp\n", + "from collections import defaultdict\n", + "\n", + "#\n", + "# Global constraint regular\n", + "#\n", + "# This is a translation of MiniZinc's regular constraint (defined in\n", + "# lib/zinc/globals.mzn), via the Comet code refered above.\n", + "# All comments are from the MiniZinc code.\n", + "# '''\n", + "# The sequence of values in array 'x' (which must all be in the range 1..S)\n", + "# is accepted by the DFA of 'Q' states with input 1..S and transition\n", + "# function 'd' (which maps (1..Q, 1..S) -> 0..Q)) and initial state 'q0'\n", + "# (which must be in 1..Q) and accepting states 'F' (which all must be in\n", + "# 1..Q). We reserve state 0 to be an always failing state.\n", + "# '''\n", + "#\n", + "# x : IntVar array\n", + "# Q : number of states\n", + "# S : input_max\n", + "# d : transition matrix\n", + "# q0: initial state\n", + "# F : accepting states\n", + "\n", + "\n", + "def regular(x, Q, S, d, q0, F):\n", + "\n", + " solver = x[0].solver()\n", + "\n", + " assert Q > 0, 'regular: \"Q\" must be greater than zero'\n", + " assert S > 0, 'regular: \"S\" must be greater than zero'\n", + "\n", + " # d2 is the same as d, except we add one extra transition for\n", + " # each possible input; each extra transition is from state zero\n", + " # to state zero. This allows us to continue even if we hit a\n", + " # non-accepted input.\n", + "\n", + " # Comet: int d2[0..Q, 1..S]\n", + " d2 = []\n", + " for i in range(Q + 1):\n", + " row = []\n", + " for j in range(S):\n", + " if i == 0:\n", + " row.append(0)\n", + " else:\n", + " row.append(d[i - 1][j])\n", + " d2.append(row)\n", + "\n", + " d2_flatten = [d2[i][j] for i in range(Q + 1) for j in range(S)]\n", + "\n", + " # If x has index set m..n, then a[m-1] holds the initial state\n", + " # (q0), and a[i+1] holds the state we're in after processing\n", + " # x[i]. If a[n] is in F, then we succeed (ie. accept the\n", + " # string).\n", + " x_range = list(range(0, len(x)))\n", + " m = 0\n", + " n = len(x)\n", + "\n", + " a = [solver.IntVar(0, Q + 1, 'a[%i]' % i) for i in range(m, n + 1)]\n", + "\n", + " # Check that the final state is in F\n", + " solver.Add(solver.MemberCt(a[-1], F))\n", + " # First state is q0\n", + " solver.Add(a[m] == q0)\n", + " for i in x_range:\n", + " solver.Add(x[i] >= 1)\n", + " solver.Add(x[i] <= S)\n", + "\n", + " # Determine a[i+1]: a[i+1] == d2[a[i], x[i]]\n", + " solver.Add(\n", + " a[i + 1] == solver.Element(d2_flatten, ((a[i]) * S) + (x[i] - 1)))\n", + "\n", + "\n", + "\n", + "# Create the solver.\n", + "solver = pywrapcp.Solver('Nurse rostering using regular')\n", + "\n", + "#\n", + "# data\n", + "#\n", + "\n", + "# Note: If you change num_nurses or num_days,\n", + "# please also change the constraints\n", + "# on nurse_stat and/or day_stat.\n", + "num_nurses = 7\n", + "num_days = 14\n", + "\n", + "day_shift = 1\n", + "night_shift = 2\n", + "off_shift = 3\n", + "shifts = [day_shift, night_shift, off_shift]\n", + "\n", + "# the DFA (for regular)\n", + "n_states = 6\n", + "input_max = 3\n", + "initial_state = 1 # 0 is for the failing state\n", + "accepting_states = [1, 2, 3, 4, 5, 6]\n", + "\n", + "transition_fn = [\n", + " # d,n,o\n", + " [2, 3, 1], # state 1\n", + " [4, 4, 1], # state 2\n", + " [4, 5, 1], # state 3\n", + " [6, 6, 1], # state 4\n", + " [6, 0, 1], # state 5\n", + " [0, 0, 1] # state 6\n", + "]\n", + "\n", + "days = ['d', 'n', 'o'] # for presentation\n", + "\n", + "#\n", + "# declare variables\n", + "#\n", + "x = {}\n", + "for i in range(num_nurses):\n", + " for j in range(num_days):\n", + " x[i, j] = solver.IntVar(shifts, 'x[%i,%i]' % (i, j))\n", + "\n", + "x_flat = [x[i, j] for i in range(num_nurses) for j in range(num_days)]\n", + "\n", + "# summary of the nurses\n", + "nurse_stat = [\n", + " solver.IntVar(0, num_days, 'nurse_stat[%i]' % i)\n", + " for i in range(num_nurses)\n", + "]\n", + "\n", + "# summary of the shifts per day\n", + "day_stat = {}\n", + "for i in range(num_days):\n", + " for j in shifts:\n", + " day_stat[i, j] = solver.IntVar(0, num_nurses, 'day_stat[%i,%i]' % (i, j))\n", + "\n", + "day_stat_flat = [day_stat[i, j] for i in range(num_days) for j in shifts]\n", + "\n", + "#\n", + "# constraints\n", + "#\n", + "for i in range(num_nurses):\n", + " reg_input = [x[i, j] for j in range(num_days)]\n", + " regular(reg_input, n_states, input_max, transition_fn, initial_state,\n", + " accepting_states)\n", + "\n", + "#\n", + "# Statistics and constraints for each nurse\n", + "#\n", + "for i in range(num_nurses):\n", + " # number of worked days (day or night shift)\n", + " b = [\n", + " solver.IsEqualCstVar(x[i, j], day_shift) + solver.IsEqualCstVar(\n", + " x[i, j], night_shift) for j in range(num_days)\n", + " ]\n", + " solver.Add(nurse_stat[i] == solver.Sum(b))\n", + "\n", + " # Each nurse must work between 7 and 10\n", + " # days during this period\n", + " solver.Add(nurse_stat[i] >= 7)\n", + " solver.Add(nurse_stat[i] <= 10)\n", + "\n", + "#\n", + "# Statistics and constraints for each day\n", + "#\n", + "for j in range(num_days):\n", + " for t in shifts:\n", + " b = [solver.IsEqualCstVar(x[i, j], t) for i in range(num_nurses)]\n", + " solver.Add(day_stat[j, t] == solver.Sum(b))\n", + "\n", + " #\n", + " # Some constraints for this day:\n", + " #\n", + " # Note: We have a strict requirements of\n", + " # the number of shifts.\n", + " # Using atleast constraints is much harder\n", + " # in this model.\n", + " #\n", + " if j % 7 == 5 or j % 7 == 6:\n", + " # special constraints for the weekends\n", + " solver.Add(day_stat[j, day_shift] == 2)\n", + " solver.Add(day_stat[j, night_shift] == 1)\n", + " solver.Add(day_stat[j, off_shift] == 4)\n", + " else:\n", + " # workdays:\n", + "\n", + " # - exactly 3 on day shift\n", + " solver.Add(day_stat[j, day_shift] == 3)\n", + " # - exactly 2 on night\n", + " solver.Add(day_stat[j, night_shift] == 2)\n", + " # - exactly 1 off duty\n", + " solver.Add(day_stat[j, off_shift] == 2)\n", + "\n", + "#\n", + "# solution and search\n", + "#\n", + "db = solver.Phase(day_stat_flat + x_flat + nurse_stat,\n", + " solver.CHOOSE_FIRST_UNBOUND, solver.ASSIGN_MIN_VALUE)\n", + "\n", + "solver.NewSearch(db)\n", + "\n", + "num_solutions = 0\n", + "while solver.NextSolution():\n", + " num_solutions += 1\n", + "\n", + " for i in range(num_nurses):\n", + " print('Nurse%i: ' % i, end=' ')\n", + " this_day_stat = defaultdict(int)\n", + " for j in range(num_days):\n", + " d = days[x[i, j].Value() - 1]\n", + " this_day_stat[d] += 1\n", + " print(d, end=' ')\n", + " print(\n", + " ' day_stat:', [(d, this_day_stat[d]) for d in this_day_stat], end=' ')\n", + " print('total:', nurse_stat[i].Value(), 'workdays')\n", + " print()\n", + "\n", + " print('Statistics per day:')\n", + " for j in range(num_days):\n", + " print('Day%2i: ' % j, end=' ')\n", + " for t in shifts:\n", + " print(day_stat[j, t].Value(), end=' ')\n", + " print()\n", + " print()\n", + "\n", + " # We just show 2 solutions\n", + " if num_solutions >= 2:\n", + " break\n", + "\n", + "solver.EndSearch()\n", + "print()\n", + "print('num_solutions:', num_solutions)\n", + "print('failures:', solver.Failures())\n", + "print('branches:', solver.Branches())\n", + "print('WallTime:', solver.WallTime(), 'ms')\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/contrib/nurses_cp.ipynb b/examples/notebook/contrib/nurses_cp.ipynb new file mode 100644 index 0000000000..8646fc9be4 --- /dev/null +++ b/examples/notebook/contrib/nurses_cp.ipynb @@ -0,0 +1,190 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from __future__ import print_function\n", + "import sys\n", + "from ortools.constraint_solver import pywrapcp\n", + "\n", + "\n", + "# Creates the solver.\n", + "solver = pywrapcp.Solver(\"schedule_shifts\")\n", + "\n", + "num_nurses = 4\n", + "num_shifts = 4 # Nurse assigned to shift 0 means not working that day.\n", + "num_days = 7\n", + "# [START]\n", + "# Create shift variables.\n", + "shifts = {}\n", + "\n", + "for j in range(num_nurses):\n", + " for i in range(num_days):\n", + " shifts[(j, i)] = solver.IntVar(0, num_shifts - 1,\n", + " \"shifts(%i,%i)\" % (j, i))\n", + "shifts_flat = [\n", + " shifts[(j, i)] for j in range(num_nurses) for i in range(num_days)\n", + "]\n", + "\n", + "# Create nurse variables.\n", + "nurses = {}\n", + "\n", + "for j in range(num_shifts):\n", + " for i in range(num_days):\n", + " nurses[(j, i)] = solver.IntVar(0, num_nurses - 1,\n", + " \"shift%d day%d\" % (j, i))\n", + "# Set relationships between shifts and nurses.\n", + "for day in range(num_days):\n", + " nurses_for_day = [nurses[(j, day)] for j in range(num_shifts)]\n", + "\n", + " for j in range(num_nurses):\n", + " s = shifts[(j, day)]\n", + " solver.Add(s.IndexOf(nurses_for_day) == j)\n", + "# Make assignments different on each day\n", + "for i in range(num_days):\n", + " solver.Add(solver.AllDifferent([shifts[(j, i)] for j in range(num_nurses)]))\n", + " solver.Add(solver.AllDifferent([nurses[(j, i)] for j in range(num_shifts)]))\n", + "# Each nurse works 5 or 6 days in a week.\n", + "for j in range(num_nurses):\n", + " solver.Add(solver.Sum([shifts[(j, i)] > 0 for i in range(num_days)]) >= 5)\n", + " solver.Add(solver.Sum([shifts[(j, i)] > 0 for i in range(num_days)]) <= 6)\n", + "# Create works_shift variables. works_shift[(i, j)] is True if nurse\n", + "# i works shift j at least once during the week.\n", + "works_shift = {}\n", + "\n", + "for i in range(num_nurses):\n", + " for j in range(num_shifts):\n", + " works_shift[(i, j)] = solver.BoolVar(\"nurse%d shift%d\" % (i, j))\n", + "\n", + "for i in range(num_nurses):\n", + " for j in range(num_shifts):\n", + " solver.Add(works_shift[(\n", + " i, j)] == solver.Max([shifts[(i, k)] == j for k in range(num_days)]))\n", + "\n", + "# For each shift (other than 0), at most 2 nurses are assigned to that shift\n", + "# during the week.\n", + "for j in range(1, num_shifts):\n", + " solver.Add(\n", + " solver.Sum([works_shift[(i, j)] for i in range(num_nurses)]) <= 2)\n", + "# If s nurses works shifts 2 or 3 on, he must also work that shift the previous\n", + "# day or the following day.\n", + "solver.Add(\n", + " solver.Max(nurses[(2,\n", + " 0)] == nurses[(2,\n", + " 1)], nurses[(2,\n", + " 1)] == nurses[(2,\n", + " 2)]) == 1)\n", + "solver.Add(\n", + " solver.Max(nurses[(2,\n", + " 1)] == nurses[(2,\n", + " 2)], nurses[(2,\n", + " 2)] == nurses[(2,\n", + " 3)]) == 1)\n", + "solver.Add(\n", + " solver.Max(nurses[(2,\n", + " 2)] == nurses[(2,\n", + " 3)], nurses[(2,\n", + " 3)] == nurses[(2,\n", + " 4)]) == 1)\n", + "solver.Add(\n", + " solver.Max(nurses[(2,\n", + " 3)] == nurses[(2,\n", + " 4)], nurses[(2,\n", + " 4)] == nurses[(2,\n", + " 5)]) == 1)\n", + "solver.Add(\n", + " solver.Max(nurses[(2,\n", + " 4)] == nurses[(2,\n", + " 5)], nurses[(2,\n", + " 5)] == nurses[(2,\n", + " 6)]) == 1)\n", + "solver.Add(\n", + " solver.Max(nurses[(2,\n", + " 5)] == nurses[(2,\n", + " 6)], nurses[(2,\n", + " 6)] == nurses[(2,\n", + " 0)]) == 1)\n", + "solver.Add(\n", + " solver.Max(nurses[(2,\n", + " 6)] == nurses[(2,\n", + " 0)], nurses[(2,\n", + " 0)] == nurses[(2,\n", + " 1)]) == 1)\n", + "\n", + "solver.Add(\n", + " solver.Max(nurses[(3,\n", + " 0)] == nurses[(3,\n", + " 1)], nurses[(3,\n", + " 1)] == nurses[(3,\n", + " 2)]) == 1)\n", + "solver.Add(\n", + " solver.Max(nurses[(3,\n", + " 1)] == nurses[(3,\n", + " 2)], nurses[(3,\n", + " 2)] == nurses[(3,\n", + " 3)]) == 1)\n", + "solver.Add(\n", + " solver.Max(nurses[(3,\n", + " 2)] == nurses[(3,\n", + " 3)], nurses[(3,\n", + " 3)] == nurses[(3,\n", + " 4)]) == 1)\n", + "solver.Add(\n", + " solver.Max(nurses[(3,\n", + " 3)] == nurses[(3,\n", + " 4)], nurses[(3,\n", + " 4)] == nurses[(3,\n", + " 5)]) == 1)\n", + "solver.Add(\n", + " solver.Max(nurses[(3,\n", + " 4)] == nurses[(3,\n", + " 5)], nurses[(3,\n", + " 5)] == nurses[(3,\n", + " 6)]) == 1)\n", + "solver.Add(\n", + " solver.Max(nurses[(3,\n", + " 5)] == nurses[(3,\n", + " 6)], nurses[(3,\n", + " 6)] == nurses[(3,\n", + " 0)]) == 1)\n", + "solver.Add(\n", + " solver.Max(nurses[(3,\n", + " 6)] == nurses[(3,\n", + " 0)], nurses[(3,\n", + " 0)] == nurses[(3,\n", + " 1)]) == 1)\n", + "# Create the decision builder.\n", + "db = solver.Phase(shifts_flat, solver.CHOOSE_FIRST_UNBOUND,\n", + " solver.ASSIGN_MIN_VALUE)\n", + "# Create the solution collector.\n", + "solution = solver.Assignment()\n", + "solution.Add(shifts_flat)\n", + "collector = solver.AllSolutionCollector(solution)\n", + "\n", + "solver.Solve(db, [collector])\n", + "print(\"Solutions found:\", collector.SolutionCount())\n", + "print(\"Time:\", solver.WallTime(), \"ms\")\n", + "print()\n", + "# Display a few solutions picked at random.\n", + "a_few_solutions = [859, 2034, 5091, 7003]\n", + "\n", + "for sol in a_few_solutions:\n", + " print(\"Solution number\", sol, \"\\n\")\n", + "\n", + " for i in range(num_days):\n", + " print(\"Day\", i)\n", + " for j in range(num_nurses):\n", + " print(\"Nurse\", j, \"assigned to task\",\n", + " collector.Value(sol, shifts[(j, i)]))\n", + " print()\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/contrib/olympic.ipynb b/examples/notebook/contrib/olympic.ipynb new file mode 100644 index 0000000000..d9cb5123b8 --- /dev/null +++ b/examples/notebook/contrib/olympic.ipynb @@ -0,0 +1,127 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010 Hakan Kjellerstrand hakank@gmail.com\n", + "#\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"\n", + "\n", + " Olympic puzzle in Google CP Solver.\n", + "\n", + " Benchmark for Prolog (BProlog)\n", + " '''\n", + " File : olympic.pl\n", + " Author : Neng-Fa ZHOU\n", + " Date : 1993\n", + "\n", + " Purpose: solve a puzzle taken from Olympic Arithmetic Contest\n", + "\n", + " Given ten variables with the following configuration:\n", + "\n", + " X7 X8 X9 X10\n", + "\n", + " X4 X5 X6\n", + "\n", + " X2 X3\n", + "\n", + " X1\n", + "\n", + " We already know that X1 is equal to 3 and want to assign each variable\n", + " with a different integer from {1,2,...,10} such that for any three\n", + " variables\n", + " Xi Xj\n", + "\n", + " Xk\n", + " the following constraint is satisfied:\n", + "\n", + " |Xi-Xj| = Xk\n", + " '''\n", + "\n", + " Compare with the following models:\n", + " * MiniZinc: http://www.hakank.org/minizinc/olympic.mzn\n", + " * SICStus Prolog: http://www.hakank.org/sicstus/olympic.pl\n", + " * ECLiPSe: http://hakank.org/eclipse/olympic.ecl\n", + " * Gecode: http://hakank.org/gecode/olympic.cpp\n", + "\n", + "\n", + " This model was created by Hakan Kjellerstrand (hakank@gmail.com)\n", + " Also see my other Google CP Solver models:\n", + " http://www.hakank.org/google_or_tools/\n", + "\"\"\"\n", + "from __future__ import print_function\n", + "import sys\n", + "from ortools.constraint_solver import pywrapcp\n", + "\n", + "\n", + "def minus(solver, x, y, z):\n", + " solver.Add(z == abs(x - y))\n", + "\n", + "\n", + "\n", + "# Create the solver.\n", + "solver = pywrapcp.Solver('Olympic')\n", + "\n", + "#\n", + "# data\n", + "#\n", + "n = 10\n", + "\n", + "#\n", + "# declare variables\n", + "#\n", + "Vars = [solver.IntVar(1, n, 'Vars[%i]' % i) for i in range(n)]\n", + "X1, X2, X3, X4, X5, X6, X7, X8, X9, X10 = Vars\n", + "\n", + "#\n", + "# constraints\n", + "#\n", + "solver.Add(solver.AllDifferent(Vars))\n", + "\n", + "solver.Add(X1 == 3)\n", + "minus(solver, X2, X3, X1)\n", + "minus(solver, X4, X5, X2)\n", + "minus(solver, X5, X6, X3)\n", + "minus(solver, X7, X8, X4)\n", + "minus(solver, X8, X9, X5)\n", + "minus(solver, X9, X10, X6)\n", + "\n", + "#\n", + "# solution and search\n", + "#\n", + "db = solver.Phase(Vars, solver.INT_VAR_SIMPLE, solver.INT_VALUE_DEFAULT)\n", + "\n", + "solver.NewSearch(db)\n", + "\n", + "num_solutions = 0\n", + "while solver.NextSolution():\n", + " num_solutions += 1\n", + " print('Vars:', [Vars[i].Value() for i in range(n)])\n", + "\n", + "print()\n", + "print('num_solutions:', num_solutions)\n", + "print('failures:', solver.Failures())\n", + "print('branches:', solver.Branches())\n", + "print('WallTime:', solver.WallTime(), 'ms')\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/contrib/organize_day.ipynb b/examples/notebook/contrib/organize_day.ipynb new file mode 100644 index 0000000000..6f97aafa49 --- /dev/null +++ b/examples/notebook/contrib/organize_day.ipynb @@ -0,0 +1,126 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010 Hakan Kjellerstrand hakank@gmail.com\n", + "#\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"\n", + "\n", + " Organizing a day in Google CP Solver.\n", + "\n", + " Simple scheduling problem.\n", + "\n", + " Problem formulation from ECLiPSe:\n", + " Slides on (Finite Domain) Constraint Logic Programming, page 38f\n", + " http://eclipse-clp.org/reports/eclipse.ppt\n", + "\n", + "\n", + " Compare with the following models:\n", + " * MiniZinc: http://www.hakank.org/minizinc/organize_day.mzn\n", + " * Comet: http://www.hakank.org/comet/organize_day.co\n", + " * Gecode: http://hakank.org/gecode/organize_day.cpp\n", + "\n", + " This model was created by Hakan Kjellerstrand (hakank@gmail.com)\n", + " Also see my other Google CP Solver models:\n", + " http://www.hakank.org/google_or_tools/\n", + "\"\"\"\n", + "from __future__ import print_function\n", + "import sys\n", + "from ortools.constraint_solver import pywrapcp\n", + "\n", + "#\n", + "# No overlapping of tasks s1 and s2\n", + "#\n", + "\n", + "\n", + "def no_overlap(solver, s1, d1, s2, d2):\n", + " b1 = solver.IsLessOrEqualVar(s1 + d1, s2) # s1 + d1 <= s2\n", + " b2 = solver.IsLessOrEqualVar(s2 + d2, s1) # s2 + d2 <= s1\n", + " solver.Add(b1 + b2 >= 1)\n", + "\n", + "\n", + "\n", + "# Create the solver.\n", + "solver = pywrapcp.Solver('Organizing a day')\n", + "\n", + "#\n", + "# data\n", + "#\n", + "n = 4\n", + "\n", + "tasks = list(range(n))\n", + "work, mail, shop, bank = tasks\n", + "durations = [4, 1, 2, 1]\n", + "\n", + "# task [i,0] must be finished before task [i,1]\n", + "before_tasks = [[bank, shop], [mail, work]]\n", + "\n", + "# the valid times of the day\n", + "begin = 9\n", + "end = 17\n", + "\n", + "#\n", + "# declare variables\n", + "#\n", + "begins = [solver.IntVar(begin, end, 'begins[%i]% % i') for i in tasks]\n", + "ends = [solver.IntVar(begin, end, 'ends[%i]% % i') for i in tasks]\n", + "\n", + "#\n", + "# constraints\n", + "#\n", + "for i in tasks:\n", + " solver.Add(ends[i] == begins[i] + durations[i])\n", + "\n", + "for i in tasks:\n", + " for j in tasks:\n", + " if i < j:\n", + " no_overlap(solver, begins[i], durations[i], begins[j], durations[j])\n", + "\n", + "# specific constraints\n", + "for (before, after) in before_tasks:\n", + " solver.Add(ends[before] <= begins[after])\n", + "\n", + "solver.Add(begins[work] >= 11)\n", + "\n", + "#\n", + "# solution and search\n", + "#\n", + "db = solver.Phase(begins + ends, solver.INT_VAR_DEFAULT,\n", + " solver.INT_VALUE_DEFAULT)\n", + "\n", + "solver.NewSearch(db)\n", + "\n", + "num_solutions = 0\n", + "while solver.NextSolution():\n", + " num_solutions += 1\n", + " print('begins:', [begins[i].Value() for i in tasks])\n", + " print('ends:', [ends[i].Value() for i in tasks])\n", + " print()\n", + "\n", + "print('num_solutions:', num_solutions)\n", + "print('failures:', solver.Failures())\n", + "print('branches:', solver.Branches())\n", + "print('WallTime:', solver.WallTime(), 'ms')\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/contrib/p_median.ipynb b/examples/notebook/contrib/p_median.ipynb new file mode 100644 index 0000000000..b8e9cd7bf5 --- /dev/null +++ b/examples/notebook/contrib/p_median.ipynb @@ -0,0 +1,133 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010 Hakan Kjellerstrand hakank@gmail.com\n", + "#\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"\n", + "\n", + " P-median problem in Google CP Solver.\n", + "\n", + " Model and data from the OPL Manual, which describes the problem:\n", + " '''\n", + " The P-Median problem is a well known problem in Operations Research.\n", + " The problem can be stated very simply, like this: given a set of customers\n", + " with known amounts of demand, a set of candidate locations for warehouses,\n", + " and the distance between each pair of customer-warehouse, choose P\n", + " warehouses to open that minimize the demand-weighted distance of serving\n", + " all customers from those P warehouses.\n", + " '''\n", + "\n", + " Compare with the following models:\n", + " * MiniZinc: http://hakank.org/minizinc/p_median.mzn\n", + " * Comet: http://hakank.org/comet/p_median.co\n", + "\n", + " This model was created by Hakan Kjellerstrand (hakank@gmail.com)\n", + " Also see my other Google CP Solver models:\n", + " http://www.hakank.org/google_or_tools/\n", + "\"\"\"\n", + "from __future__ import print_function\n", + "import sys\n", + "from ortools.constraint_solver import pywrapcp\n", + "\n", + "\n", + "\n", + "# Create the solver.\n", + "solver = pywrapcp.Solver('P-median problem')\n", + "\n", + "#\n", + "# data\n", + "#\n", + "p = 2\n", + "\n", + "num_customers = 4\n", + "customers = list(range(num_customers))\n", + "Albert, Bob, Chris, Daniel = customers\n", + "num_warehouses = 3\n", + "warehouses = list(range(num_warehouses))\n", + "Santa_Clara, San_Jose, Berkeley = warehouses\n", + "\n", + "demand = [100, 80, 80, 70]\n", + "distance = [[2, 10, 50], [2, 10, 52], [50, 60, 3], [40, 60, 1]]\n", + "\n", + "#\n", + "# declare variables\n", + "#\n", + "open = [solver.IntVar(warehouses, 'open[%i]% % i') for w in warehouses]\n", + "ship = {}\n", + "for c in customers:\n", + " for w in warehouses:\n", + " ship[c, w] = solver.IntVar(0, 1, 'ship[%i,%i]' % (c, w))\n", + "ship_flat = [ship[c, w] for c in customers for w in warehouses]\n", + "\n", + "z = solver.IntVar(0, 1000, 'z')\n", + "\n", + "#\n", + "# constraints\n", + "#\n", + "z_sum = solver.Sum([\n", + " demand[c] * distance[c][w] * ship[c, w]\n", + " for c in customers\n", + " for w in warehouses\n", + "])\n", + "solver.Add(z == z_sum)\n", + "\n", + "for c in customers:\n", + " s = solver.Sum([ship[c, w] for w in warehouses])\n", + " solver.Add(s == 1)\n", + "\n", + "solver.Add(solver.Sum(open) == p)\n", + "\n", + "for c in customers:\n", + " for w in warehouses:\n", + " solver.Add(ship[c, w] <= open[w])\n", + "\n", + "# objective\n", + "objective = solver.Minimize(z, 1)\n", + "\n", + "#\n", + "# solution and search\n", + "#\n", + "db = solver.Phase(open + ship_flat, solver.INT_VAR_DEFAULT,\n", + " solver.INT_VALUE_DEFAULT)\n", + "\n", + "solver.NewSearch(db, [objective])\n", + "\n", + "num_solutions = 0\n", + "while solver.NextSolution():\n", + " num_solutions += 1\n", + " print('z:', z.Value())\n", + " print('open:', [open[w].Value() for w in warehouses])\n", + " for c in customers:\n", + " for w in warehouses:\n", + " print(ship[c, w].Value(), end=' ')\n", + " print()\n", + " print()\n", + "\n", + "print('num_solutions:', num_solutions)\n", + "print('failures:', solver.Failures())\n", + "print('branches:', solver.Branches())\n", + "print('WallTime:', solver.WallTime(), 'ms')\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/contrib/pandigital_numbers.ipynb b/examples/notebook/contrib/pandigital_numbers.ipynb new file mode 100644 index 0000000000..05395a25ce --- /dev/null +++ b/examples/notebook/contrib/pandigital_numbers.ipynb @@ -0,0 +1,168 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010 Hakan Kjellerstrand hakank@gmail.com\n", + "#\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"\n", + "\n", + " Pandigital numbers in Google CP Solver.\n", + "\n", + " From Albert H. Beiler 'Recreations in the Theory of Numbers',\n", + " quoted from http://www.worldofnumbers.com/ninedig1.htm\n", + " '''\n", + " Chapter VIII : Digits - and the magic of 9\n", + "\n", + " The following curious table shows how to arrange the 9 digits so that\n", + " the product of 2 groups is equal to a number represented by the\n", + " remaining digits.\n", + "\n", + " 12 x 483 = 5796\n", + " 42 x 138 = 5796\n", + " 18 x 297 = 5346\n", + " 27 x 198 = 5346\n", + " 39 x 186 = 7254\n", + " 48 x 159 = 7632\n", + " 28 x 157 = 4396\n", + " 4 x 1738 = 6952\n", + " 4 x 1963 = 7852\n", + " '''\n", + "\n", + " See also MathWorld http://mathworld.wolfram.com/PandigitalNumber.html\n", + " '''\n", + " A number is said to be pandigital if it contains each of the digits\n", + " from 0 to 9 (and whose leading digit must be nonzero). However,\n", + " 'zeroless' pandigital quantities contain the digits 1 through 9.\n", + " Sometimes exclusivity is also required so that each digit is\n", + " restricted to appear exactly once.\n", + " '''\n", + "\n", + " * Wikipedia http://en.wikipedia.org/wiki/Pandigital_number\n", + "\n", + "\n", + " Compare with the the following models:\n", + " * MiniZinc: http://www.hakank.org/minizinc/pandigital_numbers.mzn\n", + " * Comet : http://www.hakank.org/comet/pandigital_numbers.co\n", + " * ECLiPSe : http://www.hakank.org/eclipse/pandigital_numbers.ecl\n", + " * Gecode/R: http://www.hakank.org/gecoder/pandigital_numbers.rb\n", + " * ECLiPSe : http://hakank.org/eclipse/pandigital_numbers.ecl\n", + " * SICStus : http://hakank.org/sicstus/pandigital_numbers.pl\n", + "\n", + " This model was created by Hakan Kjellerstrand (hakank@gmail.com)\n", + " Also see my other Google CP Solver models:\n", + " http://www.hakank.org/google_or_tools/\n", + "\n", + "\"\"\"\n", + "from __future__ import print_function\n", + "import sys\n", + "\n", + "from ortools.constraint_solver import pywrapcp\n", + "\n", + "#\n", + "# converts a number (s) <-> an array of integers (t) in the specific base.\n", + "#\n", + "\n", + "\n", + "def toNum(solver, t, s, base):\n", + " tlen = len(t)\n", + " solver.Add(\n", + " s == solver.Sum([(base**(tlen - i - 1)) * t[i] for i in range(tlen)]))\n", + "\n", + "\n", + "\n", + "# Create the solver.\n", + "solver = pywrapcp.Solver(\"Pandigital numbers\")\n", + "\n", + "#\n", + "# data\n", + "#\n", + "max_d = base - 1\n", + "x_len = max_d + 1 - start\n", + "max_num = base**4 - 1\n", + "\n", + "#\n", + "# declare variables\n", + "#\n", + "num1 = solver.IntVar(0, max_num, \"num1\")\n", + "num2 = solver.IntVar(0, max_num, \"num2\")\n", + "res = solver.IntVar(0, max_num, \"res\")\n", + "\n", + "x = [solver.IntVar(start, max_d, \"x[%i]\" % i) for i in range(x_len)]\n", + "\n", + "#\n", + "# constraints\n", + "#\n", + "solver.Add(solver.AllDifferent(x))\n", + "\n", + "toNum(solver, [x[i] for i in range(len1)], num1, base)\n", + "toNum(solver, [x[i] for i in range(len1, len1 + len2)], num2, base)\n", + "toNum(solver, [x[i] for i in range(len1 + len2, x_len)], res, base)\n", + "\n", + "solver.Add(num1 * num2 == res)\n", + "\n", + "# no number must start with 0\n", + "solver.Add(x[0] > 0)\n", + "solver.Add(x[len1] > 0)\n", + "solver.Add(x[len1 + len2] > 0)\n", + "\n", + "# symmetry breaking\n", + "solver.Add(num1 < num2)\n", + "\n", + "#\n", + "# solution and search\n", + "#\n", + "solution = solver.Assignment()\n", + "solution.Add(x)\n", + "solution.Add(num1)\n", + "solution.Add(num2)\n", + "solution.Add(res)\n", + "\n", + "db = solver.Phase(x, solver.INT_VAR_SIMPLE, solver.INT_VALUE_DEFAULT)\n", + "\n", + "solver.NewSearch(db)\n", + "num_solutions = 0\n", + "solutions = []\n", + "while solver.NextSolution():\n", + " print_solution([x[i].Value() for i in range(x_len)], len1, len2, x_len)\n", + " num_solutions += 1\n", + "\n", + "solver.EndSearch()\n", + "\n", + "if 0 and num_solutions > 0:\n", + " print()\n", + " print(\"num_solutions:\", num_solutions)\n", + " print(\"failures:\", solver.Failures())\n", + " print(\"branches:\", solver.Branches())\n", + " print(\"WallTime:\", solver.WallTime())\n", + " print()\n", + "\n", + "def print_solution(x, len1, len2, x_len):\n", + " print(\"\".join([str(x[i]) for i in range(len1)]), \"*\", end=\" \")\n", + " print(\"\".join([str(x[i]) for i in range(len1, len1 + len2)]), \"=\", end=\" \")\n", + " print(\"\".join([str(x[i]) for i in range(len1 + len2, x_len)]))\n", + "\n", + "\n", + "base = 10\n", + "start = 1\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/contrib/photo_problem.ipynb b/examples/notebook/contrib/photo_problem.ipynb new file mode 100644 index 0000000000..a78fb7c198 --- /dev/null +++ b/examples/notebook/contrib/photo_problem.ipynb @@ -0,0 +1,164 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010 Hakan Kjellerstrand hakank@gmail.com\n", + "#\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"\n", + "\n", + " Photo problem in Google CP Solver.\n", + "\n", + " Problem statement from Mozart/Oz tutorial:\n", + " http://www.mozart-oz.org/home/doc/fdt/node37.html#section.reified.photo\n", + " '''\n", + " Betty, Chris, Donald, Fred, Gary, Mary, and Paul want to align in one\n", + " row for taking a photo. Some of them have preferences next to whom\n", + " they want to stand:\n", + "\n", + " 1. Betty wants to stand next to Gary and Mary.\n", + " 2. Chris wants to stand next to Betty and Gary.\n", + " 3. Fred wants to stand next to Mary and Donald.\n", + " 4. Paul wants to stand next to Fred and Donald.\n", + "\n", + " Obviously, it is impossible to satisfy all preferences. Can you find\n", + " an alignment that maximizes the number of satisfied preferences?\n", + " '''\n", + "\n", + " Oz solution:\n", + " 6 # alignment(betty:5 chris:6 donald:1 fred:3 gary:7 mary:4 paul:2)\n", + " [5, 6, 1, 3, 7, 4, 2]\n", + "\n", + "\n", + " Compare with the following models:\n", + " * MiniZinc: http://www.hakank.org/minizinc/photo_hkj.mzn\n", + " * Comet: http://hakank.org/comet/photo_problem.co\n", + " * SICStus: http://hakank.org/sicstus/photo_problem.pl\n", + "\n", + " This model was created by Hakan Kjellerstrand (hakank@gmail.com)\n", + " Also see my other Google CP Solver models:\n", + " http://www.hakank.org/google_or_tools/\n", + "\"\"\"\n", + "from __future__ import print_function\n", + "import sys\n", + "\n", + "from ortools.constraint_solver import pywrapcp\n", + "\n", + "\n", + "\n", + "# Create the solver.\n", + "solver = pywrapcp.Solver(\"Photo problem\")\n", + "\n", + "#\n", + "# data\n", + "#\n", + "persons = [\"Betty\", \"Chris\", \"Donald\", \"Fred\", \"Gary\", \"Mary\", \"Paul\"]\n", + "n = len(persons)\n", + "preferences = [\n", + " # 0 1 2 3 4 5 6\n", + " # B C D F G M P\n", + " [0, 0, 0, 0, 1, 1, 0], # Betty 0\n", + " [1, 0, 0, 0, 1, 0, 0], # Chris 1\n", + " [0, 0, 0, 0, 0, 0, 0], # Donald 2\n", + " [0, 0, 1, 0, 0, 1, 0], # Fred 3\n", + " [0, 0, 0, 0, 0, 0, 0], # Gary 4\n", + " [0, 0, 0, 0, 0, 0, 0], # Mary 5\n", + " [0, 0, 1, 1, 0, 0, 0] # Paul 6\n", + "]\n", + "\n", + "print(\"\"\"Preferences:\n", + " 1. Betty wants to stand next to Gary and Mary.\n", + " 2. Chris wants to stand next to Betty and Gary.\n", + " 3. Fred wants to stand next to Mary and Donald.\n", + " 4. Paul wants to stand next to Fred and Donald.\n", + " \"\"\")\n", + "\n", + "#\n", + "# declare variables\n", + "#\n", + "positions = [solver.IntVar(0, n - 1, \"positions[%i]\" % i) for i in range(n)]\n", + "\n", + "# successful preferences\n", + "z = solver.IntVar(0, n * n, \"z\")\n", + "\n", + "#\n", + "# constraints\n", + "#\n", + "solver.Add(solver.AllDifferent(positions))\n", + "\n", + "# calculate all the successful preferences\n", + "b = [\n", + " solver.IsEqualCstVar(abs(positions[i] - positions[j]), 1)\n", + " for i in range(n)\n", + " for j in range(n)\n", + " if preferences[i][j] == 1\n", + "]\n", + "solver.Add(z == solver.Sum(b))\n", + "\n", + "#\n", + "# Symmetry breaking (from the Oz page):\n", + "# Fred is somewhere left of Betty\n", + "solver.Add(positions[3] < positions[0])\n", + "\n", + "# objective\n", + "objective = solver.Maximize(z, 1)\n", + "if show_all_max != 0:\n", + " print(\"Showing all maximum solutions (z == 6).\\n\")\n", + " solver.Add(z == 6)\n", + "\n", + "#\n", + "# search and result\n", + "#\n", + "db = solver.Phase(positions, solver.CHOOSE_FIRST_UNBOUND,\n", + " solver.ASSIGN_MAX_VALUE)\n", + "\n", + "if show_all_max == 0:\n", + " solver.NewSearch(db, [objective])\n", + "else:\n", + " solver.NewSearch(db)\n", + "\n", + "num_solutions = 0\n", + "while solver.NextSolution():\n", + " print(\"z:\", z.Value())\n", + " p = [positions[i].Value() for i in range(n)]\n", + "\n", + " print(\" \".join(\n", + " [persons[j] for i in range(n) for j in range(n) if p[j] == i]))\n", + " print(\"Successful preferences:\")\n", + " for i in range(n):\n", + " for j in range(n):\n", + " if preferences[i][j] == 1 and abs(p[i] - p[j]) == 1:\n", + " print(\"\\t\", persons[i], persons[j])\n", + " print()\n", + " num_solutions += 1\n", + "\n", + "solver.EndSearch()\n", + "\n", + "print()\n", + "print(\"num_solutions:\", num_solutions)\n", + "print(\"failures:\", solver.Failures())\n", + "print(\"branches:\", solver.Branches())\n", + "print(\"WallTime:\", solver.WallTime())\n", + "\n", + "show_all_max = 0 # show all maximal solutions\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/contrib/place_number_puzzle.ipynb b/examples/notebook/contrib/place_number_puzzle.ipynb new file mode 100644 index 0000000000..23a68b62cd --- /dev/null +++ b/examples/notebook/contrib/place_number_puzzle.ipynb @@ -0,0 +1,111 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010 Hakan Kjellerstrand hakank@gmail.com\n", + "#\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"\n", + "\n", + " Place number puzzle Google CP Solver.\n", + "\n", + " http://ai.uwaterloo.ca/~vanbeek/Courses/Slides/introduction.pdf\n", + " '''\n", + " Place numbers 1 through 8 on nodes\n", + " - each number appears exactly once\n", + " - no connected nodes have consecutive numbers\n", + " 2 - 5\n", + " / | X | \\\n", + " 1 - 3 - 6 - 8\n", + " \\ | X | /\n", + " 4 - 7\n", + " \"\"\n", + "\n", + " Compare with the following models:\n", + " * MiniZinc: http://www.hakank.org/minizinc/place_number.mzn\n", + " * Comet: http://www.hakank.org/comet/place_number_puzzle.co\n", + " * ECLiPSe: http://www.hakank.org/eclipse/place_number_puzzle.ecl\n", + " * SICStus Prolog: http://www.hakank.org/sicstus/place_number_puzzle.pl\n", + " * Gecode: http://www.hakank.org/gecode/place_number_puzzle.cpp\n", + "\n", + " This model was created by Hakan Kjellerstrand (hakank@gmail.com)\n", + " Also see my other Google CP Solver models:\n", + " http://www.hakank.org/google_or_tools/\n", + "\n", + "\"\"\"\n", + "from __future__ import print_function\n", + "import sys\n", + "from ortools.constraint_solver import pywrapcp\n", + "\n", + "\n", + "\n", + "# Create the solver.\n", + "solver = pywrapcp.Solver(\"Place number\")\n", + "\n", + "# data\n", + "m = 32\n", + "n = 8\n", + "# Note: this is 1-based for compatibility (and lazyness)\n", + "graph = [[1, 2], [1, 3], [1, 4], [2, 1], [2, 3], [2, 5], [2, 6], [3, 2],\n", + " [3, 4], [3, 6], [3, 7], [4, 1], [4, 3], [4, 6], [4, 7], [5, 2],\n", + " [5, 3], [5, 6], [5, 8], [6, 2], [6, 3], [6, 4], [6, 5], [6, 7],\n", + " [6, 8], [7, 3], [7, 4], [7, 6], [7, 8], [8, 5], [8, 6], [8, 7]]\n", + "\n", + "# declare variables\n", + "x = [solver.IntVar(1, n, \"x%i\" % i) for i in range(n)]\n", + "\n", + "#\n", + "# constraints\n", + "#\n", + "solver.Add(solver.AllDifferent(x))\n", + "for i in range(m):\n", + " # Note: make 0-based\n", + " solver.Add(abs(x[graph[i][0] - 1] - x[graph[i][1] - 1]) > 1)\n", + "\n", + "# symmetry breaking\n", + "solver.Add(x[0] < x[n - 1])\n", + "\n", + "#\n", + "# solution and search\n", + "#\n", + "solution = solver.Assignment()\n", + "solution.Add(x)\n", + "\n", + "collector = solver.AllSolutionCollector(solution)\n", + "\n", + "solver.Solve(\n", + " solver.Phase(x, solver.CHOOSE_FIRST_UNBOUND, solver.ASSIGN_MIN_VALUE),\n", + " [collector])\n", + "\n", + "num_solutions = collector.SolutionCount()\n", + "for s in range(num_solutions):\n", + " print(\"x:\", [collector.Value(s, x[i]) for i in range(len(x))])\n", + "\n", + "print()\n", + "print(\"num_solutions:\", num_solutions)\n", + "print(\"failures:\", solver.Failures())\n", + "print(\"branches:\", solver.Branches())\n", + "print(\"WallTime:\", solver.WallTime())\n", + "print()\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/contrib/post_office_problem2.ipynb b/examples/notebook/contrib/post_office_problem2.ipynb new file mode 100644 index 0000000000..c57fdb5d23 --- /dev/null +++ b/examples/notebook/contrib/post_office_problem2.ipynb @@ -0,0 +1,141 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010 Hakan Kjellerstrand hakank@gmail.com\n", + "#\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"\n", + "\n", + " Post office problem in Google CP Solver.\n", + "\n", + " Problem statement:\n", + " http://www-128.ibm.com/developerworks/linux/library/l-glpk2/\n", + "\n", + " From Winston 'Operations Research: Applications and Algorithms':\n", + " '''\n", + " A post office requires a different number of full-time employees working\n", + " on different days of the week [summarized below]. Union rules state that\n", + " each full-time employee must work for 5 consecutive days and then receive\n", + " two days off. For example, an employee who works on Monday to Friday\n", + " must be off on Saturday and Sunday. The post office wants to meet its\n", + " daily requirements using only full-time employees. Minimize the number\n", + " of employees that must be hired.\n", + "\n", + " To summarize the important information about the problem:\n", + "\n", + " * Every full-time worker works for 5 consecutive days and takes 2 days off\n", + " * Day 1 (Monday): 17 workers needed\n", + " * Day 2 : 13 workers needed\n", + " * Day 3 : 15 workers needed\n", + " * Day 4 : 19 workers needed\n", + " * Day 5 : 14 workers needed\n", + " * Day 6 : 16 workers needed\n", + " * Day 7 (Sunday) : 11 workers needed\n", + "\n", + " The post office needs to minimize the number of employees it needs\n", + " to hire to meet its demand.\n", + " '''\n", + "\n", + " * MiniZinc: http://www.hakank.org/minizinc/post_office_problem2.mzn\n", + " * SICStus: http://www.hakank.org/sicstus/post_office_problem2.pl\n", + " * ECLiPSe: http://www.hakank.org/eclipse/post_office_problem2.ecl\n", + " * Gecode: http://www.hakank.org/gecode/post_office_problem2.cpp\n", + "\n", + "\n", + " This model was created by Hakan Kjellerstrand (hakank@gmail.com)\n", + " Also see my other Google CP Solver models:\n", + " http://www.hakank.org/google_or_tools/\n", + "\"\"\"\n", + "from __future__ import print_function\n", + "from ortools.constraint_solver import pywrapcp\n", + "\n", + "\n", + "\n", + "# Create the solver.\n", + "solver = pywrapcp.Solver('Post office problem')\n", + "\n", + "#\n", + "# data\n", + "#\n", + "\n", + "# days 0..6, monday 0\n", + "n = 7\n", + "days = list(range(n))\n", + "need = [17, 13, 15, 19, 14, 16, 11]\n", + "\n", + "# Total cost for the 5 day schedule.\n", + "# Base cost per day is 100.\n", + "# Working saturday is 100 extra\n", + "# Working sunday is 200 extra.\n", + "cost = [500, 600, 800, 800, 800, 800, 700]\n", + "\n", + "#\n", + "# variables\n", + "#\n", + "\n", + "# No. of workers starting at day i\n", + "x = [solver.IntVar(0, 100, 'x[%i]' % i) for i in days]\n", + "\n", + "total_cost = solver.IntVar(0, 20000, 'total_cost')\n", + "num_workers = solver.IntVar(0, 100, 'num_workers')\n", + "\n", + "#\n", + "# constraints\n", + "#\n", + "solver.Add(total_cost == solver.ScalProd(x, cost))\n", + "solver.Add(num_workers == solver.Sum(x))\n", + "\n", + "for i in days:\n", + " s = solver.Sum(\n", + " [x[j] for j in days if j != (i + 5) % n and j != (i + 6) % n])\n", + " solver.Add(s >= need[i])\n", + "\n", + "# objective\n", + "objective = solver.Minimize(total_cost, 1)\n", + "\n", + "#\n", + "# search and result\n", + "#\n", + "db = solver.Phase(x, solver.CHOOSE_MIN_SIZE_LOWEST_MIN,\n", + " solver.ASSIGN_MIN_VALUE)\n", + "\n", + "solver.NewSearch(db, [objective])\n", + "\n", + "num_solutions = 0\n", + "\n", + "while solver.NextSolution():\n", + " num_solutions += 1\n", + " print('num_workers:', num_workers.Value())\n", + " print('total_cost:', total_cost.Value())\n", + " print('x:', [x[i].Value() for i in days])\n", + "\n", + "solver.EndSearch()\n", + "\n", + "print()\n", + "print('num_solutions:', num_solutions)\n", + "print('failures:', solver.Failures())\n", + "print('branches:', solver.Branches())\n", + "print('WallTime:', solver.WallTime())\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/contrib/production.ipynb b/examples/notebook/contrib/production.ipynb new file mode 100644 index 0000000000..2a7366ae63 --- /dev/null +++ b/examples/notebook/contrib/production.ipynb @@ -0,0 +1,122 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2011 Hakan Kjellerstrand hakank@gmail.com\n", + "#\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"\n", + "\n", + " Production planning problem in Google or-tools.\n", + "\n", + " From the OPL model production.mod.\n", + "\n", + " This model was created by Hakan Kjellerstrand (hakank@gmail.com)\n", + " Also see my other Google CP Solver models:\n", + " http://www.hakank.org/google_or_tools/\n", + "\"\"\"\n", + "from __future__ import print_function\n", + "import sys\n", + "from ortools.linear_solver import pywraplp\n", + "\n", + "\n", + "\n", + "# Create the solver.\n", + "\n", + "# using GLPK\n", + "if sol == 'GLPK':\n", + " solver = pywraplp.Solver('CoinsGridGLPK',\n", + " pywraplp.Solver.GLPK_LINEAR_PROGRAMMING)\n", + "else:\n", + " # Using CLP\n", + " solver = pywraplp.Solver('CoinsGridCLP',\n", + " pywraplp.Solver.CLP_LINEAR_PROGRAMMING)\n", + "\n", + "#\n", + "# data\n", + "#\n", + "kluski = 0\n", + "capellini = 1\n", + "fettucine = 2\n", + "products = ['kluski', 'capellini', 'fettucine']\n", + "num_products = len(products)\n", + "\n", + "flour = 0\n", + "eggs = 1\n", + "resources = ['flour', 'eggs']\n", + "num_resources = len(resources)\n", + "\n", + "consumption = [[0.5, 0.2], [0.4, 0.4], [0.3, 0.6]]\n", + "capacity = [20, 40]\n", + "demand = [100, 200, 300]\n", + "inside_cost = [0.6, 0.8, 0.3]\n", + "outside_cost = [0.8, 0.9, 0.4]\n", + "\n", + "#\n", + "# declare variables\n", + "#\n", + "inside = [\n", + " solver.NumVar(0, 10000, 'inside[%i]' % p) for p in range(num_products)\n", + "]\n", + "outside = [\n", + " solver.NumVar(0, 10000, 'outside[%i]' % p) for p in range(num_products)\n", + "]\n", + "\n", + "# to minimize\n", + "z = solver.Sum([\n", + " inside_cost[p] * inside[p] + outside_cost[p] * outside[p]\n", + " for p in range(num_products)\n", + "])\n", + "\n", + "#\n", + "# constraints\n", + "#\n", + "for r in range(num_resources):\n", + " solver.Add(\n", + " solver.Sum([consumption[p][r] * inside[p]\n", + " for p in range(num_products)]) <= capacity[r])\n", + "\n", + "for p in range(num_products):\n", + " solver.Add(inside[p] + outside[p] >= demand[p])\n", + "\n", + "objective = solver.Minimize(z)\n", + "\n", + "solver.Solve()\n", + "\n", + "print()\n", + "print('z = ', solver.Objective().Value())\n", + "\n", + "for p in range(num_products):\n", + " print(\n", + " products[p],\n", + " ': inside:',\n", + " inside[p].SolutionValue(),\n", + " '(ReducedCost:',\n", + " inside[p].ReducedCost(),\n", + " ')',\n", + " end=' ')\n", + " print('outside:', outside[p].SolutionValue(), ' (ReducedCost:',\n", + " outside[p].ReducedCost(), ')')\n", + "print()\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/contrib/project_scheduling_sat.ipynb b/examples/notebook/contrib/project_scheduling_sat.ipynb new file mode 100644 index 0000000000..1489d3debc --- /dev/null +++ b/examples/notebook/contrib/project_scheduling_sat.ipynb @@ -0,0 +1,74 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from __future__ import division\n", + "from __future__ import print_function\n", + "\n", + "from ortools.sat.python import cp_model\n", + "\n", + "#project name, duration, starts earliest, ends latest, demand role A, demand role S, demand role J\n", + "projects = [\n", + " ['P1',3, 0, 9, 1, 0, 1],\n", + " ['P2',8, 0, 9, 1, 1, 0],\n", + " ['P3',3, 0, 9, 1, 0, 2],\n", + " ['P4',4, 0, 9, 1, 0, 1]\n", + " ]\n", + "\n", + "num_projects = len(projects)\n", + "\n", + "roles = ['A','S','J']\n", + "\n", + "#Roles available at each time step\n", + "available_roles = [\n", + " [2,2,2,2,2,2,2,2,2,2], #Role A\n", + " [1,1,1,1,1,1,1,1,1,1], #Role S\n", + " [1,1,1,1,1,1,1,2,2,2] #Role J\n", + "]\n", + "\n", + "all_projects = range(num_projects)\n", + "all_time_steps = range(time_steps)\n", + "all_roles = range(len(roles))\n", + "\n", + "# Creates the model.\n", + "model = cp_model.CpModel()\n", + "\n", + "#Creating decision variables\n", + "\n", + "#starts and ends of the projects\n", + "starts = [model.NewIntVar(projects[j][2], projects[j][3] + 1 , 'start_%i' % j) for j in all_projects]\n", + "ends = [model.NewIntVar(projects[j][2], projects[j][3] + 1, 'end_%i' % j) for j in all_projects]\n", + "intervals = [model.NewIntervalVar(starts[j], projects[j][1], ends[j], 'interval_%i' % j) for j in all_projects]\n", + "\n", + "# Role A has a capacity 2. Every project uses it.\n", + "demands = [1 for _ in all_projects]\n", + "model.AddCumulative(intervals, demands, 2)\n", + "\n", + "# Role S has a capacity of 1\n", + "model.AddNoOverlap([intervals[i] for i in all_projects if projects[i][5]])\n", + "\n", + "# Project J has a capacity of 1 or 2.\n", + "used_capacity = model.NewIntervalVar(0, 7, 7, 'unavailable')\n", + "intervals_for_project_j = intervals + [used_capacity]\n", + "demands_for_project_j = [projects[j][6] for j in all_projects] + [1]\n", + "model.AddCumulative(intervals_for_project_j, demands_for_project_j, 2)\n", + "\n", + "#We want the projects to start as early as possible\n", + "model.Minimize(sum(starts))\n", + "\n", + "# Solve model.\n", + "solver = cp_model.CpSolver()\n", + "solver.parameters.log_search_progress = True\n", + "status=solver.Solve(model)\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/contrib/pyls_api.ipynb b/examples/notebook/contrib/pyls_api.ipynb new file mode 100644 index 0000000000..4fd7cd3fc9 --- /dev/null +++ b/examples/notebook/contrib/pyls_api.ipynb @@ -0,0 +1,134 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from __future__ import print_function\n", + "from ortools.constraint_solver import pywrapcp\n", + "\n", + "\n", + "class OneVarLns(pywrapcp.BaseLns):\n", + " \"\"\"One Var LNS.\"\"\"\n", + "\n", + " def __init__(self, vars):\n", + " pywrapcp.BaseLns.__init__(self, vars)\n", + " self.__index = 0\n", + "\n", + " def InitFragments(self):\n", + " self.__index = 0\n", + "\n", + " def NextFragment(self):\n", + " if self.__index < self.Size():\n", + " self.AppendToFragment(self.__index)\n", + " self.__index += 1\n", + " return True\n", + " else:\n", + " return False\n", + "\n", + "\n", + "class MoveOneVar(pywrapcp.IntVarLocalSearchOperator):\n", + " \"\"\"Move one var up or down.\"\"\"\n", + "\n", + " def __init__(self, vars):\n", + " pywrapcp.IntVarLocalSearchOperator.__init__(self, vars)\n", + " self.__index = 0\n", + " self.__up = False\n", + "\n", + " def OneNeighbor(self):\n", + " current_value = self.OldValue(self.__index)\n", + " if self.__up:\n", + " self.SetValue(self.__index, current_value + 1)\n", + " self.__index = (self.__index + 1) % self.Size()\n", + " else:\n", + " self.SetValue(self.__index, current_value - 1)\n", + " self.__up = not self.__up\n", + " return True\n", + "\n", + " def OnStart(self):\n", + " pass\n", + "\n", + " def IsIncremental(self):\n", + " return False\n", + "\n", + "\n", + "class SumFilter(pywrapcp.IntVarLocalSearchFilter):\n", + " \"\"\"Filter to speed up LS computation.\"\"\"\n", + "\n", + " def __init__(self, vars):\n", + " pywrapcp.IntVarLocalSearchFilter.__init__(self, vars)\n", + " self.__sum = 0\n", + "\n", + " def OnSynchronize(self, delta):\n", + " self.__sum = sum(self.Value(index) for index in range(self.Size()))\n", + "\n", + " def Accept(self, delta, unused_delta_delta, unused_objective_min,\n", + " unused_objective_max):\n", + " solution_delta = delta.IntVarContainer()\n", + " solution_delta_size = solution_delta.Size()\n", + " for i in range(solution_delta_size):\n", + " if not solution_delta.Element(i).Activated():\n", + " return True\n", + "\n", + " new_sum = self.__sum\n", + " for i in range(solution_delta_size):\n", + " element = solution_delta.Element(i)\n", + " int_var = element.Var()\n", + " touched_var_index = self.IndexFromVar(int_var)\n", + " old_value = self.Value(touched_var_index)\n", + " new_value = element.Value()\n", + " new_sum += new_value - old_value\n", + "\n", + " return new_sum < self.__sum\n", + "\n", + " def IsIncremental(self):\n", + " return False\n", + "\n", + "\n", + "def Solve(type):\n", + " solver = pywrapcp.Solver('Solve')\n", + " vars = [solver.IntVar(0, 4) for _ in range(4)]\n", + " sum_var = solver.Sum(vars).Var()\n", + " obj = solver.Minimize(sum_var, 1)\n", + " db = solver.Phase(vars, solver.CHOOSE_FIRST_UNBOUND, solver.ASSIGN_MAX_VALUE)\n", + " ls = None\n", + "\n", + " if type == 0: # LNS\n", + " print('Large Neighborhood Search')\n", + " one_var_lns = OneVarLns(vars)\n", + " ls_params = solver.LocalSearchPhaseParameters(sum_var, one_var_lns, db)\n", + " ls = solver.LocalSearchPhase(vars, db, ls_params)\n", + " elif type == 1: # LS\n", + " print('Local Search')\n", + " move_one_var = MoveOneVar(vars)\n", + " ls_params = solver.LocalSearchPhaseParameters(sum_var, move_one_var, db)\n", + " ls = solver.LocalSearchPhase(vars, db, ls_params)\n", + " else:\n", + " print('Local Search with Filter')\n", + " move_one_var = MoveOneVar(vars)\n", + " sum_filter = SumFilter(vars)\n", + " ls_params = solver.LocalSearchPhaseParameters(sum_var, move_one_var, db, None,\n", + " [sum_filter])\n", + " ls = solver.LocalSearchPhase(vars, db, ls_params)\n", + "\n", + " collector = solver.LastSolutionCollector()\n", + " collector.Add(vars)\n", + " collector.AddObjective(sum_var)\n", + " log = solver.SearchLog(1000, obj)\n", + " solver.Solve(ls, [collector, obj, log])\n", + " print('Objective value = %d' % collector.ObjectiveValue(0))\n", + "\n", + "\n", + "Solve(0)\n", + "Solve(1)\n", + "Solve(2)\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/contrib/quasigroup_completion.ipynb b/examples/notebook/contrib/quasigroup_completion.ipynb new file mode 100644 index 0000000000..7e517f5d5d --- /dev/null +++ b/examples/notebook/contrib/quasigroup_completion.ipynb @@ -0,0 +1,212 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010 Hakan Kjellerstrand hakank@gmail.com\n", + "#\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"\n", + "\n", + " Quasigroup completion Google CP Solver.\n", + "\n", + "\n", + " See Carla P. Gomes and David Shmoys:\n", + " \"Completing Quasigroups or Latin Squares: Structured Graph Coloring Problem\"\n", + "\n", + " See also\n", + " Ivars Peterson \"Completing Latin Squares\"\n", + " http://www.maa.org/mathland/mathtrek_5_8_00.html\n", + " '''\n", + " Using only the numbers 1, 2, 3, and 4, arrange four sets of these numbers\n", + " into\n", + " a four-by-four array so that no column or row contains the same two numbers.\n", + " The result is known as a Latin square.\n", + " ...\n", + " The so-called quasigroup completion problem concerns a table that is\n", + " correctly\n", + " but only partially filled in. The question is whether the remaining blanks\n", + " in\n", + " the table can be filled in to obtain a complete Latin square (or a proper\n", + " quasigroup multiplication table).\n", + " '''\n", + "\n", + " Compare with the following models:\n", + " * Choco: http://www.hakank.org/choco/QuasigroupCompletion.java\n", + " * Comet: http://www.hakank.org/comet/quasigroup_completion.co\n", + " * ECLiPSE: http://www.hakank.org/eclipse/quasigroup_completion.ecl\n", + " * Gecode: http://www.hakank.org/gecode/quasigroup_completion.cpp\n", + " * Gecode/R: http://www.hakank.org/gecode_r/quasigroup_completion.rb\n", + " * JaCoP: http://www.hakank.org/JaCoP/QuasigroupCompletion.java\n", + " * MiniZinc: http://www.hakank.org/minizinc/quasigroup_completion.mzn\n", + " * Tailor/Essence': http://www.hakank.org/tailor/quasigroup_completion.eprime\n", + " * SICStus: http://hakank.org/sicstus/quasigroup_completion.pl\n", + " * Zinc: http://hakank.org/minizinc/quasigroup_completion.zinc\n", + "\n", + " This model was created by Hakan Kjellerstrand (hakank@gmail.com)\n", + " Also see my other Google CP Solver models:\n", + " http://www.hakank.org/google_or_tools/\n", + "\"\"\"\n", + "from __future__ import print_function\n", + "import sys\n", + "from ortools.constraint_solver import pywrapcp\n", + "\n", + "default_n = 5\n", + "X = 0\n", + "# default problem\n", + "# (This is the same as quasigroup1.txt)\n", + "default_puzzle = [[1, X, X, X, 4], [X, 5, X, X, X], [4, X, X, 2, X],\n", + " [X, 4, X, X, X], [X, X, 5, X, 1]]\n", + "\n", + "\n", + "\n", + "# Create the solver.\n", + "solver = pywrapcp.Solver(\"Quasigroup completion\")\n", + "\n", + "#\n", + "# data\n", + "#\n", + "\n", + "if puzzle == \"\":\n", + " puzzle = default_puzzle\n", + " n = default_n\n", + "\n", + "print(\"Problem:\")\n", + "print_game(puzzle, n, n)\n", + "\n", + "# declare variables\n", + "x = {}\n", + "for i in range(n):\n", + " for j in range(n):\n", + " x[(i, j)] = solver.IntVar(1, n, \"x %i %i\" % (i, j))\n", + "\n", + "xflat = [x[(i, j)] for i in range(n) for j in range(n)]\n", + "\n", + "#\n", + "# constraints\n", + "#\n", + "\n", + "#\n", + "# set the clues\n", + "#\n", + "for i in range(n):\n", + " for j in range(n):\n", + " if puzzle[i][j] > X:\n", + " solver.Add(x[i, j] == puzzle[i][j])\n", + "\n", + "#\n", + "# rows and columns must be different\n", + "#\n", + "for i in range(n):\n", + " solver.Add(solver.AllDifferent([x[i, j] for j in range(n)]))\n", + " solver.Add(solver.AllDifferent([x[j, i] for j in range(n)]))\n", + "\n", + "#\n", + "# solution and search\n", + "#\n", + "solution = solver.Assignment()\n", + "solution.Add(xflat)\n", + "\n", + "# This version prints out the solution directly, and\n", + "# don't collect them as solver.FirstSolutionCollector(solution) do\n", + "# (db: DecisionBuilder)\n", + "db = solver.Phase(xflat, solver.INT_VAR_SIMPLE, solver.ASSIGN_MIN_VALUE)\n", + "\n", + "solver.NewSearch(db)\n", + "num_solutions = 0\n", + "while solver.NextSolution():\n", + " num_solutions += 1\n", + " print(\"Solution %i\" % num_solutions)\n", + " xval = [x[(i, j)].Value() for i in range(n) for j in range(n)]\n", + " for i in range(n):\n", + " for j in range(n):\n", + " print(xval[i * n + j], end=\" \")\n", + " print()\n", + " print()\n", + "solver.EndSearch()\n", + "\n", + "if num_solutions == 0:\n", + " print(\"No solutions found\")\n", + "\n", + "# # Note: AllSolution may take very much RAM, hence I choose to\n", + "# # show just the first solution.\n", + "# # collector = solver.AllSolutionCollector(solution)\n", + "# collector = solver.FirstSolutionCollector(solution)\n", + "# solver.Solve(solver.Phase([x[(i,j)] for i in range(n) for j in range(n)],\n", + "# solver.CHOOSE_FIRST_UNBOUND,\n", + "# solver.ASSIGN_MIN_VALUE),\n", + "# [collector])\n", + "#\n", + "# num_solutions = collector.SolutionCount()\n", + "# print \"\\nnum_solutions: \", num_solutions\n", + "# if num_solutions > 0:\n", + "# print \"\\nJust showing the first solution...\"\n", + "# for s in range(num_solutions):\n", + "# xval = [collector.Value(s, x[(i,j)]) for i in range(n) for j in range(n)]\n", + "# for i in range(n):\n", + "# for j in range(n):\n", + "# print xval[i*n+j],\n", + "# print\n", + "# print\n", + "\n", + "print()\n", + "print(\"num_solutions:\", num_solutions)\n", + "print(\"failures:\", solver.Failures())\n", + "print(\"branches:\", solver.Branches())\n", + "print(\"WallTime:\", solver.WallTime())\n", + "\n", + "\n", + "#\n", + "# Read a problem instance from a file\n", + "#def read_problem(file):\n", + " f = open(file, \"r\")\n", + " n = int(f.readline())\n", + " game = []\n", + " for i in range(n):\n", + " x = f.readline()\n", + " row_x = (x.rstrip()).split(\" \")\n", + " row = [0] * n\n", + " for j in range(n):\n", + " if row_x[j] == \".\":\n", + " tmp = 0\n", + " else:\n", + " tmp = int(row_x[j])\n", + " row[j] = tmp\n", + " game.append(row)\n", + " return [game, n]\n", + "\n", + "\n", + "def print_board(x, rows, cols):\n", + " for i in range(rows):\n", + " for j in range(cols):\n", + " print(\"% 2s\" % x[i, j], end=\" \")\n", + " print(\"\")\n", + "\n", + "\n", + "def print_game(game, rows, cols):\n", + " for i in range(rows):\n", + " for j in range(cols):\n", + " print(\"% 2s\" % game[i][j], end=\" \")\n", + " print(\"\")\n", + "\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/contrib/regular.ipynb b/examples/notebook/contrib/regular.ipynb new file mode 100644 index 0000000000..ec39849cff --- /dev/null +++ b/examples/notebook/contrib/regular.ipynb @@ -0,0 +1,235 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010 Hakan Kjellerstrand hakank@gmail.com\n", + "#\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"\n", + "\n", + " Global constraint regular in Google CP Solver.\n", + "\n", + " This is a translation of MiniZinc's regular constraint (defined in\n", + " lib/zinc/globals.mzn). All comments are from the MiniZinc code.\n", + " '''\n", + " The sequence of values in array 'x' (which must all be in the range 1..S)\n", + " is accepted by the DFA of 'Q' states with input 1..S and transition\n", + " function 'd' (which maps (1..Q, 1..S) -> 0..Q)) and initial state 'q0'\n", + " (which must be in 1..Q) and accepting states 'F' (which all must be in\n", + " 1..Q). We reserve state 0 to be an always failing state.\n", + " '''\n", + "\n", + " It is, however, translated from the Comet model:\n", + " * Comet: http://www.hakank.org/comet/regular.co\n", + "\n", + " Here we test with the following regular expression:\n", + " 0*1{3}0+1{2}0+1{1}0*\n", + " using an array of size 10.\n", + "\n", + " This model was created by Hakan Kjellerstrand (hakank@gmail.com)\n", + " Also see my other Google CP Solver models:\n", + " http://www.hakank.org/google_or_tools/\n", + "\n", + "\"\"\"\n", + "from __future__ import print_function\n", + "from ortools.constraint_solver import pywrapcp\n", + "\n", + "\n", + "#\n", + "# Global constraint regular\n", + "#\n", + "# This is a translation of MiniZinc's regular constraint (defined in\n", + "# lib/zinc/globals.mzn), via the Comet code refered above.\n", + "# All comments are from the MiniZinc code.\n", + "# '''\n", + "# The sequence of values in array 'x' (which must all be in the range 1..S)\n", + "# is accepted by the DFA of 'Q' states with input 1..S and transition\n", + "# function 'd' (which maps (1..Q, 1..S) -> 0..Q)) and initial state 'q0'\n", + "# (which must be in 1..Q) and accepting states 'F' (which all must be in\n", + "# 1..Q). We reserve state 0 to be an always failing state.\n", + "# '''\n", + "#\n", + "# x : IntVar array\n", + "# Q : number of states\n", + "# S : input_max\n", + "# d : transition matrix\n", + "# q0: initial state\n", + "# F : accepting states\n", + "def regular(x, Q, S, d, q0, F):\n", + "\n", + " solver = x[0].solver()\n", + "\n", + " assert Q > 0, 'regular: \"Q\" must be greater than zero'\n", + " assert S > 0, 'regular: \"S\" must be greater than zero'\n", + "\n", + " # d2 is the same as d, except we add one extra transition for\n", + " # each possible input; each extra transition is from state zero\n", + " # to state zero. This allows us to continue even if we hit a\n", + " # non-accepted input.\n", + "\n", + " # int d2[0..Q, 1..S];\n", + " d2 = []\n", + " for i in range(Q + 1):\n", + " row = []\n", + " for j in range(S):\n", + " if i == 0:\n", + " row.append(0)\n", + " else:\n", + " row.append(d[i - 1][j])\n", + " d2.append(row)\n", + "\n", + " d2_flatten = [d2[i][j] for i in range(Q + 1) for j in range(S)]\n", + "\n", + " # If x has index set m..n, then a[m-1] holds the initial state\n", + " # (q0), and a[i+1] holds the state we're in after processing\n", + " # x[i]. If a[n] is in F, then we succeed (ie. accept the\n", + " # string).\n", + " x_range = list(range(0, len(x)))\n", + " m = 0\n", + " n = len(x)\n", + "\n", + " a = [solver.IntVar(0, Q + 1, 'a[%i]' % i) for i in range(m, n + 1)]\n", + "\n", + " # Check that the final state is in F\n", + " solver.Add(solver.MemberCt(a[-1], F))\n", + " # First state is q0\n", + " solver.Add(a[m] == q0)\n", + " for i in x_range:\n", + " solver.Add(x[i] >= 1)\n", + " solver.Add(x[i] <= S)\n", + "\n", + " # Determine a[i+1]: a[i+1] == d2[a[i], x[i]]\n", + " solver.Add(\n", + " a[i + 1] == solver.Element(d2_flatten, ((a[i]) * S) + (x[i] - 1)))\n", + "\n", + "\n", + "#\n", + "# Make a transition (automaton) matrix from a\n", + "# single pattern, e.g. [3,2,1]\n", + "#\n", + "def make_transition_matrix(pattern):\n", + "\n", + " p_len = len(pattern)\n", + " print('p_len:', p_len)\n", + " num_states = p_len + sum(pattern)\n", + " print('num_states:', num_states)\n", + " t_matrix = []\n", + " for i in range(num_states):\n", + " row = []\n", + " for j in range(2):\n", + " row.append(0)\n", + " t_matrix.append(row)\n", + "\n", + " # convert pattern to a 0/1 pattern for easy handling of\n", + " # the states\n", + " tmp = [0 for i in range(num_states)]\n", + " c = 0\n", + " tmp[c] = 0\n", + " for i in range(p_len):\n", + " for j in range(pattern[i]):\n", + " c += 1\n", + " tmp[c] = 1\n", + " if c < num_states - 1:\n", + " c += 1\n", + " tmp[c] = 0\n", + " print('tmp:', tmp)\n", + "\n", + " t_matrix[num_states - 1][0] = num_states\n", + " t_matrix[num_states - 1][1] = 0\n", + "\n", + " for i in range(num_states):\n", + " if tmp[i] == 0:\n", + " t_matrix[i][0] = i + 1\n", + " t_matrix[i][1] = i + 2\n", + " else:\n", + " if i < num_states - 1:\n", + " if tmp[i + 1] == 1:\n", + " t_matrix[i][0] = 0\n", + " t_matrix[i][1] = i + 2\n", + " else:\n", + " t_matrix[i][0] = i + 2\n", + " t_matrix[i][1] = 0\n", + "\n", + " print('The states:')\n", + " for i in range(num_states):\n", + " for j in range(2):\n", + " print(t_matrix[i][j], end=' ')\n", + " print()\n", + " print()\n", + "\n", + " return t_matrix\n", + "\n", + "\n", + "\n", + "# Create the solver.\n", + "solver = pywrapcp.Solver('Regular test')\n", + "\n", + "#\n", + "# data\n", + "#\n", + "\n", + "this_len = 10\n", + "pp = [3, 2, 1]\n", + "\n", + "transition_fn = make_transition_matrix(pp)\n", + "n_states = len(transition_fn)\n", + "input_max = 2\n", + "\n", + "# Note: we use '1' and '2' (rather than 0 and 1)\n", + "# since 0 represents the failing state.\n", + "initial_state = 1\n", + "\n", + "accepting_states = [n_states]\n", + "\n", + "# declare variables\n", + "reg_input = [\n", + " solver.IntVar(1, input_max, 'reg_input[%i]' % i) for i in range(this_len)\n", + "]\n", + "\n", + "#\n", + "# constraints\n", + "#\n", + "regular(reg_input, n_states, input_max, transition_fn, initial_state,\n", + " accepting_states)\n", + "\n", + "#\n", + "# solution and search\n", + "#\n", + "db = solver.Phase(reg_input, solver.CHOOSE_MIN_SIZE_HIGHEST_MAX,\n", + " solver.ASSIGN_MIN_VALUE)\n", + "\n", + "solver.NewSearch(db)\n", + "\n", + "num_solutions = 0\n", + "while solver.NextSolution():\n", + " print('reg_input:', [reg_input[i].Value() - 1 for i in range(this_len)])\n", + " num_solutions += 1\n", + "\n", + "solver.EndSearch()\n", + "print()\n", + "print('num_solutions:', num_solutions)\n", + "print('failures:', solver.Failures())\n", + "print('branches:', solver.Branches())\n", + "print('WallTime:', solver.WallTime(), 'ms')\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/contrib/regular_table.ipynb b/examples/notebook/contrib/regular_table.ipynb new file mode 100644 index 0000000000..7e0f08c4ab --- /dev/null +++ b/examples/notebook/contrib/regular_table.ipynb @@ -0,0 +1,228 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010 Hakan Kjellerstrand hakank@gmail.com\n", + "#\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"\n", + "\n", + " Global constraint regular in Google CP Solver.\n", + "\n", + " This is a translation of MiniZinc's regular constraint (defined in\n", + " lib/zinc/globals.mzn). All comments are from the MiniZinc code.\n", + " '''\n", + " The sequence of values in array 'x' (which must all be in the range 1..S)\n", + " is accepted by the DFA of 'Q' states with input 1..S and transition\n", + " function 'd' (which maps (1..Q, 1..S) -> 0..Q)) and initial state 'q0'\n", + " (which must be in 1..Q) and accepting states 'F' (which all must be in\n", + " 1..Q). We reserve state 0 to be an always failing state.\n", + " '''\n", + "\n", + " It is, however, translated from the Comet model:\n", + " * Comet: http://www.hakank.org/comet/regular.co\n", + "\n", + " Here we test with the following regular expression:\n", + " 0*1{3}0+1{2}0+1{1}0*\n", + " using an array of size 10.\n", + "\n", + " This model was created by Hakan Kjellerstrand (hakank@gmail.com)\n", + " Also see my other Google CP Solver models:\n", + " http://www.hakank.org/google_or_tools/\n", + "\n", + "\"\"\"\n", + "from __future__ import print_function\n", + "from ortools.constraint_solver import pywrapcp\n", + "\n", + "\n", + "#\n", + "# Global constraint regular\n", + "#\n", + "# This is a translation of MiniZinc's regular constraint (defined in\n", + "# lib/zinc/globals.mzn), via the Comet code refered above.\n", + "# All comments are from the MiniZinc code.\n", + "# '''\n", + "# The sequence of values in array 'x' (which must all be in the range 1..S)\n", + "# is accepted by the DFA of 'Q' states with input 1..S and transition\n", + "# function 'd' (which maps (1..Q, 1..S) -> 0..Q)) and initial state 'q0'\n", + "# (which must be in 1..Q) and accepting states 'F' (which all must be in\n", + "# 1..Q). We reserve state 0 to be an always failing state.\n", + "# '''\n", + "#\n", + "# x : IntVar array\n", + "# Q : number of states\n", + "# S : input_max\n", + "# d : transition matrix\n", + "# q0: initial state\n", + "# F : accepting states\n", + "def regular(x, Q, S, d, q0, F):\n", + "\n", + " solver = x[0].solver()\n", + "\n", + " assert Q > 0, 'regular: \"Q\" must be greater than zero'\n", + " assert S > 0, 'regular: \"S\" must be greater than zero'\n", + "\n", + " # d2 is the same as d, except we add one extra transition for\n", + " # each possible input; each extra transition is from state zero\n", + " # to state zero. This allows us to continue even if we hit a\n", + " # non-accepted input.\n", + "\n", + " d2 = []\n", + " for i in range(Q + 1):\n", + " for j in range(S):\n", + " if i == 0:\n", + " d2.append((0, j, 0))\n", + " else:\n", + " d2.append((i, j, d[i - 1][j]))\n", + "\n", + " # If x hasindex set m..n, then a[m-1] holds the initial state\n", + " # (q0), and a[i+1] holds the state we're in after processing\n", + " # x[i]. If a[n] is in F, then we succeed (ie. accept the\n", + " # string).\n", + " x_range = list(range(0, len(x)))\n", + " m = 0\n", + " n = len(x)\n", + "\n", + " a = [solver.IntVar(0, Q, 'a[%i]' % i) for i in range(m, n + 1)]\n", + "\n", + " # Check that the final state is in F\n", + " solver.Add(solver.MemberCt(a[-1], F))\n", + " # First state is q0\n", + " solver.Add(a[m] == q0)\n", + " for i in x_range:\n", + " solver.Add(x[i] >= 1)\n", + " solver.Add(x[i] <= S)\n", + " # Determine a[i+1]: a[i+1] == d2[a[i], x[i]]\n", + " solver.Add(solver.AllowedAssignments((a[i], x[i] - 1, a[i + 1]), d2))\n", + "\n", + "\n", + "#\n", + "# Make a transition (automaton) matrix from a\n", + "# single pattern, e.g. [3,2,1]\n", + "#\n", + "def make_transition_matrix(pattern):\n", + "\n", + " p_len = len(pattern)\n", + " print('p_len:', p_len)\n", + " num_states = p_len + sum(pattern)\n", + " print('num_states:', num_states)\n", + " t_matrix = []\n", + " for i in range(num_states):\n", + " row = []\n", + " for j in range(2):\n", + " row.append(0)\n", + " t_matrix.append(row)\n", + "\n", + " # convert pattern to a 0/1 pattern for easy handling of\n", + " # the states\n", + " tmp = [0 for i in range(num_states)]\n", + " c = 0\n", + " tmp[c] = 0\n", + " for i in range(p_len):\n", + " for j in range(pattern[i]):\n", + " c += 1\n", + " tmp[c] = 1\n", + " if c < num_states - 1:\n", + " c += 1\n", + " tmp[c] = 0\n", + " print('tmp:', tmp)\n", + "\n", + " t_matrix[num_states - 1][0] = num_states\n", + " t_matrix[num_states - 1][1] = 0\n", + "\n", + " for i in range(num_states):\n", + " if tmp[i] == 0:\n", + " t_matrix[i][0] = i + 1\n", + " t_matrix[i][1] = i + 2\n", + " else:\n", + " if i < num_states - 1:\n", + " if tmp[i + 1] == 1:\n", + " t_matrix[i][0] = 0\n", + " t_matrix[i][1] = i + 2\n", + " else:\n", + " t_matrix[i][0] = i + 2\n", + " t_matrix[i][1] = 0\n", + "\n", + " print('The states:')\n", + " for i in range(num_states):\n", + " for j in range(2):\n", + " print(t_matrix[i][j], end=' ')\n", + " print()\n", + " print()\n", + "\n", + " return t_matrix\n", + "\n", + "\n", + "\n", + "# Create the solver.\n", + "solver = pywrapcp.Solver('Regular test')\n", + "\n", + "#\n", + "# data\n", + "#\n", + "\n", + "this_len = 10\n", + "pp = [3, 2, 1]\n", + "\n", + "transition_fn = make_transition_matrix(pp)\n", + "n_states = len(transition_fn)\n", + "input_max = 2\n", + "\n", + "# Note: we use '1' and '2' (rather than 0 and 1)\n", + "# since 0 represents the failing state.\n", + "initial_state = 1\n", + "\n", + "accepting_states = [n_states]\n", + "\n", + "# declare variables\n", + "reg_input = [\n", + " solver.IntVar(1, input_max, 'reg_input[%i]' % i) for i in range(this_len)\n", + "]\n", + "\n", + "#\n", + "# constraints\n", + "#\n", + "regular(reg_input, n_states, input_max, transition_fn, initial_state,\n", + " accepting_states)\n", + "\n", + "#\n", + "# solution and search\n", + "#\n", + "db = solver.Phase(reg_input, solver.CHOOSE_MIN_SIZE_HIGHEST_MAX,\n", + " solver.ASSIGN_MIN_VALUE)\n", + "\n", + "solver.NewSearch(db)\n", + "\n", + "num_solutions = 0\n", + "while solver.NextSolution():\n", + " print('reg_input:', [reg_input[i].Value() - 1 for i in range(this_len)])\n", + " num_solutions += 1\n", + "\n", + "solver.EndSearch()\n", + "print()\n", + "print('num_solutions:', num_solutions)\n", + "print('failures:', solver.Failures())\n", + "print('branches:', solver.Branches())\n", + "print('WallTime:', solver.WallTime(), 'ms')\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/contrib/regular_table2.ipynb b/examples/notebook/contrib/regular_table2.ipynb new file mode 100644 index 0000000000..de21035282 --- /dev/null +++ b/examples/notebook/contrib/regular_table2.ipynb @@ -0,0 +1,212 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010 Hakan Kjellerstrand hakank@gmail.com\n", + "#\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"\n", + "\n", + " Global constraint regular in Google CP Solver.\n", + "\n", + " This is a translation of MiniZinc's regular constraint (defined in\n", + " lib/zinc/globals.mzn). All comments are from the MiniZinc code.\n", + " '''\n", + " The sequence of values in array 'x' (which must all be in the range 1..S)\n", + " is accepted by the DFA of 'Q' states with input 1..S and transition\n", + " function 'd' (which maps (1..Q, 1..S) -> 0..Q)) and initial state 'q0'\n", + " (which must be in 1..Q) and accepting states 'F' (which all must be in\n", + " 1..Q). We reserve state 0 to be an always failing state.\n", + " '''\n", + "\n", + " It is, however, translated from the Comet model:\n", + " * Comet: http://www.hakank.org/comet/regular.co\n", + "\n", + " Here we test with the following regular expression:\n", + " 0*1{3}0+1{2}0+1{1}0*\n", + " using an array of size 10.\n", + "\n", + " This model was created by Hakan Kjellerstrand (hakank@gmail.com)\n", + " Also see my other Google CP Solver models:\n", + " http://www.hakank.org/google_or_tools/\n", + "\n", + "\"\"\"\n", + "from __future__ import print_function\n", + "from ortools.constraint_solver import pywrapcp\n", + "\n", + "\n", + "#\n", + "# Global constraint regular\n", + "#\n", + "# This is a translation of MiniZinc's regular constraint (defined in\n", + "# lib/zinc/globals.mzn), via the Comet code refered above.\n", + "# All comments are from the MiniZinc code.\n", + "# '''\n", + "# The sequence of values in array 'x' (which must all be in the range 1..S)\n", + "# is accepted by the DFA of 'Q' states with input 1..S and transition\n", + "# function 'd' (which maps (1..Q, 1..S) -> 0..Q)) and initial state 'q0'\n", + "# (which must be in 1..Q) and accepting states 'F' (which all must be in\n", + "# 1..Q). We reserve state 0 to be an always failing state.\n", + "# '''\n", + "#\n", + "# x : IntVar array\n", + "# Q : number of states\n", + "# S : input_max\n", + "# d : transition matrix\n", + "# q0: initial state\n", + "# F : accepting states\n", + "def regular(x, Q, S, d, q0, F):\n", + "\n", + " solver = x[0].solver()\n", + "\n", + " assert Q > 0, 'regular: \"Q\" must be greater than zero'\n", + " assert S > 0, 'regular: \"S\" must be greater than zero'\n", + "\n", + " # d2 is the same as d, except we add one extra transition for\n", + " # each possible input; each extra transition is from state zero\n", + " # to state zero. This allows us to continue even if we hit a\n", + " # non-accepted input.\n", + "\n", + " d2 = []\n", + " for i in range(Q + 1):\n", + " for j in range(1, S + 1):\n", + " if i == 0:\n", + " d2.append((0, j, 0))\n", + " else:\n", + " d2.append((i, j, d[i - 1][j - 1]))\n", + "\n", + " solver.Add(solver.TransitionConstraint(x, d2, q0, F))\n", + "\n", + "\n", + "#\n", + "# Make a transition (automaton) matrix from a\n", + "# single pattern, e.g. [3,2,1]\n", + "#\n", + "\n", + "\n", + "def make_transition_matrix(pattern):\n", + "\n", + " p_len = len(pattern)\n", + " print('p_len:', p_len)\n", + " num_states = p_len + sum(pattern)\n", + " print('num_states:', num_states)\n", + " t_matrix = []\n", + " for i in range(num_states):\n", + " row = []\n", + " for j in range(2):\n", + " row.append(0)\n", + " t_matrix.append(row)\n", + "\n", + " # convert pattern to a 0/1 pattern for easy handling of\n", + " # the states\n", + " tmp = [0 for i in range(num_states)]\n", + " c = 0\n", + " tmp[c] = 0\n", + " for i in range(p_len):\n", + " for j in range(pattern[i]):\n", + " c += 1\n", + " tmp[c] = 1\n", + " if c < num_states - 1:\n", + " c += 1\n", + " tmp[c] = 0\n", + " print('tmp:', tmp)\n", + "\n", + " t_matrix[num_states - 1][0] = num_states\n", + " t_matrix[num_states - 1][1] = 0\n", + "\n", + " for i in range(num_states):\n", + " if tmp[i] == 0:\n", + " t_matrix[i][0] = i + 1\n", + " t_matrix[i][1] = i + 2\n", + " else:\n", + " if i < num_states - 1:\n", + " if tmp[i + 1] == 1:\n", + " t_matrix[i][0] = 0\n", + " t_matrix[i][1] = i + 2\n", + " else:\n", + " t_matrix[i][0] = i + 2\n", + " t_matrix[i][1] = 0\n", + "\n", + " print('The states:')\n", + " for i in range(num_states):\n", + " for j in range(2):\n", + " print(t_matrix[i][j], end=' ')\n", + " print()\n", + " print()\n", + "\n", + " return t_matrix\n", + "\n", + "\n", + "\n", + "# Create the solver.\n", + "solver = pywrapcp.Solver('Regular test')\n", + "\n", + "#\n", + "# data\n", + "#\n", + "\n", + "this_len = 10\n", + "pp = [3, 2, 1]\n", + "\n", + "transition_fn = make_transition_matrix(pp)\n", + "n_states = len(transition_fn)\n", + "input_max = 2\n", + "\n", + "# Note: we use '1' and '2' (rather than 0 and 1)\n", + "# since 0 represents the failing state.\n", + "initial_state = 1\n", + "\n", + "accepting_states = [n_states]\n", + "\n", + "# declare variables\n", + "reg_input = [\n", + " solver.IntVar(1, input_max, 'reg_input[%i]' % i) for i in range(this_len)\n", + "]\n", + "\n", + "#\n", + "# constraints\n", + "#\n", + "regular(reg_input, n_states, input_max, transition_fn, initial_state,\n", + " accepting_states)\n", + "\n", + "#\n", + "# solution and search\n", + "#\n", + "db = solver.Phase(reg_input, solver.CHOOSE_MIN_SIZE_HIGHEST_MAX,\n", + " solver.ASSIGN_MIN_VALUE)\n", + "\n", + "solver.NewSearch(db)\n", + "\n", + "num_solutions = 0\n", + "while solver.NextSolution():\n", + " print('reg_input:', [reg_input[i].Value() - 1 for i in range(this_len)])\n", + " num_solutions += 1\n", + "\n", + "solver.EndSearch()\n", + "print()\n", + "print('num_solutions:', num_solutions)\n", + "print('failures:', solver.Failures())\n", + "print('branches:', solver.Branches())\n", + "print('WallTime:', solver.WallTime(), 'ms')\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/contrib/rogo2.ipynb b/examples/notebook/contrib/rogo2.ipynb new file mode 100644 index 0000000000..01de986113 --- /dev/null +++ b/examples/notebook/contrib/rogo2.ipynb @@ -0,0 +1,199 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2011 Hakan Kjellerstrand hakank@gmail.com\n", + "#\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"\n", + "\n", + " Rogo puzzle solver in Google CP Solver.\n", + "\n", + " From http://www.rogopuzzle.co.nz/\n", + " '''\n", + " The object is to collect the biggest score possible using a given\n", + " number of steps in a loop around a grid. The best possible score\n", + " for a puzzle is given with it, so you can easily check that you have\n", + " solved the puzzle. Rogo puzzles can also include forbidden squares,\n", + " which must be avoided in your loop.\n", + " '''\n", + "\n", + " Also see Mike Trick:\n", + " 'Operations Research, Sudoko, Rogo, and Puzzles'\n", + " http://mat.tepper.cmu.edu/blog/?p=1302\n", + "\n", + " Problem instances:\n", + " * http://www.hakank.org/google_or_tools/rogo_mike_trick.py\n", + " * http://www.hakank.org/google_or_tools/rogo_20110106.py\n", + " * http://www.hakank.org/google_or_tools/rogo_20110107.py\n", + "\n", + "\n", + " Compare with the following models:\n", + " * Answer Set Programming:\n", + " http://www.hakank.org/answer_set_programming/rogo2.lp\n", + " * MiniZinc: http://www.hakank.org/minizinc/rogo2.mzn\n", + "\n", + " This model was created by Hakan Kjellerstrand (hakank@gmail.com)\n", + " Also see my other Google CP Solver models:\n", + " http://www.hakank.org/google_or_tools/\n", + "\n", + "\"\"\"\n", + "from __future__ import print_function\n", + "import sys\n", + "import re\n", + "\n", + "from ortools.constraint_solver import pywrapcp\n", + "\n", + "\n", + "\n", + "# Create the solver.\n", + "solver = pywrapcp.Solver(\"Rogo grid puzzle\")\n", + "\n", + "#\n", + "# data\n", + "#\n", + "W = 0\n", + "B = -1\n", + "print(\"rows: %i cols: %i max_steps: %i\" % (rows, cols, max_steps))\n", + "\n", + "problem_flatten = [problem[i][j] for i in range(rows) for j in range(cols)]\n", + "max_point = max(problem_flatten)\n", + "print(\"max_point:\", max_point)\n", + "max_sum = sum(problem_flatten)\n", + "print(\"max_sum:\", max_sum)\n", + "print()\n", + "\n", + "#\n", + "# declare variables\n", + "#\n", + "\n", + "# the coordinates\n", + "x = [solver.IntVar(0, rows - 1, \"x[%i]\" % i) for i in range(max_steps)]\n", + "y = [solver.IntVar(0, cols - 1, \"y[%i]\" % i) for i in range(max_steps)]\n", + "\n", + "# the collected points\n", + "points = [\n", + " solver.IntVar(0, max_point, \"points[%i]\" % i) for i in range(max_steps)\n", + "]\n", + "\n", + "# objective: sum of points in the path\n", + "sum_points = solver.IntVar(0, max_sum)\n", + "\n", + "#\n", + "# constraints\n", + "#\n", + "\n", + "# all coordinates must be unique\n", + "for s in range(max_steps):\n", + " for t in range(s + 1, max_steps):\n", + " b1 = x[s] != x[t]\n", + " b2 = y[s] != y[t]\n", + " solver.Add(b1 + b2 >= 1)\n", + "\n", + "# calculate the points (to maximize)\n", + "for s in range(max_steps):\n", + " solver.Add(points[s] == solver.Element(problem_flatten, x[s] * cols + y[s]))\n", + "\n", + "solver.Add(sum_points == sum(points))\n", + "\n", + "# ensure that there are not black cells in\n", + "# the path\n", + "for s in range(max_steps):\n", + " solver.Add(solver.Element(problem_flatten, x[s] * cols + y[s]) != B)\n", + "\n", + "# get the path\n", + "for s in range(max_steps - 1):\n", + " solver.Add(abs(x[s] - x[s + 1]) + abs(y[s] - y[s + 1]) == 1)\n", + "\n", + "# close the path around the corner\n", + "solver.Add(abs(x[max_steps - 1] - x[0]) + abs(y[max_steps - 1] - y[0]) == 1)\n", + "\n", + "# symmetry breaking: the cell with lowest coordinates\n", + "# should be in the first step.\n", + "for i in range(1, max_steps):\n", + " solver.Add(x[0] * cols + y[0] < x[i] * cols + y[i])\n", + "\n", + "# symmetry breaking: second step is larger than\n", + "# first step\n", + "# solver.Add(x[0]*cols+y[0] < x[1]*cols+y[1])\n", + "\n", + "#\n", + "# objective\n", + "#\n", + "objective = solver.Maximize(sum_points, 1)\n", + "\n", + "#\n", + "# solution and search\n", + "#\n", + "# db = solver.Phase(x + y,\n", + "# solver.CHOOSE_MIN_SIZE_LOWEST_MIN,\n", + "# solver.ASSIGN_MIN_VALUE)\n", + "\n", + "# Default search\n", + "parameters = pywrapcp.DefaultPhaseParameters()\n", + "\n", + "parameters.heuristic_period = 200000\n", + "# parameters.var_selection_schema = parameters.CHOOSE_MAX_SUM_IMPACT\n", + "parameters.var_selection_schema = parameters.CHOOSE_MAX_AVERAGE_IMPACT # <-\n", + "# parameters.var_selection_schema = parameters.CHOOSE_MAX_VALUE_IMPACT\n", + "\n", + "parameters.value_selection_schema = parameters.SELECT_MIN_IMPACT # <-\n", + "# parameters.value_selection_schema = parameters.SELECT_MAX_IMPACT\n", + "\n", + "# parameters.initialization_splits = 10\n", + "\n", + "db = solver.DefaultPhase(x + y, parameters)\n", + "\n", + "solver.NewSearch(db, [objective])\n", + "\n", + "num_solutions = 0\n", + "while solver.NextSolution():\n", + " num_solutions += 1\n", + " print(\"sum_points:\", sum_points.Value())\n", + " print(\"adding 1 to coords...\")\n", + " for s in range(max_steps):\n", + " print(\"%i %i\" % (x[s].Value() + 1, y[s].Value() + 1))\n", + " print()\n", + "\n", + "print(\"\\nnum_solutions:\", num_solutions)\n", + "print(\"failures:\", solver.Failures())\n", + "print(\"branches:\", solver.Branches())\n", + "print(\"WallTime:\", solver.WallTime())\n", + "\n", + "\n", + "# Default problem:\n", + "# Data from\n", + "# Mike Trick: \"Operations Research, Sudoko, Rogo, and Puzzles\"\n", + "# http://mat.tepper.cmu.edu/blog/?p=1302\n", + "#\n", + "# This has 48 solutions with symmetries;\n", + "# 4 when the path symmetry is removed.\n", + "#rows = 5\n", + "cols = 9\n", + "max_steps = 12\n", + "W = 0\n", + "B = -1\n", + "problem = [[2, W, W, W, W, W, W, W, W], [W, 3, W, W, 1, W, W, 2, W],\n", + " [W, W, W, W, W, W, B, W, 2], [W, W, 2, B, W, W, W, W, W],\n", + " [W, W, W, W, 2, W, W, 1, W]]\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/contrib/rostering_with_travel.ipynb b/examples/notebook/contrib/rostering_with_travel.ipynb new file mode 100644 index 0000000000..f9c22377f7 --- /dev/null +++ b/examples/notebook/contrib/rostering_with_travel.ipynb @@ -0,0 +1,127 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from ortools.sat.python import cp_model\n", + "\n", + "\n", + "def SolveRosteringWithTravel():\n", + " model = cp_model.CpModel()\n", + "\n", + " # [duration, start, end, location]\n", + " jobs = [[3, 0, 6, 1], [5, 0, 6, 0], [1, 3, 7, 1], [1, 3, 5, 0], [3, 0, 3, 0],\n", + " [3, 0, 8, 0]]\n", + "\n", + " max_length = 20\n", + "\n", + " num_machines = 3\n", + " all_machines = range(num_machines)\n", + "\n", + " horizon = 20\n", + " travel_time = 1\n", + " num_jobs = len(jobs)\n", + " all_jobs = range(num_jobs)\n", + "\n", + " intervals = []\n", + " optional_intervals = []\n", + " performed = []\n", + " starts = []\n", + " ends = []\n", + " travels = []\n", + "\n", + " for m in all_machines:\n", + " optional_intervals.append([])\n", + "\n", + " for i in all_jobs:\n", + " # Create main interval.\n", + " start = model.NewIntVar(jobs[i][1], horizon, 'start_%i' % i)\n", + " duration = jobs[i][0]\n", + " end = model.NewIntVar(0, jobs[i][2], 'end_%i' % i)\n", + " interval = model.NewIntervalVar(start, duration, end, 'interval_%i' % i)\n", + " starts.append(start)\n", + " intervals.append(interval)\n", + " ends.append(end)\n", + "\n", + " job_performed = []\n", + " job_travels = []\n", + " for m in all_machines:\n", + " performed_on_m = model.NewBoolVar('perform_%i_on_m%i' % (i, m))\n", + " job_performed.append(performed_on_m)\n", + "\n", + " # Create an optional copy of interval to be executed on a machine\n", + " location0 = model.NewIntVar(jobs[i][3], jobs[i][3],\n", + " 'location_%i_on_m%i' % (i, m))\n", + " start0 = model.NewIntVar(jobs[i][1], horizon, 'start_%i_on_m%i' % (i, m))\n", + " end0 = model.NewIntVar(0, jobs[i][2], 'end_%i_on_m%i' % (i, m))\n", + " interval0 = model.NewOptionalIntervalVar(\n", + " start0, duration, end0, performed_on_m, 'interval_%i_on_m%i' % (i, m))\n", + " optional_intervals[m].append(interval0)\n", + "\n", + " # We only propagate the constraint if the tasks is performed on the machine.\n", + " model.Add(start0 == start).OnlyEnforceIf(performed_on_m)\n", + " # Adding travel constraint\n", + " travel = model.NewBoolVar('is_travel_%i_on_m%i' % (i, m))\n", + " startT = model.NewIntVar(0, horizon, 'start_%i_on_m%i' % (i, m))\n", + " endT = model.NewIntVar(0, horizon, 'end_%i_on_m%i' % (i, m))\n", + " intervalT = model.NewOptionalIntervalVar(\n", + " startT, travel_time, endT, travel,\n", + " 'travel_interval_%i_on_m%i' % (i, m))\n", + " optional_intervals[m].append(intervalT)\n", + " job_travels.append(travel)\n", + "\n", + " model.Add(end0 == startT).OnlyEnforceIf(travel)\n", + "\n", + " performed.append(job_performed)\n", + " travels.append(job_travels)\n", + "\n", + " model.Add(sum(job_performed) == 1)\n", + "\n", + " for m in all_machines:\n", + " if m == 1:\n", + " for i in all_jobs:\n", + " if i == 2:\n", + " for c in all_jobs:\n", + " if (i != c) and (jobs[i][3] != jobs[c][3]):\n", + " is_job_earlier = model.NewBoolVar('is_j%i_earlier_j%i' % (i, c))\n", + " model.Add(starts[i] < starts[c]).OnlyEnforceIf(is_job_earlier)\n", + " model.Add(starts[i] >= starts[c]).OnlyEnforceIf(\n", + " is_job_earlier.Not())\n", + "\n", + " # Max Length constraint (modeled as a cumulative)\n", + " # model.AddCumulative(intervals, demands, max_length)\n", + "\n", + " # Choose which machine to perform the jobs on.\n", + " for m in all_machines:\n", + " model.AddNoOverlap(optional_intervals[m])\n", + "\n", + " # Objective variable.\n", + " total_cost = model.NewIntVar(0, 1000, 'cost')\n", + " model.Add(total_cost == sum(\n", + " performed[j][m] * (10 * (m + 1)) for j in all_jobs for m in all_machines))\n", + " model.Minimize(total_cost)\n", + "\n", + " # Solve model.\n", + " solver = cp_model.CpSolver()\n", + " result = solver.Solve(model)\n", + "\n", + " print()\n", + " print(result)\n", + " print('Statistics')\n", + " print(' - conflicts : %i' % solver.NumConflicts())\n", + " print(' - branches : %i' % solver.NumBranches())\n", + " print(' - wall time : %f ms' % solver.WallTime())\n", + "\n", + "\n", + "SolveRosteringWithTravel()\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/contrib/safe_cracking.ipynb b/examples/notebook/contrib/safe_cracking.ipynb new file mode 100644 index 0000000000..87a2e8b9a2 --- /dev/null +++ b/examples/notebook/contrib/safe_cracking.ipynb @@ -0,0 +1,115 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010 Hakan Kjellerstrand hakank@gmail.com\n", + "#\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"\n", + "\n", + " Safe cracking puzzle in Google CP Solver.\n", + "\n", + " From the Oz Primer:\n", + " http://www.comp.nus.edu.sg/~henz/projects/puzzles/digits/index.html\n", + " '''\n", + " The code of Professor Smart's safe is a sequence of 9 distinct\n", + " nonzero digits C1 .. C9 such that the following equations and\n", + " inequations are satisfied:\n", + "\n", + " C4 - C6 = C7\n", + " C1 * C2 * C3 = C8 + C9\n", + " C2 + C3 + C6 < C8\n", + " C9 < C8\n", + "\n", + " and\n", + "\n", + " C1 <> 1, C2 <> 2, ..., C9 <> 9\n", + "\n", + " can you find the correct combination?\n", + " '''\n", + "\n", + " Compare with the following models:\n", + " * MiniZinc: http://www.hakank.org/minizinc/safe_cracking.mzn\n", + " * ECLiPSe : http://www.hakank.org/eclipse/safe_cracking.ecl\n", + " * SICStus : http://www.hakank.org/sicstus/safe_cracking.pl\n", + " * Gecode: http://hakank.org/gecode/safe_cracking.cpp\n", + "\n", + " This model was created by Hakan Kjellerstrand (hakank@gmail.com)\n", + " Also see my other Google CP Solver models:\n", + " http://www.hakank.org/google_or_tools/\n", + "\"\"\"\n", + "from __future__ import print_function\n", + "from ortools.constraint_solver import pywrapcp\n", + "\n", + "\n", + "\n", + "# Create the solver.\n", + "solver = pywrapcp.Solver('Safe cracking puzzle')\n", + "\n", + "#\n", + "# data\n", + "#\n", + "n = 9\n", + "digits = list(range(1, n + 1))\n", + "\n", + "#\n", + "# variables\n", + "#\n", + "\n", + "LD = [solver.IntVar(digits, 'LD[%i]' % i) for i in range(n)]\n", + "C1, C2, C3, C4, C5, C6, C7, C8, C9 = LD\n", + "\n", + "#\n", + "# constraints\n", + "#\n", + "solver.Add(solver.AllDifferent(LD))\n", + "\n", + "solver.Add(C4 - C6 == C7)\n", + "solver.Add(C1 * C2 * C3 == C8 + C9)\n", + "solver.Add(C2 + C3 + C6 < C8)\n", + "solver.Add(C9 < C8)\n", + "for i in range(n):\n", + " solver.Add(LD[i] != i + 1)\n", + "\n", + "#\n", + "# search and result\n", + "#\n", + "db = solver.Phase(LD, solver.INT_VAR_DEFAULT, solver.INT_VALUE_DEFAULT)\n", + "\n", + "solver.NewSearch(db)\n", + "\n", + "num_solutions = 0\n", + "\n", + "while solver.NextSolution():\n", + " num_solutions += 1\n", + " print('LD:', [LD[i].Value() for i in range(n)])\n", + "\n", + "solver.EndSearch()\n", + "\n", + "print()\n", + "print('num_solutions:', num_solutions)\n", + "print('failures:', solver.Failures())\n", + "print('branches:', solver.Branches())\n", + "print('WallTime:', solver.WallTime(), 'ms')\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/contrib/scheduling_speakers.ipynb b/examples/notebook/contrib/scheduling_speakers.ipynb new file mode 100644 index 0000000000..47b7da6374 --- /dev/null +++ b/examples/notebook/contrib/scheduling_speakers.ipynb @@ -0,0 +1,103 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010 Hakan Kjellerstrand hakank@gmail.com\n", + "#\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"\n", + "\n", + " Scheduling speakers problem in Google CP Solver.\n", + "\n", + " From Rina Dechter, Constraint Processing, page 72\n", + " Scheduling of 6 speakers in 6 slots.\n", + "\n", + " Compare with the following models:\n", + " * MiniZinc: http://www.hakank.org/minizinc/scheduling_speakers.mzn\n", + " * SICStus Prolog: http://www.hakank.org/sicstus/scheduling_speakers.pl\n", + " * ECLiPSe: http://hakank.org/eclipse/scheduling_speakers.ecl\n", + " * Gecode: http://hakank.org/gecode/scheduling_speakers.cpp\n", + "\n", + " This model was created by Hakan Kjellerstrand (hakank@gmail.com)\n", + " Also see my other Google CP Solver models:\n", + " http://www.hakank.org/google_or_tools/\n", + "\"\"\"\n", + "from __future__ import print_function\n", + "from ortools.constraint_solver import pywrapcp\n", + "\n", + "\n", + "\n", + "# Create the solver.\n", + "solver = pywrapcp.Solver('Scheduling speakers')\n", + "\n", + "#\n", + "# data\n", + "#\n", + "n = 6 # number of speakers\n", + "\n", + "# slots available to speak\n", + "available = [\n", + " # Reasoning:\n", + " [3, 4, 5, 6], # 2) the only one with 6 after speaker F -> 1\n", + " [3, 4], # 5) 3 or 4\n", + " [2, 3, 4, 5], # 3) only with 5 after F -> 1 and A -> 6\n", + " [2, 3, 4], # 4) only with 2 after C -> 5 and F -> 1\n", + " [3, 4], # 5) 3 or 4\n", + " [1, 2, 3, 4, 5, 6] # 1) the only with 1\n", + "]\n", + "\n", + "#\n", + "# variables\n", + "#\n", + "x = [solver.IntVar(1, n, 'x[%i]' % i) for i in range(n)]\n", + "\n", + "#\n", + "# constraints\n", + "#\n", + "solver.Add(solver.AllDifferent(x))\n", + "\n", + "for i in range(n):\n", + " solver.Add(solver.MemberCt(x[i], available[i]))\n", + "\n", + "#\n", + "# search and result\n", + "#\n", + "db = solver.Phase(x, solver.INT_VAR_DEFAULT, solver.INT_VALUE_DEFAULT)\n", + "\n", + "solver.NewSearch(db)\n", + "\n", + "num_solutions = 0\n", + "\n", + "while solver.NextSolution():\n", + " num_solutions += 1\n", + " print('x:', [x[i].Value() for i in range(n)])\n", + "\n", + "solver.EndSearch()\n", + "\n", + "print()\n", + "print('num_solutions:', num_solutions)\n", + "print('failures:', solver.Failures())\n", + "print('branches:', solver.Branches())\n", + "print('WallTime:', solver.WallTime(), 'ms')\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/contrib/scheduling_with_transitions_sat.ipynb b/examples/notebook/contrib/scheduling_with_transitions_sat.ipynb new file mode 100644 index 0000000000..21984ff53f --- /dev/null +++ b/examples/notebook/contrib/scheduling_with_transitions_sat.ipynb @@ -0,0 +1,347 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# -*- coding: utf-8 -*-\n", + "\"\"\"Scheduling problem with transition time between tasks and transitions costs.\n", + "\n", + "@author: CSLiu2\n", + "\"\"\"\n", + "\n", + "from __future__ import print_function\n", + "from __future__ import absolute_import\n", + "from __future__ import division\n", + "\n", + "import argparse\n", + "import collections\n", + "\n", + "from ortools.sat.python import cp_model\n", + "from google.protobuf import text_format\n", + "\n", + "#----------------------------------------------------------------------------\n", + "# Command line arguments.\n", + "PARSER = argparse.ArgumentParser()\n", + "PARSER.add_argument(\n", + " '--problem_instance', default=0, type=int, help='Problem instance.')\n", + "PARSER.add_argument(\n", + " '--output_proto',\n", + " default='',\n", + " help='Output file to write the cp_model proto to.')\n", + "PARSER.add_argument('--params', default='', help='Sat solver parameters.')\n", + "\n", + "\n", + "#----------------------------------------------------------------------------\n", + "# Intermediate solution printer\n", + "class SolutionPrinter(cp_model.CpSolverSolutionCallback):\n", + " \"\"\"Print intermediate solutions.\"\"\"\n", + "\n", + " def __init__(self, makespan):\n", + " cp_model.CpSolverSolutionCallback.__init__(self)\n", + " self.__solution_count = 0\n", + " self.__makespan = makespan\n", + "\n", + " def OnSolutionCallback(self):\n", + " print('Solution %i, time = %f s, objective = %i, makespan = %i' %\n", + " (self.__solution_count, self.WallTime(), self.ObjectiveValue(),\n", + " self.Value(self.__makespan)))\n", + " self.__solution_count += 1\n", + "\n", + "\n", + "\"\"\"Solves the scheduling with transitions problem.\"\"\"\n", + "\n", + "instance = args.problem_instance\n", + "parameters = args.params\n", + "output_proto = args.output_proto\n", + "\n", + "#----------------------------------------------------------------------------\n", + "# Data.\n", + "small_jobs = [[[(100, 0, 'R6'), (2, 1, 'R6')]],\n", + " [[(2, 0, 'R3'), (100, 1, 'R3')]],\n", + " [[(100, 0, 'R1'), (16, 1, 'R1')]],\n", + " [[(1, 0, 'R1'), (38, 1, 'R1')]], [[(14, 0, 'R1'), (10, 1,\n", + " 'R1')]],\n", + " [[(16, 0, 'R3'), (17, 1, 'R3')]],\n", + " [[(14, 0, 'R3'), (14, 1, 'R3')]],\n", + " [[(14, 0, 'R3'), (15, 1, 'R3')]],\n", + " [[(14, 0, 'R3'), (13, 1, 'R3')]],\n", + " [[(100, 0, 'R1'), (38, 1, 'R1')]]]\n", + "\n", + "large_jobs = [\n", + " [[(-1, 0, 'R1'), (10, 1, 'R1')]], [[(9, 0, 'R3'),\n", + " (22, 1, 'R3')]],\n", + " [[(-1, 0, 'R3'), (13, 1, 'R3')]], [[(-1, 0, 'R3'), (38, 1, 'R3')]],\n", + " [[(-1, 0, 'R3'), (38, 1, 'R3')]], [[(-1, 0, 'R3'), (16, 1, 'R3')]],\n", + " [[(-1, 0, 'R3'), (11, 1, 'R3')]], [[(-1, 0, 'R3'), (13, 1, 'R3')]],\n", + " [[(13, 0, 'R3'), (-1, 1, 'R3')]], [[(13, 0, 'R3'), (-1, 1, 'R3')]],\n", + " [[(-1, 0, 'R3'), (15, 1, 'R3')]], [[(12, 0, 'R1'), (-1, 1, 'R1')]],\n", + " [[(14, 0, 'R1'), (-1, 1, 'R1')]], [[(13, 0, 'R3'), (-1, 1, 'R3')]],\n", + " [[(-1, 0, 'R3'), (15, 1, 'R3')]], [[(14, 0, 'R1'), (-1, 1, 'R1')]],\n", + " [[(13, 0, 'R3'), (-1, 1, 'R3')]], [[(14, 0, 'R3'), (-1, 1, 'R3')]],\n", + " [[(13, 0, 'R1'), (-1, 1, 'R1')]], [[(15, 0, 'R1'), (-1, 1, 'R1')]],\n", + " [[(-1, 0, 'R2'), (16, 1, 'R2')]], [[(-1, 0, 'R2'), (16, 1, 'R2')]],\n", + " [[(-1, 0, 'R5'), (27, 1, 'R5')]], [[(-1, 0, 'R5'), (13, 1, 'R5')]],\n", + " [[(-1, 0, 'R5'), (13, 1, 'R5')]], [[(-1, 0, 'R5'), (13, 1, 'R5')]],\n", + " [[(13, 0, 'R1'), (-1, 1, 'R1')]], [[(-1, 0, 'R1'), (17, 1, 'R1')]],\n", + " [[(14, 0, 'R4'), (-1, 1, 'R4')]], [[(13, 0, 'R1'), (-1, 1, 'R1')]],\n", + " [[(16, 0, 'R4'), (-1, 1, 'R4')]], [[(16, 0, 'R4'), (-1, 1, 'R4')]],\n", + " [[(16, 0, 'R4'), (-1, 1, 'R4')]], [[(16, 0, 'R4'), (-1, 1, 'R4')]],\n", + " [[(13, 0, 'R1'), (-1, 1, 'R1')]], [[(13, 0, 'R1'), (-1, 1, 'R1')]],\n", + " [[(14, 0, 'R4'), (-1, 1, 'R4')]], [[(13, 0, 'R1'), (-1, 1, 'R1')]],\n", + " [[(12, 0, 'R1'), (-1, 1, 'R1')]], [[(14, 0, 'R4'), (-1, 1, 'R4')]],\n", + " [[(-1, 0, 'R5'), (14, 1, 'R5')]], [[(14, 0, 'R4'), (-1, 1, 'R4')]],\n", + " [[(14, 0, 'R4'), (-1, 1, 'R4')]], [[(14, 0, 'R4'), (-1, 1, 'R4')]],\n", + " [[(14, 0, 'R4'), (-1, 1, 'R4')]], [[(-1, 0, 'R1'), (21, 1, 'R1')]],\n", + " [[(-1, 0, 'R1'), (21, 1, 'R1')]], [[(-1, 0, 'R1'), (21, 1, 'R1')]],\n", + " [[(13, 0, 'R6'), (-1, 1, 'R6')]], [[(13, 0, 'R2'), (-1, 1, 'R2')]],\n", + " [[(-1, 0, 'R6'), (12, 1, 'R6')]], [[(13, 0, 'R1'), (-1, 1, 'R1')]],\n", + " [[(13, 0, 'R1'), (-1, 1, 'R1')]], [[(-1, 0, 'R6'), (14, 1, 'R6')]],\n", + " [[(-1, 0, 'R5'), (5, 1, 'R5')]], [[(3, 0, 'R2'), (-1, 1, 'R2')]],\n", + " [[(-1, 0, 'R1'), (21, 1, 'R1')]], [[(-1, 0, 'R1'), (21, 1, 'R1')]],\n", + " [[(-1, 0, 'R1'), (21, 1, 'R1')]], [[(-1, 0, 'R5'), (1, 1, 'R5')]],\n", + " [[(1, 0, 'R2'), (-1, 1, 'R2')]], [[(-1, 0, 'R2'), (19, 1, 'R2')]],\n", + " [[(13, 0, 'R4'), (-1, 1, 'R4')]], [[(12, 0, 'R4'), (-1, 1, 'R4')]],\n", + " [[(-1, 0, 'R3'), (2, 1, 'R3')]], [[(11, 0, 'R4'), (-1, 1, 'R4')]],\n", + " [[(6, 0, 'R6'), (-1, 1, 'R6')]], [[(6, 0, 'R6'), (-1, 1, 'R6')]],\n", + " [[(1, 0, 'R2'), (-1, 1, 'R2')]], [[(12, 0, 'R4'), (-1, 1, 'R4')]]\n", + "]\n", + "\n", + "jobs = small_jobs if instance == 0 else large_jobs\n", + "\n", + "#----------------------------------------------------------------------------\n", + "# Helper data.\n", + "num_jobs = len(jobs)\n", + "all_jobs = range(num_jobs)\n", + "num_machines = 2\n", + "all_machines = range(num_machines)\n", + "\n", + "#----------------------------------------------------------------------------\n", + "# Model.\n", + "model = cp_model.CpModel()\n", + "\n", + "#----------------------------------------------------------------------------\n", + "# Compute a maximum makespan greedily.\n", + "horizon = 0\n", + "for job in jobs:\n", + " for task in job:\n", + " max_task_duration = 0\n", + " for alternative in task:\n", + " max_task_duration = max(max_task_duration, alternative[0])\n", + " horizon += max_task_duration\n", + "\n", + "print('Horizon = %i' % horizon)\n", + "\n", + "#----------------------------------------------------------------------------\n", + "# Global storage of variables.\n", + "intervals_per_machines = collections.defaultdict(list)\n", + "presences_per_machines = collections.defaultdict(list)\n", + "starts_per_machines = collections.defaultdict(list)\n", + "ends_per_machines = collections.defaultdict(list)\n", + "resources_per_machines = collections.defaultdict(list)\n", + "ranks_per_machines = collections.defaultdict(list)\n", + "\n", + "job_starts = {} # indexed by (job_id, task_id).\n", + "job_presences = {} # indexed by (job_id, task_id, alt_id).\n", + "job_ranks = {} # indexed by (job_id, task_id, alt_id).\n", + "job_ends = [] # indexed by job_id\n", + "\n", + "#----------------------------------------------------------------------------\n", + "# Scan the jobs and create the relevant variables and intervals.\n", + "for job_id in all_jobs:\n", + " job = jobs[job_id]\n", + " num_tasks = len(job)\n", + " previous_end = None\n", + " for task_id in range(num_tasks):\n", + " task = job[task_id]\n", + "\n", + " min_duration = task[0][0]\n", + " max_duration = task[0][0]\n", + "\n", + " num_alternatives = len(task)\n", + " all_alternatives = range(num_alternatives)\n", + "\n", + " for alt_id in range(1, num_alternatives):\n", + " alt_duration = task[alt_id][0]\n", + " min_duration = min(min_duration, alt_duration)\n", + " max_duration = max(max_duration, alt_duration)\n", + "\n", + " # Create main interval for the task.\n", + " suffix_name = '_j%i_t%i' % (job_id, task_id)\n", + " start = model.NewIntVar(0, horizon, 'start' + suffix_name)\n", + " duration = model.NewIntVar(min_duration, max_duration,\n", + " 'duration' + suffix_name)\n", + " end = model.NewIntVar(0, horizon, 'end' + suffix_name)\n", + "\n", + " # Store the start for the solution.\n", + " job_starts[(job_id, task_id)] = start\n", + "\n", + " # Add precedence with previous task in the same job.\n", + " if previous_end:\n", + " model.Add(start >= previous_end)\n", + " previous_end = end\n", + "\n", + " # Create alternative intervals.\n", + " l_presences = []\n", + " for alt_id in all_alternatives:\n", + " ### add to link interval with eqp constraint.\n", + " ### process time = -1 cannot be processed at this machine.\n", + " if jobs[job_id][task_id][alt_id][0] == -1:\n", + " continue\n", + " alt_suffix = '_j%i_t%i_a%i' % (job_id, task_id, alt_id)\n", + " l_presence = model.NewBoolVar('presence' + alt_suffix)\n", + " l_start = model.NewIntVar(0, horizon, 'start' + alt_suffix)\n", + " l_duration = task[alt_id][0]\n", + " l_end = model.NewIntVar(0, horizon, 'end' + alt_suffix)\n", + " l_interval = model.NewOptionalIntervalVar(\n", + " l_start, l_duration, l_end, l_presence, 'interval' + alt_suffix)\n", + " l_rank = model.NewIntVar(-1, num_jobs, 'rank' + alt_suffix)\n", + " l_presences.append(l_presence)\n", + " l_machine = task[alt_id][1]\n", + " l_type = task[alt_id][2]\n", + "\n", + " # Link the master variables with the local ones.\n", + " model.Add(start == l_start).OnlyEnforceIf(l_presence)\n", + " model.Add(duration == l_duration).OnlyEnforceIf(l_presence)\n", + " model.Add(end == l_end).OnlyEnforceIf(l_presence)\n", + "\n", + " # Add the local variables to the right machine.\n", + " intervals_per_machines[l_machine].append(l_interval)\n", + " starts_per_machines[l_machine].append(l_start)\n", + " ends_per_machines[l_machine].append(l_end)\n", + " presences_per_machines[l_machine].append(l_presence)\n", + " resources_per_machines[l_machine].append(l_type)\n", + " ranks_per_machines[l_machine].append(l_rank)\n", + "\n", + " # Store the variables for the solution.\n", + " job_presences[(job_id, task_id, alt_id)] = l_presence\n", + " job_ranks[(job_id, task_id, alt_id)] = l_rank\n", + "\n", + " # Only one machine can process each lot.\n", + " model.Add(sum(l_presences) == 1)\n", + "\n", + " job_ends.append(previous_end)\n", + "\n", + "#----------------------------------------------------------------------------\n", + "# Create machines constraints nonoverlap process\n", + "for machine_id in all_machines:\n", + " intervals = intervals_per_machines[machine_id]\n", + " if len(intervals) > 1:\n", + " model.AddNoOverlap(intervals)\n", + "\n", + "#----------------------------------------------------------------------------\n", + "# Transition times and transition costs using a circuit constraint.\n", + "switch_literals = []\n", + "for machine_id in all_machines:\n", + " machine_starts = starts_per_machines[machine_id]\n", + " machine_ends = ends_per_machines[machine_id]\n", + " machine_presences = presences_per_machines[machine_id]\n", + " machine_resources = resources_per_machines[machine_id]\n", + " machine_ranks = ranks_per_machines[machine_id]\n", + " intervals = intervals_per_machines[machine_id]\n", + " arcs = []\n", + " num_machine_tasks = len(machine_starts)\n", + " all_machine_tasks = range(num_machine_tasks)\n", + "\n", + " for i in all_machine_tasks:\n", + " # Initial arc from the dummy node (0) to a task.\n", + " start_lit = model.NewBoolVar('')\n", + " arcs.append([0, i + 1, start_lit])\n", + " # If this task is the first, set both rank and start to 0.\n", + " model.Add(machine_ranks[i] == 0).OnlyEnforceIf(start_lit)\n", + " model.Add(machine_starts[i] == 0).OnlyEnforceIf(start_lit)\n", + " # Final arc from an arc to the dummy node.\n", + " arcs.append([i + 1, 0, model.NewBoolVar('')])\n", + " # Self arc if the task is not performed.\n", + " arcs.append([i + 1, i + 1, machine_presences[i].Not()])\n", + " model.Add(machine_ranks[i] == -1).OnlyEnforceIf(\n", + " machine_presences[i].Not())\n", + "\n", + " for j in all_machine_tasks:\n", + " if i == j:\n", + " continue\n", + "\n", + " lit = model.NewBoolVar('%i follows %i' % (j, i))\n", + " arcs.append([i + 1, j + 1, lit])\n", + " model.AddImplication(lit, machine_presences[i])\n", + " model.AddImplication(lit, machine_presences[j])\n", + "\n", + " # Maintain rank incrementally.\n", + " model.Add(machine_ranks[j] == machine_ranks[i] + 1).OnlyEnforceIf(lit)\n", + "\n", + " # Compute the transition time if task j is the successor of task i.\n", + " if machine_resources[i] != machine_resources[j]:\n", + " transition_time = 3\n", + " switch_literals.append(lit)\n", + " else:\n", + " transition_time = 0\n", + " # We add the reified transition to link the literals with the times\n", + " # of the tasks.\n", + " model.Add(machine_starts[j] == machine_ends[i] +\n", + " transition_time).OnlyEnforceIf(lit)\n", + "\n", + " model.AddCircuit(arcs)\n", + "\n", + "#----------------------------------------------------------------------------\n", + "# Objective.\n", + "makespan = model.NewIntVar(0, horizon, 'makespan')\n", + "model.AddMaxEquality(makespan, job_ends)\n", + "makespan_weight = 1\n", + "transition_weight = 5\n", + "model.Minimize(makespan * makespan_weight +\n", + " sum(switch_literals) * transition_weight)\n", + "\n", + "#----------------------------------------------------------------------------\n", + "# Write problem to file.\n", + "if output_proto:\n", + " print('Writing proto to %s' % output_proto)\n", + " with open(output_proto, 'w') as text_file:\n", + " text_file.write(str(model))\n", + "\n", + "#----------------------------------------------------------------------------\n", + "# Solve.\n", + "solver = cp_model.CpSolver()\n", + "solver.parameters.max_time_in_seconds = 60 * 60 * 2\n", + "if parameters:\n", + " text_format.Merge(parameters, solver.parameters)\n", + "solution_printer = SolutionPrinter(makespan)\n", + "status = solver.SolveWithSolutionCallback(model, solution_printer)\n", + "\n", + "#----------------------------------------------------------------------------\n", + "# Print solution.\n", + "if status == cp_model.FEASIBLE or status == cp_model.OPTIMAL:\n", + " for job_id in all_jobs:\n", + " for task_id in range(len(jobs[job_id])):\n", + " start_value = solver.Value(job_starts[(job_id, task_id)])\n", + " machine = 0\n", + " duration = 0\n", + " select = 0\n", + " rank = -1\n", + "\n", + " for alt_id in range(len(jobs[job_id][task_id])):\n", + " if jobs[job_id][task_id][alt_id][0] == -1:\n", + " continue\n", + "\n", + " if solver.BooleanValue(job_presences[(job_id, task_id, alt_id)]):\n", + " duration = jobs[job_id][task_id][alt_id][0]\n", + " machine = jobs[job_id][task_id][alt_id][1]\n", + " select = alt_id\n", + " rank = solver.Value(job_ranks[(job_id, task_id, alt_id)])\n", + "\n", + " print(\n", + " ' Job %i starts at %i (alt %i, duration %i) with rank %i on machine %i'\n", + " % (job_id, start_value, select, duration, rank, machine))\n", + "\n", + " print('Solve status: %s' % solver.StatusName(status))\n", + " print('Objective value: %i' % solver.ObjectiveValue())\n", + " print('Makespan: %i' % solver.Value(makespan))\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/school_scheduling_sat.ipynb b/examples/notebook/contrib/school_scheduling_sat.ipynb similarity index 97% rename from examples/notebook/school_scheduling_sat.ipynb rename to examples/notebook/contrib/school_scheduling_sat.ipynb index 179ee43e23..f8262b88e0 100644 --- a/examples/notebook/school_scheduling_sat.ipynb +++ b/examples/notebook/contrib/school_scheduling_sat.ipynb @@ -78,8 +78,8 @@ " for section in all_sections:\n", " course = level * self.num_sections + section\n", " for subject in all_subjects:\n", - " required_slots = self.problem.curriculum[self.problem.levels[\n", - " level], self.problem.subjects[subject]]\n", + " required_slots = self.problem.curriculum[\n", + " self.problem.levels[level], self.problem.subjects[subject]]\n", " self.model.Add(\n", " sum(self.assignment[course, subject, teacher, slot]\n", " for slot in all_slots\n", @@ -181,5 +181,5 @@ ], "metadata": {}, "nbformat": 4, - "nbformat_minor": 2 + "nbformat_minor": 4 } diff --git a/examples/notebook/contrib/secret_santa.ipynb b/examples/notebook/contrib/secret_santa.ipynb new file mode 100644 index 0000000000..4c1e8db3fa --- /dev/null +++ b/examples/notebook/contrib/secret_santa.ipynb @@ -0,0 +1,128 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010 Hakan Kjellerstrand hakank@gmail.com\n", + "#\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"\n", + "\n", + " Secret Santa problem in Google CP Solver.\n", + "\n", + " From Ruby Quiz Secret Santa\n", + " http://www.rubyquiz.com/quiz2.html\n", + " '''\n", + " Honoring a long standing tradition started by my wife's dad, my friends\n", + " all play a Secret Santa game around Christmas time. We draw names and\n", + " spend a week sneaking that person gifts and clues to our identity. On the\n", + " last night of the game, we get together, have dinner, share stories, and,\n", + " most importantly, try to guess who our Secret Santa was. It's a crazily\n", + " fun way to enjoy each other's company during the holidays.\n", + "\n", + " To choose Santas, we use to draw names out of a hat. This system was\n", + " tedious, prone to many 'Wait, I got myself...' problems. This year, we\n", + " made a change to the rules that further complicated picking and we knew\n", + " the hat draw would not stand up to the challenge. Naturally, to solve\n", + " this problem, I scripted the process. Since that turned out to be more\n", + " interesting than I had expected, I decided to share.\n", + "\n", + " This weeks Ruby Quiz is to implement a Secret Santa selection script.\n", + "\n", + " Your script will be fed a list of names on STDIN.\n", + " ...\n", + " Your script should then choose a Secret Santa for every name in the list.\n", + " Obviously, a person cannot be their own Secret Santa. In addition, my friends\n", + " no longer allow people in the same family to be Santas for each other and your\n", + " script should take this into account.\n", + " '''\n", + "\n", + " Comment: This model skips the file input and mail parts. We\n", + " assume that the friends are identified with a number from 1..n,\n", + " and the families is identified with a number 1..num_families.\n", + "\n", + " Compare with the following model:\n", + " * MiniZinc: http://www.hakank.org/minizinc/secret_santa.mzn\n", + "\n", + "\n", + " This model gives 4089600 solutions and the following statistics:\n", + " - failures: 31264\n", + " - branches: 8241726\n", + " - WallTime: 23735 ms (note: without any printing of the solutions)\n", + "\n", + " This model was created by Hakan Kjellerstrand (hakank@gmail.com)\n", + " Also see my other Google CP Solver models:\n", + " http://www.hakank.org/google_or_tools/\n", + "\"\"\"\n", + "from __future__ import print_function\n", + "import sys\n", + "from ortools.constraint_solver import pywrapcp\n", + "\n", + "\n", + "\n", + "# Create the solver.\n", + "solver = pywrapcp.Solver('Secret Santa problem')\n", + "\n", + "#\n", + "# data\n", + "#\n", + "family = [1, 1, 1, 1, 2, 3, 3, 3, 3, 3, 4, 4]\n", + "num_families = max(family)\n", + "n = len(family)\n", + "\n", + "#\n", + "# declare variables\n", + "#\n", + "x = [solver.IntVar(0, n - 1, 'x[%i]' % i) for i in range(n)]\n", + "\n", + "#\n", + "# constraints\n", + "#\n", + "solver.Add(solver.AllDifferent(x))\n", + "\n", + "# Can't be one own's Secret Santa\n", + "# Ensure that there are no fix-point in the array\n", + "for i in range(n):\n", + " solver.Add(x[i] != i)\n", + "\n", + "# No Secret Santa to a person in the same family\n", + "for i in range(n):\n", + " solver.Add(family[i] != solver.Element(family, x[i]))\n", + "\n", + "#\n", + "# solution and search\n", + "#\n", + "db = solver.Phase(x, solver.INT_VAR_SIMPLE, solver.INT_VALUE_SIMPLE)\n", + "\n", + "solver.NewSearch(db)\n", + "num_solutions = 0\n", + "while solver.NextSolution():\n", + " num_solutions += 1\n", + " print('x:', [x[i].Value() for i in range(n)])\n", + " print()\n", + "\n", + "print('num_solutions:', num_solutions)\n", + "print('failures:', solver.Failures())\n", + "print('branches:', solver.Branches())\n", + "print('WallTime:', solver.WallTime(), 'ms')\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/contrib/secret_santa2.ipynb b/examples/notebook/contrib/secret_santa2.ipynb new file mode 100644 index 0000000000..a1b9ecaf94 --- /dev/null +++ b/examples/notebook/contrib/secret_santa2.ipynb @@ -0,0 +1,227 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010 Hakan Kjellerstrand hakank@gmail.com\n", + "#\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"\n", + "\n", + " Secret Santa problem II in Google CP Solver.\n", + "\n", + " From Maple Primes: 'Secret Santa Graph Theory'\n", + " http://www.mapleprimes.com/blog/jpmay/secretsantagraphtheory\n", + " '''\n", + " Every year my extended family does a 'secret santa' gift exchange.\n", + " Each person draws another person at random and then gets a gift for\n", + " them. At first, none of my siblings were married, and so the draw was\n", + " completely random. Then, as people got married, we added the restriction\n", + " that spouses should not draw each others names. This restriction meant\n", + " that we moved from using slips of paper on a hat to using a simple\n", + " computer program to choose names. Then people began to complain when\n", + " they would get the same person two years in a row, so the program was\n", + " modified to keep some history and avoid giving anyone a name in their\n", + " recent history. This year, not everyone was participating, and so after\n", + " removing names, and limiting the number of exclusions to four per person,\n", + " I had data something like this:\n", + "\n", + " Name: Spouse, Recent Picks\n", + "\n", + " Noah: Ava. Ella, Evan, Ryan, John\n", + " Ava: Noah, Evan, Mia, John, Ryan\n", + " Ryan: Mia, Ella, Ava, Lily, Evan\n", + " Mia: Ryan, Ava, Ella, Lily, Evan\n", + " Ella: John, Lily, Evan, Mia, Ava\n", + " John: Ella, Noah, Lily, Ryan, Ava\n", + " Lily: Evan, John, Mia, Ava, Ella\n", + " Evan: Lily, Mia, John, Ryan, Noah\n", + " '''\n", + "\n", + " Note: I interpret this as the following three constraints:\n", + " 1) One cannot be a Secret Santa of one's spouse\n", + " 2) One cannot be a Secret Santa for somebody two years in a row\n", + " 3) Optimization: maximize the time since the last time\n", + "\n", + " This model also handle single persons, something the original\n", + " problem don't mention.\n", + "\n", + " Compare with the following models:\n", + " * Google CP Solver: http://www.hakank.org/google_or_tools/secret_santa.py\n", + " * MiniZinc: http://www.hakank.org/minizinc/secret_santa2.mzn\n", + "\n", + "\n", + " This model was created by Hakan Kjellerstrand (hakank@gmail.com)\n", + " Also see my other Google CP Solver models:\n", + " http://www.hakank.org/google_or_tools/\n", + "\"\"\"\n", + "from __future__ import print_function\n", + "import sys\n", + "from ortools.constraint_solver import pywrapcp\n", + "\n", + "\n", + "\n", + "# Create the solver.\n", + "solver = pywrapcp.Solver('Secret Santa problem II')\n", + "\n", + "#\n", + "# data\n", + "#\n", + "\n", + "#\n", + "# The matrix version of earlier rounds.\n", + "# M means that no earlier Santa has been assigned.\n", + "# Note: Ryan and Mia has the same recipient for years 3 and 4,\n", + "# and Ella and John has for year 4.\n", + "# This seems to be caused by modification of\n", + "# original data.\n", + "#\n", + "n_no_single = 8\n", + "M = n_no_single + 1\n", + "rounds_no_single = [\n", + " # N A R M El J L Ev\n", + " [0, M, 3, M, 1, 4, M, 2], # Noah\n", + " [M, 0, 4, 2, M, 3, M, 1], # Ava\n", + " [M, 2, 0, M, 1, M, 3, 4], # Ryan\n", + " [M, 1, M, 0, 2, M, 3, 4], # Mia\n", + " [M, 4, M, 3, 0, M, 1, 2], # Ella\n", + " [1, 4, 3, M, M, 0, 2, M], # John\n", + " [M, 3, M, 2, 4, 1, 0, M], # Lily\n", + " [4, M, 3, 1, M, 2, M, 0] # Evan\n", + "]\n", + "\n", + "#\n", + "# Rounds with a single person (fake data)\n", + "#\n", + "n_with_single = 9\n", + "M = n_with_single + 1\n", + "rounds_single = [\n", + " # N A R M El J L Ev S\n", + " [0, M, 3, M, 1, 4, M, 2, 2], # Noah\n", + " [M, 0, 4, 2, M, 3, M, 1, 1], # Ava\n", + " [M, 2, 0, M, 1, M, 3, 4, 4], # Ryan\n", + " [M, 1, M, 0, 2, M, 3, 4, 3], # Mia\n", + " [M, 4, M, 3, 0, M, 1, 2, M], # Ella\n", + " [1, 4, 3, M, M, 0, 2, M, M], # John\n", + " [M, 3, M, 2, 4, 1, 0, M, M], # Lily\n", + " [4, M, 3, 1, M, 2, M, 0, M], # Evan\n", + " [1, 2, 3, 4, M, 2, M, M, 0] # Single\n", + "]\n", + "\n", + "if single == 1:\n", + " n = n_with_single\n", + " Noah, Ava, Ryan, Mia, Ella, John, Lily, Evan, Single = list(range(n))\n", + " rounds = rounds_single\n", + "else:\n", + " n = n_no_single\n", + " Noah, Ava, Ryan, Mia, Ella, John, Lily, Evan = list(range(n))\n", + " rounds = rounds_no_single\n", + "\n", + "M = n + 1\n", + "\n", + "persons = [\n", + " 'Noah', 'Ava', 'Ryan', 'Mia', 'Ella', 'John', 'Lily', 'Evan', 'Single'\n", + "]\n", + "\n", + "spouses = [\n", + " Ava, # Noah\n", + " Noah, # Ava\n", + " Mia, # Rya\n", + " Ryan, # Mia\n", + " John, # Ella\n", + " Ella, # John\n", + " Evan, # Lily\n", + " Lily, # Evan\n", + " -1 # Single has no spouse\n", + "]\n", + "\n", + "#\n", + "# declare variables\n", + "#\n", + "santas = [solver.IntVar(0, n - 1, 'santas[%i]' % i) for i in range(n)]\n", + "santa_distance = [\n", + " solver.IntVar(0, M, 'santa_distance[%i]' % i) for i in range(n)\n", + "]\n", + "\n", + "# total of 'distance', to maximize\n", + "z = solver.IntVar(0, n * n * n, 'z')\n", + "\n", + "#\n", + "# constraints\n", + "#\n", + "solver.Add(solver.AllDifferent(santas))\n", + "\n", + "solver.Add(z == solver.Sum(santa_distance))\n", + "\n", + "# Can't be one own's Secret Santa\n", + "# (i.e. ensure that there are no fix-point in the array.)\n", + "for i in range(n):\n", + " solver.Add(santas[i] != i)\n", + "\n", + "# no Santa for a spouses\n", + "for i in range(n):\n", + " if spouses[i] > -1:\n", + " solver.Add(santas[i] != spouses[i])\n", + "\n", + "# optimize 'distance' to earlier rounds:\n", + "for i in range(n):\n", + " solver.Add(santa_distance[i] == solver.Element(rounds[i], santas[i]))\n", + "\n", + "# cannot be a Secret Santa for the same person\n", + "# two years in a row.\n", + "for i in range(n):\n", + " for j in range(n):\n", + " if rounds[i][j] == 1:\n", + " solver.Add(santas[i] != j)\n", + "\n", + "# objective\n", + "objective = solver.Maximize(z, 1)\n", + "\n", + "#\n", + "# solution and search\n", + "#\n", + "db = solver.Phase(santas, solver.CHOOSE_MIN_SIZE_LOWEST_MIN,\n", + " solver.ASSIGN_CENTER_VALUE)\n", + "\n", + "solver.NewSearch(db, [objective])\n", + "\n", + "num_solutions = 0\n", + "while solver.NextSolution():\n", + " num_solutions += 1\n", + " print('total distances:', z.Value())\n", + " print('santas:', [santas[i].Value() for i in range(n)])\n", + " for i in range(n):\n", + " print('%s\\tis a Santa to %s (distance %i)' % \\\n", + " (persons[i],\n", + " persons[santas[i].Value()],\n", + " santa_distance[i].Value()))\n", + " # print 'distance:', [santa_distance[i].Value()\n", + " # for i in range(n)]\n", + " print()\n", + "\n", + "print('num_solutions:', num_solutions)\n", + "print('failures:', solver.Failures())\n", + "print('branches:', solver.Branches())\n", + "print('WallTime:', solver.WallTime(), 'ms')\n", + "\n", + "single = 0\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/contrib/send_more_money_any_base.ipynb b/examples/notebook/contrib/send_more_money_any_base.ipynb new file mode 100644 index 0000000000..049d319396 --- /dev/null +++ b/examples/notebook/contrib/send_more_money_any_base.ipynb @@ -0,0 +1,117 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010 Hakan Kjellerstrand hakank@gmail.com\n", + "#\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"\n", + "\n", + " SEND+MORE=MONEY in 'any' base in Google CP Solver.\n", + "\n", + " Alphametic problem SEND+MORE=MONEY in any base.\n", + "\n", + " Examples:\n", + " Base 10 has one solution:\n", + " {9, 5, 6, 7, 1, 0, 8, 2}\n", + " Base 11 has three soltutions:\n", + " {10, 5, 6, 8, 1, 0, 9, 2}\n", + " {10, 6, 7, 8, 1, 0, 9, 3}\n", + " {10, 7, 8, 6, 1, 0, 9, 2}\n", + "\n", + " Also, compare with the following models:\n", + " * Comet : http://www.hakank.org/comet/send_more_money_any_base.co\n", + " * ECLiPSE : http://www.hakank.org/eclipse/send_more_money_any_base.ecl\n", + " * Essence : http://www.hakank.org/tailor/send_more_money_any_base.eprime\n", + " * Gecode : http://www.hakank.org/gecode/send_more_money_any_base.cpp\n", + " * Gecode/R: http://www.hakank.org/gecode_r/send_more_money_any_base.rb\n", + " * MiniZinc: http://www.hakank.org/minizinc/send_more_money_any_base.mzn\n", + " * Zinc: http://www.hakank.org/minizinc/send_more_money_any_base.zinc\n", + " * SICStus: http://www.hakank.org/sicstus/send_more_money_any_base.pl\n", + "\n", + "\n", + " This model was created by Hakan Kjellerstrand (hakank@gmail.com)\n", + " Also see my other Google CP Solver models:\n", + " http://www.hakank.org/google_or_tools/\n", + "\n", + "\"\"\"\n", + "from __future__ import print_function\n", + "import sys\n", + "from ortools.constraint_solver import pywrapcp\n", + "\n", + "\n", + "\n", + "# Create the solver.\n", + "solver = pywrapcp.Solver('Send most money')\n", + "\n", + "# data\n", + "print('base:', base)\n", + "\n", + "# declare variables\n", + "s = solver.IntVar(0, base - 1, 's')\n", + "e = solver.IntVar(0, base - 1, 'e')\n", + "n = solver.IntVar(0, base - 1, 'n')\n", + "d = solver.IntVar(0, base - 1, 'd')\n", + "m = solver.IntVar(0, base - 1, 'm')\n", + "o = solver.IntVar(0, base - 1, 'o')\n", + "r = solver.IntVar(0, base - 1, 'r')\n", + "y = solver.IntVar(0, base - 1, 'y')\n", + "\n", + "x = [s, e, n, d, m, o, r, y]\n", + "\n", + "#\n", + "# constraints\n", + "#\n", + "solver.Add(solver.AllDifferent(x))\n", + "solver.Add(\n", + " s * base**3 + e * base**2 + n * base + d + m * base**3 + o * base**2 +\n", + " r * base + e == m * base**4 + o * base**3 + n * base**2 + e * base + y,)\n", + "solver.Add(s > 0)\n", + "solver.Add(m > 0)\n", + "\n", + "#\n", + "# solution and search\n", + "#\n", + "solution = solver.Assignment()\n", + "solution.Add(x)\n", + "\n", + "collector = solver.AllSolutionCollector(solution)\n", + "\n", + "solver.Solve(\n", + " solver.Phase(x, solver.CHOOSE_FIRST_UNBOUND, solver.ASSIGN_MAX_VALUE),\n", + " [collector])\n", + "\n", + "num_solutions = collector.SolutionCount()\n", + "money_val = 0\n", + "for s in range(num_solutions):\n", + " print('x:', [collector.Value(s, x[i]) for i in range(len(x))])\n", + "\n", + "print()\n", + "print('num_solutions:', num_solutions)\n", + "print('failures:', solver.Failures())\n", + "print('branches:', solver.Branches())\n", + "print('WallTime:', solver.WallTime())\n", + "print()\n", + "\n", + "base = 10\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/contrib/send_most_money.ipynb b/examples/notebook/contrib/send_most_money.ipynb new file mode 100644 index 0000000000..26a1dda5f9 --- /dev/null +++ b/examples/notebook/contrib/send_most_money.ipynb @@ -0,0 +1,126 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010 Hakan Kjellerstrand hakank@gmail.com\n", + "#\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"\n", + "\n", + " SEND+MOST=MONEY in Google CP Solver.\n", + "\n", + "\n", + " Alphametic problem were we maximize MONEY.\n", + "\n", + " Problem from the lecture notes:\n", + " http://www.ict.kth.se/courses/ID2204/notes/L01.pdf\n", + "\n", + " Compare with the following models:\n", + " * Comet : http://www.hakank.org/comet/send_most_money.co\n", + " * Comet : http://www.hakank.org/comet/send_most_money2.co\n", + " * ECLiPSE : http://www.hakank.org/eclipse/send_most_money.ecl\n", + " * SICStus: http://hakank.org/sicstus/send_most_money.pl\n", + " * MiniZinc: http://www.hakank.org/minizinc/send_most_money.mzn\n", + " * Gecode/R: http://www.hakank.org/gecode_r/send_most_money2.rb\n", + " * Tailor/Essence': http://www.hakank.org/tailor/send_most_money.eprime\n", + " * Zinc: http://www.hakank.org/minizinc/send_most_money.zinc\n", + "\n", + "\n", + " This model was created by Hakan Kjellerstrand (hakank@gmail.com)\n", + " Also see my other Google CP Solver models:\n", + " http://www.hakank.org/google_or_tools/\n", + "\n", + "\"\"\"\n", + "from __future__ import print_function\n", + "\n", + "from ortools.constraint_solver import pywrapcp\n", + "\n", + "\n", + "\n", + "# Create the solver.\n", + "solver = pywrapcp.Solver('Send most money')\n", + "\n", + "# data\n", + "\n", + "# declare variables\n", + "s = solver.IntVar(0, 9, 's')\n", + "e = solver.IntVar(0, 9, 'e')\n", + "n = solver.IntVar(0, 9, 'n')\n", + "d = solver.IntVar(0, 9, 'd')\n", + "m = solver.IntVar(0, 9, 'm')\n", + "o = solver.IntVar(0, 9, 'o')\n", + "t = solver.IntVar(0, 9, 't')\n", + "y = solver.IntVar(0, 9, 'y')\n", + "money = solver.IntVar(0, 100000, 'money')\n", + "\n", + "x = [s, e, n, d, m, o, t, y]\n", + "\n", + "#\n", + "# constraints\n", + "#\n", + "if MONEY > 0:\n", + " solver.Add(money == MONEY)\n", + "\n", + "solver.Add(solver.AllDifferent(x))\n", + "solver.Add(money == m * 10000 + o * 1000 + n * 100 + e * 10 + y)\n", + "solver.Add(money > 0)\n", + "solver.Add(1000 * s + 100 * e + 10 * n + d + 1000 * m + 100 * o + 10 * s +\n", + " t == money)\n", + "solver.Add(s > 0)\n", + "solver.Add(m > 0)\n", + "\n", + "#\n", + "# solution and search\n", + "#\n", + "solution = solver.Assignment()\n", + "solution.Add(x)\n", + "solution.Add(money)\n", + "\n", + "collector = solver.AllSolutionCollector(solution)\n", + "objective = solver.Maximize(money, 100)\n", + "cargs = [collector]\n", + "if MONEY == 0:\n", + " objective = solver.Maximize(money, 1)\n", + " cargs.extend([objective])\n", + "\n", + "solver.Solve(\n", + " solver.Phase(x, solver.CHOOSE_FIRST_UNBOUND, solver.ASSIGN_MAX_VALUE),\n", + " cargs)\n", + "\n", + "num_solutions = collector.SolutionCount()\n", + "money_val = 0\n", + "for s in range(num_solutions):\n", + " print('x:', [collector.Value(s, x[i]) for i in range(len(x))])\n", + " money_val = collector.Value(s, money)\n", + " print('money:', money_val)\n", + " print()\n", + "\n", + "print('num_solutions:', num_solutions)\n", + "print('failures:', solver.Failures())\n", + "print('branches:', solver.Branches())\n", + "print('WallTime:', solver.WallTime())\n", + "\n", + "if MONEY == 0:\n", + " return money_val\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/contrib/seseman.ipynb b/examples/notebook/contrib/seseman.ipynb new file mode 100644 index 0000000000..2c1632e078 --- /dev/null +++ b/examples/notebook/contrib/seseman.ipynb @@ -0,0 +1,147 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010 Hakan Kjellerstrand hakank@gmail.com\n", + "#\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"\n", + "\n", + " Seseman Convent problem in Google CP Solver.\n", + "\n", + "\n", + " n is the length of a border\n", + " There are (n-2)^2 \"holes\", i.e.\n", + " there are n^2 - (n-2)^2 variables to find out.\n", + "\n", + " The simplest problem, n = 3 (n x n matrix)\n", + " which is represented by the following matrix:\n", + "\n", + " a b c\n", + " d e\n", + " f g h\n", + "\n", + " Where the following constraints must hold:\n", + "\n", + " a + b + c = border_sum\n", + " a + d + f = border_sum\n", + " c + e + h = border_sum\n", + " f + g + h = border_sum\n", + " a + b + c + d + e + f = total_sum\n", + "\n", + "\n", + " Compare with the following models:\n", + " * Tailor/Essence': http://hakank.org/tailor/seseman.eprime\n", + " * MiniZinc: http://hakank.org/minizinc/seseman.mzn\n", + " * SICStus: http://hakank.org/sicstus/seseman.pl\n", + " * Zinc: http://hakank.org/minizinc/seseman.zinc\n", + " * Choco: http://hakank.org/choco/Seseman.java\n", + " * Comet: http://hakank.org/comet/seseman.co\n", + " * ECLiPSe: http://hakank.org/eclipse/seseman.ecl\n", + " * Gecode: http://hakank.org/gecode/seseman.cpp\n", + " * Gecode/R: http://hakank.org/gecode_r/seseman.rb\n", + " * JaCoP: http://hakank.org/JaCoP/Seseman.java\n", + "\n", + "\n", + " This model was created by Hakan Kjellerstrand (hakank@gmail.com)\n", + " Also see my other Google CP Solver models:\n", + " http://www.hakank.org/google_or_tools/\n", + "\"\"\"\n", + "from __future__ import print_function\n", + "from ortools.constraint_solver import pywrapcp\n", + "\n", + "\n", + "# Create the solver.\n", + "solver = pywrapcp.Solver(\"Seseman Convent problem\")\n", + "\n", + "# data\n", + "n = 3\n", + "border_sum = n * n\n", + "\n", + "# declare variables\n", + "total_sum = solver.IntVar(1, n * n * n * n, \"total_sum\")\n", + "# x[0..n-1,0..n-1]\n", + "x = {}\n", + "for i in range(n):\n", + " for j in range(n):\n", + " x[(i, j)] = solver.IntVar(0, n * n, \"x %i %i\" % (i, j))\n", + "\n", + "#\n", + "# constraints\n", + "#\n", + "# zero all middle cells\n", + "for i in range(1, n - 1):\n", + " for j in range(1, n - 1):\n", + " solver.Add(x[(i, j)] == 0)\n", + "\n", + "# all borders must be >= 1\n", + "for i in range(n):\n", + " for j in range(n):\n", + " if i == 0 or j == 0 or i == n - 1 or j == n - 1:\n", + " solver.Add(x[(i, j)] >= 1)\n", + "\n", + "# sum the borders (border_sum)\n", + "solver.Add(solver.Sum([x[(i, 0)] for i in range(n)]) == border_sum)\n", + "solver.Add(solver.Sum([x[(i, n - 1)] for i in range(n)]) == border_sum)\n", + "solver.Add(solver.Sum([x[(0, i)] for i in range(n)]) == border_sum)\n", + "solver.Add(solver.Sum([x[(n - 1, i)] for i in range(n)]) == border_sum)\n", + "\n", + "# total\n", + "solver.Add(\n", + " solver.Sum([x[(i, j)] for i in range(n) for j in range(n)]) == total_sum)\n", + "\n", + "#\n", + "# solution and search\n", + "#\n", + "solution = solver.Assignment()\n", + "solution.Add([x[(i, j)] for i in range(n) for j in range(n)])\n", + "solution.Add(total_sum)\n", + "\n", + "# all solutions\n", + "collector = solver.AllSolutionCollector(solution)\n", + "# search_log = solver.SearchLog(100, total_sum)\n", + "solver.Solve(\n", + " solver.Phase([x[(i, j)] for i in range(n) for j in range(n)],\n", + " solver.CHOOSE_PATH, solver.ASSIGN_MIN_VALUE), [collector])\n", + "#[collector, search_log])\n", + "\n", + "num_solutions = collector.SolutionCount()\n", + "# print \"x:\", x\n", + "print(\"num_solutions:\", num_solutions)\n", + "print()\n", + "for s in range(num_solutions):\n", + " # print [collector.Value(s, x[(i,j)])\n", + " # for i in range(n) for j in range(n)]\n", + " print(\"total_sum:\", collector.Value(s, total_sum))\n", + " for i in range(n):\n", + " for j in range(n):\n", + " print(collector.Value(s, x[(i, j)]), end=\" \")\n", + " print()\n", + " print()\n", + "\n", + "print(\"failures:\", solver.Failures())\n", + "print(\"branches:\", solver.Branches())\n", + "print(\"WallTime:\", solver.WallTime())\n", + "print(\"num_solutions:\", num_solutions)\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/contrib/seseman_b.ipynb b/examples/notebook/contrib/seseman_b.ipynb new file mode 100644 index 0000000000..e4cb763485 --- /dev/null +++ b/examples/notebook/contrib/seseman_b.ipynb @@ -0,0 +1,143 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010 Hakan Kjellerstrand hakank@gmail.com\n", + "#\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"\n", + "\n", + " Seseman Convent problem in Google CP Solver.\n", + "\n", + "\n", + " n is the length of a border\n", + " There are (n-2)^2 \"holes\", i.e.\n", + " there are n^2 - (n-2)^2 variables to find out.\n", + "\n", + " The simplest problem, n = 3 (n x n matrix)\n", + " which is represented by the following matrix:\n", + "\n", + " a b c\n", + " d e\n", + " f g h\n", + "\n", + " Where the following constraints must hold:\n", + "\n", + " a + b + c = border_sum\n", + " a + d + f = border_sum\n", + " c + e + h = border_sum\n", + " f + g + h = border_sum\n", + " a + b + c + d + e + f = total_sum\n", + "\n", + "\n", + " Compare with the following models:\n", + " * Tailor/Essence': http://hakank.org/tailor/seseman.eprime\n", + " * MiniZinc: http://hakank.org/minizinc/seseman.mzn\n", + " * SICStus: http://hakank.org/sicstus/seseman.pl\n", + " * Zinc: http://hakank.org/minizinc/seseman.zinc\n", + " * Choco: http://hakank.org/choco/Seseman.java\n", + " * Comet: http://hakank.org/comet/seseman.co\n", + " * ECLiPSe: http://hakank.org/eclipse/seseman.ecl\n", + " * Gecode: http://hakank.org/gecode/seseman.cpp\n", + " * Gecode/R: http://hakank.org/gecode_r/seseman.rb\n", + " * JaCoP: http://hakank.org/JaCoP/Seseman.java\n", + "\n", + " This version use a better way of looping through all solutions.\n", + "\n", + "\n", + " This model was created by Hakan Kjellerstrand (hakank@gmail.com)\n", + " Also see my other Google CP Solver models:\n", + " http://www.hakank.org/google_or_tools/\n", + "\"\"\"\n", + "from __future__ import print_function\n", + "from ortools.constraint_solver import pywrapcp\n", + "\n", + "\n", + "# Create the solver.\n", + "solver = pywrapcp.Solver(\"Seseman Convent problem\")\n", + "\n", + "# data\n", + "n = 3\n", + "border_sum = n * n\n", + "\n", + "# declare variables\n", + "total_sum = solver.IntVar(1, n * n * n * n, \"total_sum\")\n", + "# x[0..n-1,0..n-1]\n", + "x = {}\n", + "for i in range(n):\n", + " for j in range(n):\n", + " x[(i, j)] = solver.IntVar(0, n * n, \"x %i %i\" % (i, j))\n", + "\n", + "#\n", + "# constraints\n", + "#\n", + "# zero all middle cells\n", + "for i in range(1, n - 1):\n", + " for j in range(1, n - 1):\n", + " solver.Add(x[(i, j)] == 0)\n", + "\n", + "# all borders must be >= 1\n", + "for i in range(n):\n", + " for j in range(n):\n", + " if i == 0 or j == 0 or i == n - 1 or j == n - 1:\n", + " solver.Add(x[(i, j)] >= 1)\n", + "\n", + "# sum the borders (border_sum)\n", + "solver.Add(solver.Sum([x[(i, 0)] for i in range(n)]) == border_sum)\n", + "solver.Add(solver.Sum([x[(i, n - 1)] for i in range(n)]) == border_sum)\n", + "solver.Add(solver.Sum([x[(0, i)] for i in range(n)]) == border_sum)\n", + "solver.Add(solver.Sum([x[(n - 1, i)] for i in range(n)]) == border_sum)\n", + "\n", + "# total\n", + "solver.Add(\n", + " solver.Sum([x[(i, j)] for i in range(n) for j in range(n)]) == total_sum)\n", + "\n", + "#\n", + "# solution and search\n", + "#\n", + "solution = solver.Assignment()\n", + "solution.Add([x[(i, j)] for i in range(n) for j in range(n)])\n", + "solution.Add(total_sum)\n", + "\n", + "db = solver.Phase([x[(i, j)] for i in range(n) for j in range(n)],\n", + " solver.CHOOSE_PATH, solver.ASSIGN_MIN_VALUE)\n", + "\n", + "solver.NewSearch(db)\n", + "\n", + "num_solutions = 0\n", + "\n", + "while solver.NextSolution():\n", + " num_solutions += 1\n", + " print(\"total_sum:\", total_sum.Value())\n", + " for i in range(n):\n", + " for j in range(n):\n", + " print(x[(i, j)].Value(), end=\" \")\n", + " print()\n", + " print()\n", + "\n", + "print(\"num_solutions:\", num_solutions)\n", + "print(\"failures:\", solver.Failures())\n", + "print(\"branches:\", solver.Branches())\n", + "print(\"WallTime:\", solver.WallTime())\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/contrib/set_covering.ipynb b/examples/notebook/contrib/set_covering.ipynb new file mode 100644 index 0000000000..c662cf8ea6 --- /dev/null +++ b/examples/notebook/contrib/set_covering.ipynb @@ -0,0 +1,103 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010 Hakan Kjellerstrand hakank@gmail.com\n", + "#\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"\n", + "\n", + " Set covering in Google CP Solver.\n", + "\n", + " Placing of firestations, from Winston 'Operations Research', page 486.\n", + "\n", + " Compare with the following models:\n", + " * MiniZinc: http://www.hakank.org/minizinc/set_covering.mzn\n", + " * ECLiPSe : http://www.hakank.org/eclipse/set_covering.ecl\n", + " * Comet : http://www.hakank.org/comet/set_covering.co\n", + " * Gecode : http://www.hakank.org/gecode/set_covering.cpp\n", + " * SICStus : http://www.hakank.org/sicstus/set_covering.pl\n", + "\n", + "\n", + " This model was created by Hakan Kjellerstrand (hakank@gmail.com)\n", + " Also see my other Google CP Solver models:\n", + " http://www.hakank.org/google_or_tools/\n", + "\n", + "\"\"\"\n", + "from __future__ import print_function\n", + "from ortools.constraint_solver import pywrapcp\n", + "\n", + "\n", + "\n", + "# Create the solver.\n", + "solver = pywrapcp.Solver(\"Set covering\")\n", + "\n", + "#\n", + "# data\n", + "#\n", + "min_distance = 15\n", + "num_cities = 6\n", + "\n", + "distance = [[0, 10, 20, 30, 30, 20], [10, 0, 25, 35, 20, 10],\n", + " [20, 25, 0, 15, 30, 20], [30, 35, 15, 0, 15, 25],\n", + " [30, 20, 30, 15, 0, 14], [20, 10, 20, 25, 14, 0]]\n", + "\n", + "#\n", + "# declare variables\n", + "#\n", + "x = [solver.IntVar(0, 1, \"x[%i]\" % i) for i in range(num_cities)]\n", + "\n", + "#\n", + "# constraints\n", + "#\n", + "\n", + "# objective to minimize\n", + "z = solver.Sum(x)\n", + "\n", + "# ensure that all cities are covered\n", + "for i in range(num_cities):\n", + " b = [x[j] for j in range(num_cities) if distance[i][j] <= min_distance]\n", + " solver.Add(solver.SumGreaterOrEqual(b, 1))\n", + "\n", + "objective = solver.Minimize(z, 1)\n", + "\n", + "#\n", + "# solution and search\n", + "#\n", + "solution = solver.Assignment()\n", + "solution.Add(x)\n", + "solution.AddObjective(z)\n", + "\n", + "collector = solver.LastSolutionCollector(solution)\n", + "solver.Solve(\n", + " solver.Phase(x + [z], solver.INT_VAR_DEFAULT, solver.INT_VALUE_DEFAULT),\n", + " [collector, objective])\n", + "\n", + "print(\"z:\", collector.ObjectiveValue(0))\n", + "print(\"x:\", [collector.Value(0, x[i]) for i in range(num_cities)])\n", + "\n", + "print(\"failures:\", solver.Failures())\n", + "print(\"branches:\", solver.Branches())\n", + "print(\"WallTime:\", solver.WallTime())\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/contrib/set_covering2.ipynb b/examples/notebook/contrib/set_covering2.ipynb new file mode 100644 index 0000000000..b41876f1ba --- /dev/null +++ b/examples/notebook/contrib/set_covering2.ipynb @@ -0,0 +1,106 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010 Hakan Kjellerstrand hakank@gmail.com\n", + "#\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"\n", + "\n", + " Set covering in Google CP Solver.\n", + "\n", + " Example 9.1-2, page 354ff, from\n", + " Taha 'Operations Research - An Introduction'\n", + " Minimize the number of security telephones in street\n", + " corners on a campus.\n", + "\n", + " Compare with the following models:\n", + " * MiniZinc: http://www.hakank.org/minizinc/set_covering2.mzn\n", + " * Comet : http://www.hakank.org/comet/set_covering2.co\n", + " * ECLiPSe : http://www.hakank.org/eclipse/set_covering2.ecl\n", + " * SICStus: http://hakank.org/sicstus/set_covering2.pl\n", + " * Gecode: http://hakank.org/gecode/set_covering2.cpp\n", + "\n", + " This model was created by Hakan Kjellerstrand (hakank@gmail.com)\n", + " Also see my other Google CP Solver models:\n", + " http://www.hakank.org/google_or_tools/\n", + "\n", + "\"\"\"\n", + "from __future__ import print_function\n", + "from ortools.constraint_solver import pywrapcp\n", + "\n", + "\n", + "\n", + "# Create the solver.\n", + "solver = pywrapcp.Solver(\"Set covering\")\n", + "\n", + "#\n", + "# data\n", + "#\n", + "n = 8 # maximum number of corners\n", + "num_streets = 11 # number of connected streets\n", + "\n", + "# corners of each street\n", + "# Note: 1-based (handled below)\n", + "corner = [[1, 2], [2, 3], [4, 5], [7, 8], [6, 7], [2, 6], [1, 6], [4, 7],\n", + " [2, 4], [5, 8], [3, 5]]\n", + "\n", + "#\n", + "# declare variables\n", + "#\n", + "x = [solver.IntVar(0, 1, \"x[%i]\" % i) for i in range(n)]\n", + "\n", + "#\n", + "# constraints\n", + "#\n", + "\n", + "# number of telephones, to be minimized\n", + "z = solver.Sum(x)\n", + "\n", + "# ensure that all corners are covered\n", + "for i in range(num_streets):\n", + " # also, convert to 0-based\n", + " solver.Add(solver.SumGreaterOrEqual([x[j - 1] for j in corner[i]], 1))\n", + "\n", + "objective = solver.Minimize(z, 1)\n", + "\n", + "#\n", + "# solution and search\n", + "#\n", + "solution = solver.Assignment()\n", + "solution.Add(x)\n", + "solution.AddObjective(z)\n", + "\n", + "collector = solver.LastSolutionCollector(solution)\n", + "solver.Solve(\n", + " solver.Phase(x, solver.INT_VAR_DEFAULT, solver.INT_VALUE_DEFAULT),\n", + " [collector, objective])\n", + "\n", + "print(\"z:\", collector.ObjectiveValue(0))\n", + "print(\"x:\", [collector.Value(0, x[i]) for i in range(n)])\n", + "\n", + "print(\"failures:\", solver.Failures())\n", + "print(\"branches:\", solver.Branches())\n", + "print(\"WallTime:\", solver.WallTime())\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/contrib/set_covering3.ipynb b/examples/notebook/contrib/set_covering3.ipynb new file mode 100644 index 0000000000..680c4885b6 --- /dev/null +++ b/examples/notebook/contrib/set_covering3.ipynb @@ -0,0 +1,134 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010 Hakan Kjellerstrand hakank@gmail.com\n", + "#\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"\n", + "\n", + " Set covering in Google CP Solver.\n", + "\n", + " Problem from\n", + " Katta G. Murty: 'Optimization Models for Decision Making', page 302f\n", + " http://ioe.engin.umich.edu/people/fac/books/murty/opti_model/junior-7.pdf\n", + "\n", + " 10 senators making a committee, where there must at least be one\n", + " representative from each group:\n", + " group: senators:\n", + " southern 1 2 3 4 5\n", + " northern 6 7 8 9 10\n", + " liberals 2 3 8 9 10\n", + " conservative 1 5 6 7\n", + " democrats 3 4 5 6 7 9\n", + " republicans 1 2 8 10\n", + "\n", + " The objective is to minimize the number of senators.\n", + "\n", + " Compare with the following models:\n", + " * MiniZinc: http://www.hakank.org/minizinc/set_covering3_model.mzn (model)\n", + " http://www.hakank.org/minizinc/set_covering3.mzn (data)\n", + " * Comet : http://www.hakank.org/comet/set_covering3.co\n", + " * ECLiPSe : http://www.hakank.org/eclipse/set_covering3.ecl\n", + " * SICStus : http://hakank.org/sicstus/set_covering3.pl\n", + " * Gecode : http://hakank.org/gecode/set_covering3.cpp\n", + "\n", + "\n", + " This model was created by Hakan Kjellerstrand (hakank@gmail.com)\n", + " Also see my other Google CP Solver models:\n", + " http://www.hakank.org/google_or_tools/\n", + "\n", + "\"\"\"\n", + "from __future__ import print_function\n", + "from ortools.constraint_solver import pywrapcp\n", + "\n", + "\n", + "\n", + "# Create the solver.\n", + "solver = pywrapcp.Solver(\"Set covering\")\n", + "\n", + "#\n", + "# data\n", + "#\n", + "num_groups = 6\n", + "num_senators = 10\n", + "\n", + "# which group does a senator belong to?\n", + "belongs = [\n", + " [1, 1, 1, 1, 1, 0, 0, 0, 0, 0], # 1 southern\n", + " [0, 0, 0, 0, 0, 1, 1, 1, 1, 1], # 2 northern\n", + " [0, 1, 1, 0, 0, 0, 0, 1, 1, 1], # 3 liberals\n", + " [1, 0, 0, 0, 1, 1, 1, 0, 0, 0], # 4 conservative\n", + " [0, 0, 1, 1, 1, 1, 1, 0, 1, 0], # 5 democrats\n", + " [1, 1, 0, 0, 0, 0, 0, 1, 0, 1] # 6 republicans\n", + "]\n", + "\n", + "#\n", + "# declare variables\n", + "#\n", + "x = [solver.IntVar(0, 1, \"x[%i]\" % i) for i in range(num_senators)]\n", + "\n", + "#\n", + "# constraints\n", + "#\n", + "\n", + "# number of assigned senators (to minimize)\n", + "z = solver.Sum(x)\n", + "\n", + "# ensure that each group is covered by at least\n", + "# one senator\n", + "for i in range(num_groups):\n", + " solver.Add(\n", + " solver.SumGreaterOrEqual(\n", + " [x[j] * belongs[i][j] for j in range(num_senators)], 1))\n", + "\n", + "objective = solver.Minimize(z, 1)\n", + "\n", + "#\n", + "# solution and search\n", + "#\n", + "solution = solver.Assignment()\n", + "solution.Add(x)\n", + "solution.AddObjective(z)\n", + "\n", + "collector = solver.LastSolutionCollector(solution)\n", + "solver.Solve(\n", + " solver.Phase(x, solver.INT_VAR_DEFAULT, solver.INT_VALUE_DEFAULT),\n", + " [collector, objective])\n", + "\n", + "print(\"z:\", collector.ObjectiveValue(0))\n", + "print(\"x:\", [collector.Value(0, x[i]) for i in range(num_senators)])\n", + "for j in range(num_senators):\n", + " if collector.Value(0, x[j]) == 1:\n", + " print(\"Senator\", j + 1, \"belongs to these groups:\", end=\" \")\n", + " for i in range(num_groups):\n", + " if belongs[i][j] == 1:\n", + " print(i + 1, end=\" \")\n", + " print()\n", + "\n", + "print()\n", + "print(\"failures:\", solver.Failures())\n", + "print(\"branches:\", solver.Branches())\n", + "print(\"WallTime:\", solver.WallTime())\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/contrib/set_covering4.ipynb b/examples/notebook/contrib/set_covering4.ipynb new file mode 100644 index 0000000000..ce2da66c14 --- /dev/null +++ b/examples/notebook/contrib/set_covering4.ipynb @@ -0,0 +1,162 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010 Hakan Kjellerstrand hakank@gmail.com\n", + "#\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"\n", + "\n", + " Set partition and set covering in Google CP Solver.\n", + "\n", + " Example from the Swedish book\n", + " Lundgren, Roennqvist, Vaebrand\n", + " 'Optimeringslaera' (translation: 'Optimization theory'),\n", + " page 408.\n", + "\n", + " * Set partition:\n", + " We want to minimize the cost of the alternatives which covers all the\n", + " objects, i.e. all objects must be choosen. The requirement is than an\n", + " object may be selected _exactly_ once.\n", + "\n", + " Note: This is 1-based representation\n", + "\n", + " Alternative Cost Object\n", + " 1 19 1,6\n", + " 2 16 2,6,8\n", + " 3 18 1,4,7\n", + " 4 13 2,3,5\n", + " 5 15 2,5\n", + " 6 19 2,3\n", + " 7 15 2,3,4\n", + " 8 17 4,5,8\n", + " 9 16 3,6,8\n", + " 10 15 1,6,7\n", + "\n", + " The problem has a unique solution of z = 49 where alternatives\n", + " 3, 5, and 9\n", + " is selected.\n", + "\n", + " * Set covering:\n", + " If we, however, allow that an object is selected _more than one time_,\n", + " then the solution is z = 45 (i.e. less cost than the first problem),\n", + " and the alternatives\n", + " 4, 8, and 10\n", + " is selected, where object 5 is selected twice (alt. 4 and 8).\n", + " It's an unique solution as well.\n", + "\n", + "\n", + " Compare with the following models:\n", + " * MiniZinc: http://www.hakank.org/minizinc/set_covering4.mzn\n", + " * Comet : http://www.hakank.org/comet/set_covering4.co\n", + " * ECLiPSe : http://www.hakank.org/eclipse/set_covering4.ecl\n", + " * SICStus : http://www.hakank.org/sicstus/set_covering4.pl\n", + " * Gecode : http://www.hakank.org/gecode/set_covering4.cpp\n", + "\n", + "\n", + " This model was created by Hakan Kjellerstrand (hakank@gmail.com)\n", + " Also see my other Google CP Solver models:\n", + " http://www.hakank.org/google_or_tools/\n", + "\n", + "\"\"\"\n", + "from __future__ import print_function\n", + "from ortools.constraint_solver import pywrapcp\n", + "\n", + "\n", + "\n", + "# Create the solver.\n", + "solver = pywrapcp.Solver(\"Set partition and set covering\")\n", + "\n", + "#\n", + "# data\n", + "#\n", + "num_alternatives = 10\n", + "num_objects = 8\n", + "\n", + "# costs for the alternatives\n", + "costs = [19, 16, 18, 13, 15, 19, 15, 17, 16, 15]\n", + "\n", + "# the alternatives, and their objects\n", + "a = [\n", + " # 1 2 3 4 5 6 7 8 the objects\n", + " [1, 0, 0, 0, 0, 1, 0, 0], # alternative 1\n", + " [0, 1, 0, 0, 0, 1, 0, 1], # alternative 2\n", + " [1, 0, 0, 1, 0, 0, 1, 0], # alternative 3\n", + " [0, 1, 1, 0, 1, 0, 0, 0], # alternative 4\n", + " [0, 1, 0, 0, 1, 0, 0, 0], # alternative 5\n", + " [0, 1, 1, 0, 0, 0, 0, 0], # alternative 6\n", + " [0, 1, 1, 1, 0, 0, 0, 0], # alternative 7\n", + " [0, 0, 0, 1, 1, 0, 0, 1], # alternative 8\n", + " [0, 0, 1, 0, 0, 1, 0, 1], # alternative 9\n", + " [1, 0, 0, 0, 0, 1, 1, 0] # alternative 10\n", + "]\n", + "\n", + "#\n", + "# declare variables\n", + "#\n", + "x = [solver.IntVar(0, 1, \"x[%i]\" % i) for i in range(num_alternatives)]\n", + "\n", + "#\n", + "# constraints\n", + "#\n", + "\n", + "# sum the cost of the choosen alternative,\n", + "# to be minimized\n", + "z = solver.ScalProd(x, costs)\n", + "\n", + "#\n", + "for j in range(num_objects):\n", + " if set_partition == 1:\n", + " solver.Add(\n", + " solver.SumEquality([x[i] * a[i][j] for i in range(num_alternatives)],\n", + " 1))\n", + " else:\n", + " solver.Add(\n", + " solver.SumGreaterOrEqual(\n", + " [x[i] * a[i][j] for i in range(num_alternatives)], 1))\n", + "\n", + "objective = solver.Minimize(z, 1)\n", + "\n", + "#\n", + "# solution and search\n", + "#\n", + "solution = solver.Assignment()\n", + "solution.Add(x)\n", + "solution.AddObjective(z)\n", + "\n", + "collector = solver.LastSolutionCollector(solution)\n", + "solver.Solve(\n", + " solver.Phase([x[i] for i in range(num_alternatives)],\n", + " solver.INT_VAR_DEFAULT, solver.INT_VALUE_DEFAULT),\n", + " [collector, objective])\n", + "\n", + "print(\"z:\", collector.ObjectiveValue(0))\n", + "print(\n", + " \"selected alternatives:\",\n", + " [i + 1 for i in range(num_alternatives) if collector.Value(0, x[i]) == 1])\n", + "\n", + "print(\"failures:\", solver.Failures())\n", + "print(\"branches:\", solver.Branches())\n", + "print(\"WallTime:\", solver.WallTime())\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/contrib/set_covering_deployment.ipynb b/examples/notebook/contrib/set_covering_deployment.ipynb new file mode 100644 index 0000000000..2720a7072e --- /dev/null +++ b/examples/notebook/contrib/set_covering_deployment.ipynb @@ -0,0 +1,143 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010 Hakan Kjellerstrand hakank@gmail.com\n", + "#\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"\n", + "\n", + " Set covering deployment in Google CP Solver\n", + "\n", + " From http://mathworld.wolfram.com/SetCoveringDeployment.html\n", + " '''\n", + " Set covering deployment (sometimes written 'set-covering deployment'\n", + " and abbreviated SCDP for 'set covering deployment problem') seeks\n", + " an optimal stationing of troops in a set of regions so that a\n", + " relatively small number of troop units can control a large\n", + " geographic region. ReVelle and Rosing (2000) first described\n", + " this in a study of Emperor Constantine the Great's mobile field\n", + " army placements to secure the Roman Empire.\n", + " '''\n", + "\n", + " Compare with the the following models:\n", + " * MiniZinc: http://www.hakank.org/minizinc/set_covering_deployment.mzn\n", + " * Comet : http://www.hakank.org/comet/set_covering_deployment.co\n", + " * Gecode : http://www.hakank.org/gecode/set_covering_deployment.cpp\n", + " * ECLiPSe : http://www.hakank.org/eclipse/set_covering_deployment.ecl\n", + " * SICStus : http://hakank.org/sicstus/set_covering_deployment.pl\n", + "\n", + " This model was created by Hakan Kjellerstrand (hakank@gmail.com)\n", + " Also see my other Google CP Solver models:\n", + " http://www.hakank.org/google_or_tools/\n", + "\n", + "\"\"\"\n", + "from __future__ import print_function\n", + "from ortools.constraint_solver import pywrapcp\n", + "\n", + "\n", + "\n", + "# Create the solver.\n", + "solver = pywrapcp.Solver(\"Set covering deployment\")\n", + "\n", + "#\n", + "# data\n", + "#\n", + "\n", + "countries = [\n", + " \"Alexandria\", \"Asia Minor\", \"Britain\", \"Byzantium\", \"Gaul\", \"Iberia\",\n", + " \"Rome\", \"Tunis\"\n", + "]\n", + "n = len(countries)\n", + "\n", + "# the incidence matrix (neighbours)\n", + "mat = [[0, 1, 0, 1, 0, 0, 1, 1], [1, 0, 0, 1, 0, 0, 0, 0],\n", + " [0, 0, 0, 0, 1, 1, 0, 0], [1, 1, 0, 0, 0, 0, 1, 0],\n", + " [0, 0, 1, 0, 0, 1, 1, 0], [0, 0, 1, 0, 1, 0, 1, 1],\n", + " [1, 0, 0, 1, 1, 1, 0, 1], [1, 0, 0, 0, 0, 1, 1, 0]]\n", + "\n", + "#\n", + "# declare variables\n", + "#\n", + "\n", + "# First army\n", + "X = [solver.IntVar(0, 1, \"X[%i]\" % i) for i in range(n)]\n", + "\n", + "# Second (reserv) army\n", + "Y = [solver.IntVar(0, 1, \"Y[%i]\" % i) for i in range(n)]\n", + "\n", + "#\n", + "# constraints\n", + "#\n", + "\n", + "# total number of armies\n", + "num_armies = solver.Sum([X[i] + Y[i] for i in range(n)])\n", + "\n", + "#\n", + "# Constraint 1: There is always an army in a city\n", + "# (+ maybe a backup)\n", + "# Or rather: Is there a backup, there\n", + "# must be an an army\n", + "#\n", + "[solver.Add(X[i] >= Y[i]) for i in range(n)]\n", + "\n", + "#\n", + "# Constraint 2: There should always be an backup army near every city\n", + "#\n", + "for i in range(n):\n", + " neighbors = solver.Sum([Y[j] for j in range(n) if mat[i][j] == 1])\n", + " solver.Add(X[i] + neighbors >= 1)\n", + "\n", + "objective = solver.Minimize(num_armies, 1)\n", + "\n", + "#\n", + "# solution and search\n", + "#\n", + "solution = solver.Assignment()\n", + "solution.Add(X)\n", + "solution.Add(Y)\n", + "solution.Add(num_armies)\n", + "solution.AddObjective(num_armies)\n", + "\n", + "collector = solver.LastSolutionCollector(solution)\n", + "solver.Solve(\n", + " solver.Phase(X + Y, solver.INT_VAR_DEFAULT, solver.INT_VALUE_DEFAULT),\n", + " [collector, objective])\n", + "\n", + "print(\"num_armies:\", collector.ObjectiveValue(0))\n", + "print(\"X:\", [collector.Value(0, X[i]) for i in range(n)])\n", + "print(\"Y:\", [collector.Value(0, Y[i]) for i in range(n)])\n", + "\n", + "for i in range(n):\n", + " if collector.Value(0, X[i]) == 1:\n", + " print(\"army:\", countries[i], end=\" \")\n", + " if collector.Value(0, Y[i]) == 1:\n", + " print(\"reserv army:\", countries[i], \" \")\n", + "print()\n", + "\n", + "print()\n", + "print(\"failures:\", solver.Failures())\n", + "print(\"branches:\", solver.Branches())\n", + "print(\"WallTime:\", solver.WallTime())\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/contrib/set_covering_skiena.ipynb b/examples/notebook/contrib/set_covering_skiena.ipynb new file mode 100644 index 0000000000..1446526f8d --- /dev/null +++ b/examples/notebook/contrib/set_covering_skiena.ipynb @@ -0,0 +1,130 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010 Hakan Kjellerstrand hakank@gmail.com\n", + "#\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"\n", + "\n", + " Set covering in Google CP Solver.\n", + "\n", + " Example from Steven Skiena, The Stony Brook Algorithm Repository\n", + " http://www.cs.sunysb.edu/~algorith/files/set-cover.shtml\n", + " '''\n", + " Input Description: A set of subsets S_1, ..., S_m of the\n", + " universal set U = {1,...,n}.\n", + "\n", + " Problem: What is the smallest subset of subsets T subset S such\n", + " that \\cup_{t_i in T} t_i = U?\n", + " '''\n", + " Data is from the pictures INPUT/OUTPUT.\n", + "\n", + " Compare with the following models:\n", + " * MiniZinc: http://www.hakank.org/minizinc/set_covering_skiena.mzn\n", + " * Comet: http://www.hakank.org/comet/set_covering_skiena.co\n", + " * ECLiPSe: http://www.hakank.org/eclipse/set_covering_skiena.ecl\n", + " * SICStus Prolog: http://www.hakank.org/sicstus/set_covering_skiena.pl\n", + " * Gecode: http://hakank.org/gecode/set_covering_skiena.cpp\n", + "\n", + "\n", + " This model was created by Hakan Kjellerstrand (hakank@gmail.com)\n", + " Also see my other Google CP Solver models:\n", + " http://www.hakank.org/google_or_tools/\n", + "\"\"\"\n", + "from __future__ import print_function\n", + "from ortools.constraint_solver import pywrapcp\n", + "\n", + "\n", + "\n", + "# Create the solver.\n", + "solver = pywrapcp.Solver('Set covering Skiena')\n", + "\n", + "#\n", + "# data\n", + "#\n", + "num_sets = 7\n", + "num_elements = 12\n", + "belongs = [\n", + " # 1 2 3 4 5 6 7 8 9 0 1 2 elements\n", + " [1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], # Set 1\n", + " [0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0], # 2\n", + " [0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0], # 3\n", + " [0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0], # 4\n", + " [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0], # 5\n", + " [1, 1, 1, 0, 1, 0, 0, 0, 1, 1, 1, 0], # 6\n", + " [0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1] # 7\n", + "]\n", + "\n", + "#\n", + "# variables\n", + "#\n", + "x = [solver.IntVar(0, 1, 'x[%i]' % i) for i in range(num_sets)]\n", + "\n", + "# number of choosen sets\n", + "z = solver.IntVar(0, num_sets * 2, 'z')\n", + "\n", + "# total number of elements in the choosen sets\n", + "tot_elements = solver.IntVar(0, num_sets * num_elements)\n", + "\n", + "#\n", + "# constraints\n", + "#\n", + "solver.Add(z == solver.Sum(x))\n", + "\n", + "# all sets must be used\n", + "for j in range(num_elements):\n", + " s = solver.Sum([belongs[i][j] * x[i] for i in range(num_sets)])\n", + " solver.Add(s >= 1)\n", + "\n", + "# number of used elements\n", + "solver.Add(tot_elements == solver.Sum([\n", + " x[i] * belongs[i][j] for i in range(num_sets) for j in range(num_elements)\n", + "]))\n", + "\n", + "# objective\n", + "objective = solver.Minimize(z, 1)\n", + "\n", + "#\n", + "# search and result\n", + "#\n", + "db = solver.Phase(x, solver.INT_VAR_DEFAULT, solver.INT_VALUE_DEFAULT)\n", + "\n", + "solver.NewSearch(db, [objective])\n", + "\n", + "num_solutions = 0\n", + "while solver.NextSolution():\n", + " num_solutions += 1\n", + " print('z:', z.Value())\n", + " print('tot_elements:', tot_elements.Value())\n", + " print('x:', [x[i].Value() for i in range(num_sets)])\n", + "\n", + "solver.EndSearch()\n", + "\n", + "print()\n", + "print('num_solutions:', num_solutions)\n", + "print('failures:', solver.Failures())\n", + "print('branches:', solver.Branches())\n", + "print('WallTime:', solver.WallTime(), 'ms')\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/contrib/set_partition.ipynb b/examples/notebook/contrib/set_partition.ipynb new file mode 100644 index 0000000000..64522b1b12 --- /dev/null +++ b/examples/notebook/contrib/set_partition.ipynb @@ -0,0 +1,180 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010 Hakan Kjellerstrand hakank@gmail.com\n", + "#\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"\n", + "\n", + " Set partition problem in Google CP Solver.\n", + "\n", + " Problem formulation from\n", + " http://www.koalog.com/resources/samples/PartitionProblem.java.html\n", + " '''\n", + " This is a partition problem.\n", + " Given the set S = {1, 2, ..., n},\n", + " it consists in finding two sets A and B such that:\n", + "\n", + " A U B = S,\n", + " |A| = |B|,\n", + " sum(A) = sum(B),\n", + " sum_squares(A) = sum_squares(B)\n", + "\n", + " '''\n", + "\n", + " This model uses a binary matrix to represent the sets.\n", + "\n", + "\n", + " Also, compare with other models which uses var sets:\n", + " * MiniZinc: http://www.hakank.org/minizinc/set_partition.mzn\n", + " * Gecode/R: http://www.hakank.org/gecode_r/set_partition.rb\n", + " * Comet: http://hakank.org/comet/set_partition.co\n", + " * Gecode: http://hakank.org/gecode/set_partition.cpp\n", + " * ECLiPSe: http://hakank.org/eclipse/set_partition.ecl\n", + " * SICStus: http://hakank.org/sicstus/set_partition.pl\n", + "\n", + " This model was created by Hakan Kjellerstrand (hakank@gmail.com)\n", + " Also see my other Google CP Solver models:\n", + " http://www.hakank.org/google_or_tools/\n", + "\"\"\"\n", + "from __future__ import print_function\n", + "import sys\n", + "\n", + "from ortools.constraint_solver import pywrapcp\n", + "\n", + "\n", + "#\n", + "# Partition the sets (binary matrix representation).\n", + "#\n", + "def partition_sets(x, num_sets, n):\n", + " solver = list(x.values())[0].solver()\n", + "\n", + " for i in range(num_sets):\n", + " for j in range(num_sets):\n", + " if i != j:\n", + " b = solver.Sum([x[i, k] * x[j, k] for k in range(n)])\n", + " solver.Add(b == 0)\n", + "\n", + " # ensure that all integers is in\n", + " # (exactly) one partition\n", + " b = [x[i, j] for i in range(num_sets) for j in range(n)]\n", + " solver.Add(solver.Sum(b) == n)\n", + "\n", + "\n", + "\n", + "# Create the solver.\n", + "solver = pywrapcp.Solver(\"Set partition\")\n", + "\n", + "#\n", + "# data\n", + "#\n", + "print(\"n:\", n)\n", + "print(\"num_sets:\", num_sets)\n", + "print()\n", + "\n", + "# Check sizes\n", + "assert n % num_sets == 0, \"Equal sets is not possible.\"\n", + "\n", + "#\n", + "# variables\n", + "#\n", + "\n", + "# the set\n", + "a = {}\n", + "for i in range(num_sets):\n", + " for j in range(n):\n", + " a[i, j] = solver.IntVar(0, 1, \"a[%i,%i]\" % (i, j))\n", + "\n", + "a_flat = [a[i, j] for i in range(num_sets) for j in range(n)]\n", + "\n", + "#\n", + "# constraints\n", + "#\n", + "\n", + "# partition set\n", + "partition_sets(a, num_sets, n)\n", + "\n", + "for i in range(num_sets):\n", + " for j in range(i, num_sets):\n", + "\n", + " # same cardinality\n", + " solver.Add(\n", + " solver.Sum([a[i, k] for k in range(n)]) == solver.Sum(\n", + " [a[j, k] for k in range(n)]))\n", + "\n", + " # same sum\n", + " solver.Add(\n", + " solver.Sum([k * a[i, k] for k in range(n)]) == solver.Sum(\n", + " [k * a[j, k] for k in range(n)]))\n", + "\n", + " # same sum squared\n", + " solver.Add(\n", + " solver.Sum([(k * a[i, k]) * (k * a[i, k]) for k in range(n)]) ==\n", + " solver.Sum([(k * a[j, k]) * (k * a[j, k]) for k in range(n)]))\n", + "\n", + "# symmetry breaking for num_sets == 2\n", + "if num_sets == 2:\n", + " solver.Add(a[0, 0] == 1)\n", + "\n", + "#\n", + "# search and result\n", + "#\n", + "db = solver.Phase(a_flat, solver.INT_VAR_DEFAULT, solver.INT_VALUE_DEFAULT)\n", + "\n", + "solver.NewSearch(db)\n", + "\n", + "num_solutions = 0\n", + "while solver.NextSolution():\n", + " a_val = {}\n", + " for i in range(num_sets):\n", + " for j in range(n):\n", + " a_val[i, j] = a[i, j].Value()\n", + "\n", + " sq = sum([(j + 1) * a_val[0, j] for j in range(n)])\n", + " print(\"sums:\", sq)\n", + " sq2 = sum([((j + 1) * a_val[0, j])**2 for j in range(n)])\n", + " print(\"sums squared:\", sq2)\n", + "\n", + " for i in range(num_sets):\n", + " if sum([a_val[i, j] for j in range(n)]):\n", + " print(i + 1, \":\", end=\" \")\n", + " for j in range(n):\n", + " if a_val[i, j] == 1:\n", + " print(j + 1, end=\" \")\n", + " print()\n", + "\n", + " print()\n", + " num_solutions += 1\n", + "\n", + "solver.EndSearch()\n", + "\n", + "print()\n", + "print(\"num_solutions:\", num_solutions)\n", + "print(\"failures:\", solver.Failures())\n", + "print(\"branches:\", solver.Branches())\n", + "print(\"WallTime:\", solver.WallTime())\n", + "\n", + "n = 16\n", + "num_sets = 2\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/contrib/sicherman_dice.ipynb b/examples/notebook/contrib/sicherman_dice.ipynb new file mode 100644 index 0000000000..ff2cde8613 --- /dev/null +++ b/examples/notebook/contrib/sicherman_dice.ipynb @@ -0,0 +1,150 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010 Hakan Kjellerstrand hakank@gmail.com\n", + "#\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"\n", + "\n", + " Sicherman Dice in Google CP Solver.\n", + "\n", + " From http://en.wikipedia.org/wiki/Sicherman_dice\n", + " \"\"\n", + " Sicherman dice are the only pair of 6-sided dice which are not normal dice,\n", + " bear only positive integers, and have the same probability distribution for\n", + " the sum as normal dice.\n", + "\n", + " The faces on the dice are numbered 1, 2, 2, 3, 3, 4 and 1, 3, 4, 5, 6, 8.\n", + " \"\"\n", + "\n", + " I read about this problem in a book/column by Martin Gardner long\n", + " time ago, and got inspired to model it now by the WolframBlog post\n", + " \"Sicherman Dice\": http://blog.wolfram.com/2010/07/13/sicherman-dice/\n", + "\n", + " This model gets the two different ways, first the standard way and\n", + " then the Sicherman dice:\n", + "\n", + " x1 = [1, 2, 3, 4, 5, 6]\n", + " x2 = [1, 2, 3, 4, 5, 6]\n", + " ----------\n", + " x1 = [1, 2, 2, 3, 3, 4]\n", + " x2 = [1, 3, 4, 5, 6, 8]\n", + "\n", + "\n", + " Extra: If we also allow 0 (zero) as a valid value then the\n", + " following two solutions are also valid:\n", + "\n", + " x1 = [0, 1, 1, 2, 2, 3]\n", + " x2 = [2, 4, 5, 6, 7, 9]\n", + " ----------\n", + " x1 = [0, 1, 2, 3, 4, 5]\n", + " x2 = [2, 3, 4, 5, 6, 7]\n", + "\n", + " These two extra cases are mentioned here:\n", + " http://mathworld.wolfram.com/SichermanDice.html\n", + "\n", + " Compare with these models:\n", + " * MiniZinc: http://hakank.org/minizinc/sicherman_dice.mzn\n", + " * Gecode: http://hakank.org/gecode/sicherman_dice.cpp\n", + "\n", + " This model was created by Hakan Kjellerstrand (hakank@gmail.com)\n", + " Also see my other Google CP Solver models:\n", + " http://www.hakank.org/google_or_tools/\n", + "\"\"\"\n", + "from __future__ import print_function\n", + "import sys\n", + "from ortools.constraint_solver import pywrapcp\n", + "\n", + "\n", + "\n", + "# Create the solver.\n", + "solver = pywrapcp.Solver(\"Sicherman dice\")\n", + "\n", + "#\n", + "# data\n", + "#\n", + "n = 6\n", + "m = 10\n", + "\n", + "# standard distribution\n", + "standard_dist = [1, 2, 3, 4, 5, 6, 5, 4, 3, 2, 1]\n", + "\n", + "#\n", + "# declare variables\n", + "#\n", + "\n", + "# the two dice\n", + "x1 = [solver.IntVar(0, m, \"x1(%i)\" % i) for i in range(n)]\n", + "x2 = [solver.IntVar(0, m, \"x2(%i)\" % i) for i in range(n)]\n", + "\n", + "#\n", + "# constraints\n", + "#\n", + "# [solver.Add(standard_dist[k] == solver.Sum([x1[i] + x2[j] == k+2 for i in range(n) for j in range(n)]))\n", + "# for k in range(len(standard_dist))]\n", + "for k in range(len(standard_dist)):\n", + " tmp = [solver.BoolVar() for i in range(n) for j in range(n)]\n", + " for i in range(n):\n", + " for j in range(n):\n", + " solver.Add(tmp[i * n + j] == solver.IsEqualCstVar(x1[i] + x2[j], k + 2))\n", + " solver.Add(standard_dist[k] == solver.Sum(tmp))\n", + "\n", + "# symmetry breaking\n", + "[solver.Add(x1[i] <= x1[i + 1]) for i in range(n - 1)],\n", + "[solver.Add(x2[i] <= x2[i + 1]) for i in range(n - 1)],\n", + "[solver.Add(x1[i] <= x2[i]) for i in range(n - 1)],\n", + "\n", + "#\n", + "# solution and search\n", + "#\n", + "solution = solver.Assignment()\n", + "solution.Add(x1)\n", + "solution.Add(x2)\n", + "\n", + "# db: DecisionBuilder\n", + "db = solver.Phase(x1 + x2, solver.INT_VAR_SIMPLE, solver.ASSIGN_MIN_VALUE)\n", + "\n", + "solver.NewSearch(db)\n", + "num_solutions = 0\n", + "while solver.NextSolution():\n", + " print(\"x1:\", [x1[i].Value() for i in range(n)])\n", + " print(\"x2:\", [x2[i].Value() for i in range(n)])\n", + " print()\n", + "\n", + " num_solutions += 1\n", + "solver.EndSearch()\n", + "\n", + "print()\n", + "print(\"num_solutions:\", num_solutions, \"solver.solutions:\",\n", + " solver.Solutions())\n", + "print(\"failures:\", solver.Failures())\n", + "print(\"branches:\", solver.Branches())\n", + "print(\"WallTime:\", solver.WallTime())\n", + "print(\"MemoryUsage:\", solver.MemoryUsage())\n", + "print(\"SearchDepth:\", solver.SearchDepth())\n", + "print(\"SolveDepth:\", solver.SolveDepth())\n", + "print(\"stamp:\", solver.Stamp())\n", + "print(\"solver\", solver)\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/contrib/ski_assignment.ipynb b/examples/notebook/contrib/ski_assignment.ipynb new file mode 100644 index 0000000000..2d26a5076c --- /dev/null +++ b/examples/notebook/contrib/ski_assignment.ipynb @@ -0,0 +1,131 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010 Hakan Kjellerstrand hakank@gmail.com\n", + "#\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"\n", + "\n", + " Ski assignment in Google CP Solver.\n", + "\n", + " From Jeffrey Lee Hellrung, Jr.:\n", + " PIC 60, Fall 2008 Final Review, December 12, 2008\n", + " http://www.math.ucla.edu/~jhellrun/course_files/Fall%25202008/PIC%252060%2520-%2520Data%2520Structures%2520and%2520Algorithms/final_review.pdf\n", + " '''\n", + " 5. Ski Optimization! Your job at Snapple is pleasant but in the winter\n", + " you've decided to become a ski bum. You've hooked up with the Mount\n", + " Baldy Ski Resort. They'll let you ski all winter for free in exchange\n", + " for helping their ski rental shop with an algorithm to assign skis to\n", + " skiers. Ideally, each skier should obtain a pair of skis whose height\n", + " matches his or her own height exactly. Unfortunately, this is generally\n", + " not possible. We define the disparity between a skier and his or her\n", + " skis to be the absolute value of the difference between the height of\n", + " the skier and the pair of skis. Our objective is to find an assignment\n", + " of skis to skiers that minimizes the sum of the disparities.\n", + " ...\n", + " Illustrate your algorithm by explicitly filling out the A[i, j] table\n", + " for the following sample data:\n", + " * Ski heights: 1, 2, 5, 7, 13, 21.\n", + " * Skier heights: 3, 4, 7, 11, 18.\n", + " '''\n", + "\n", + " Compare with the following models:\n", + " * Comet : http://www.hakank.org/comet/ski_assignment.co\n", + " * MiniZinc: http://hakank.org/minizinc/ski_assignment.mzn\n", + " * ECLiPSe : http://www.hakank.org/eclipse/ski_assignment.ecl\n", + " * SICStus: http://hakank.org/sicstus/ski_assignment.pl\n", + " * Gecode: http://hakank.org/gecode/ski_assignment.cpp\n", + "\n", + " This model was created by Hakan Kjellerstrand (hakank@gmail.com)\n", + " Also see my other Google CP Solver models:\n", + " http://www.hakank.org/google_or_tools/\n", + "\"\"\"\n", + "from __future__ import print_function\n", + "import sys\n", + "\n", + "from ortools.constraint_solver import pywrapcp\n", + "\n", + "\n", + "\n", + "# Create the solver.\n", + "solver = pywrapcp.Solver('Ski assignment')\n", + "\n", + "#\n", + "# data\n", + "#\n", + "num_skis = 6\n", + "num_skiers = 5\n", + "ski_heights = [1, 2, 5, 7, 13, 21]\n", + "skier_heights = [3, 4, 7, 11, 18]\n", + "\n", + "#\n", + "# variables\n", + "#\n", + "\n", + "# which ski to choose for each skier\n", + "x = [solver.IntVar(0, num_skis - 1, 'x[%i]' % i) for i in range(num_skiers)]\n", + "z = solver.IntVar(0, sum(ski_heights), 'z')\n", + "\n", + "#\n", + "# constraints\n", + "#\n", + "solver.Add(solver.AllDifferent(x))\n", + "\n", + "z_tmp = [\n", + " abs(solver.Element(ski_heights, x[i]) - skier_heights[i])\n", + " for i in range(num_skiers)\n", + "]\n", + "solver.Add(z == sum(z_tmp))\n", + "\n", + "# objective\n", + "objective = solver.Minimize(z, 1)\n", + "\n", + "#\n", + "# search and result\n", + "#\n", + "db = solver.Phase(x, solver.INT_VAR_DEFAULT, solver.INT_VALUE_DEFAULT)\n", + "\n", + "solver.NewSearch(db, [objective])\n", + "\n", + "num_solutions = 0\n", + "while solver.NextSolution():\n", + " num_solutions += 1\n", + " print('total differences:', z.Value())\n", + " for i in range(num_skiers):\n", + " x_val = x[i].Value()\n", + " ski_height = ski_heights[x[i].Value()]\n", + " diff = ski_height - skier_heights[i]\n", + " print('Skier %i: Ski %i with length %2i (diff: %2i)' %\\\n", + " (i, x_val, ski_height, diff))\n", + " print()\n", + "\n", + "solver.EndSearch()\n", + "\n", + "print()\n", + "print('num_solutions:', num_solutions)\n", + "print('failures:', solver.Failures())\n", + "print('branches:', solver.Branches())\n", + "print('WallTime:', solver.WallTime())\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/contrib/slitherlink.ipynb b/examples/notebook/contrib/slitherlink.ipynb new file mode 100644 index 0000000000..e75577db66 --- /dev/null +++ b/examples/notebook/contrib/slitherlink.ipynb @@ -0,0 +1,299 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from ortools.constraint_solver import pywrapcp\n", + "from collections import deque\n", + "\n", + "small = [[3, 2, -1, 3], [-1, -1, -1, 2], [3, -1, -1, -1], [3, -1, 3, 1]]\n", + "\n", + "medium = [[-1, 0, -1, 1, -1, -1, 1, -1], [-1, 3, -1, -1, 2, 3, -1, 2],\n", + " [-1, -1, 0, -1, -1, -1, -1, 0], [-1, 3, -1, -1, 0, -1, -1, -1],\n", + " [-1, -1, -1, 3, -1, -1, 0, -1], [1, -1, -1, -1, -1, 3, -1, -1],\n", + " [3, -1, 1, 3, -1, -1, 3, -1], [-1, 0, -1, -1, 3, -1, 3, -1]]\n", + "\n", + "big = [[3, -1, -1, -1, 2, -1, 1, -1, 1, 2], [1, -1, 0, -1, 3, -1, 2, 0, -1, -1],\n", + " [-1, 3, -1, -1, -1, -1, -1, -1, 3, -1],\n", + " [2, 0, -1, 3, -1, 2, 3, -1, -1, -1], [-1, -1, -1, 1, 1, 1, -1, -1, 3, 3],\n", + " [2, 3, -1, -1, 2, 2, 3, -1, -1, -1], [-1, -1, -1, 1, 2, -1, 2, -1, 3, 3],\n", + " [-1, 2, -1, -1, -1, -1, -1, -1, 2, -1],\n", + " [-1, -1, 1, 1, -1, 2, -1, 1, -1, 3], [3, 3, -1, 1, -1, 2, -1, -1, -1, 2]]\n", + "\n", + "\n", + "def NeighboringArcs(i, j, h_arcs, v_arcs):\n", + " tmp = []\n", + " if j > 0:\n", + " tmp.append(h_arcs[i][j - 1])\n", + " if j < len(v_arcs) - 1:\n", + " tmp.append(h_arcs[i][j])\n", + " if i > 0:\n", + " tmp.append(v_arcs[j][i - 1])\n", + " if i < len(h_arcs) - 1:\n", + " tmp.append(v_arcs[j][i])\n", + " return tmp\n", + "\n", + "\n", + "def PrintSolution(data, h_arcs, v_arcs):\n", + " num_rows = len(data)\n", + " num_columns = len(data[0])\n", + "\n", + " for i in range(num_rows):\n", + " first_line = ''\n", + " second_line = ''\n", + " third_line = ''\n", + " for j in range(num_columns):\n", + " h_arc = h_arcs[i][j].Value()\n", + " v_arc = v_arcs[j][i].Value()\n", + " cnt = data[i][j]\n", + " first_line += ' ---' if h_arc else ' '\n", + " second_line += '|' if v_arc else ' '\n", + " second_line += ' ' if cnt == -1 else ' %i ' % cnt\n", + " third_line += '| ' if v_arc == 1 else ' '\n", + " termination = v_arcs[num_columns][i].Value()\n", + " second_line += '|' if termination else ' '\n", + " third_line += '|' if termination else ' '\n", + " print(first_line)\n", + " print(third_line)\n", + " print(second_line)\n", + " print(third_line)\n", + " last_line = ''\n", + " for j in range(num_columns):\n", + " h_arc = h_arcs[num_rows][j].Value()\n", + " last_line += ' ---' if h_arc else ' '\n", + " print(last_line)\n", + "\n", + "\n", + "class BooleanSumEven(pywrapcp.PyConstraint):\n", + "\n", + " def __init__(self, solver, vars):\n", + " pywrapcp.PyConstraint.__init__(self, solver)\n", + " self.__vars = vars\n", + " self.__num_possible_true_vars = pywrapcp.NumericalRevInteger(0)\n", + " self.__num_always_true_vars = pywrapcp.NumericalRevInteger(0)\n", + "\n", + " def Post(self):\n", + " for i in range(len(self.__vars)):\n", + " v = self.__vars[i]\n", + " if not v.Bound():\n", + " demon = self.Demon(BooleanSumEven.Update, i)\n", + " v.WhenBound(demon)\n", + "\n", + " def InitialPropagate(self):\n", + " num_always_true = 0\n", + " num_possible_true = 0\n", + " possible_true_index = -1\n", + " for i in range(len(self.__vars)):\n", + " var = self.__vars[i]\n", + " if var.Min() == 1:\n", + " num_always_true += 1\n", + " num_possible_true += 1\n", + " elif var.Max() == 1:\n", + " num_possible_true += 1\n", + " possible_true_index = i\n", + "\n", + " if num_always_true == num_possible_true and num_possible_true % 2 == 1:\n", + " self.solver().Fail()\n", + "\n", + " if num_possible_true == num_always_true + 1:\n", + " self.__vars[possible_true_index].SetValue(num_always_true % 2)\n", + "\n", + " self.__num_possible_true_vars.SetValue(self.solver(), num_possible_true)\n", + " self.__num_always_true_vars.SetValue(self.solver(), num_always_true)\n", + "\n", + " def Update(self, index):\n", + " solver = self.solver()\n", + " value = self.__vars[index].Value()\n", + " if value == 0:\n", + " self.__num_possible_true_vars.Decr(solver)\n", + " else:\n", + " self.__num_always_true_vars.Incr(solver)\n", + "\n", + " num_possible = self.__num_possible_true_vars.Value()\n", + " num_always = self.__num_always_true_vars.Value()\n", + "\n", + " if num_always == num_possible and num_possible % 2 == 1:\n", + " solver.Fail()\n", + "\n", + " if num_possible == num_always + 1:\n", + " possible_true_index = -1\n", + " for i in range(len(self.__vars)):\n", + " if not self.__vars[i].Bound():\n", + " possible_true_index = i\n", + " break\n", + "\n", + " if possible_true_index != -1:\n", + " self.__vars[possible_true_index].SetValue(num_always % 2)\n", + "\n", + " def DebugString(self):\n", + " return 'BooleanSumEven'\n", + "\n", + "\n", + "# Dedicated constraint: There is a single path on the grid.\n", + "# This constraint does not enforce the non-crossing, this is done\n", + "# by the constraint on the degree of each node.\n", + "class GridSinglePath(pywrapcp.PyConstraint):\n", + "\n", + " def __init__(self, solver, h_arcs, v_arcs):\n", + " pywrapcp.PyConstraint.__init__(self, solver)\n", + " self.__h_arcs = h_arcs\n", + " self.__v_arcs = v_arcs\n", + "\n", + " def Post(self):\n", + " demon = self.DelayedInitialPropagateDemon()\n", + " for row in self.__h_arcs:\n", + " for var in row:\n", + " var.WhenBound(demon)\n", + "\n", + " for column in self.__v_arcs:\n", + " for var in column:\n", + " var.WhenBound(demon)\n", + "\n", + " # This constraint implements a single propagation.\n", + " # If one point is on the path, it checks the reachability of all possible\n", + " # nodes, and zero out the unreachable parts.\n", + " def InitialPropagate(self):\n", + " num_rows = len(self.__h_arcs)\n", + " num_columns = len(self.__v_arcs)\n", + "\n", + " num_points = num_rows * num_columns\n", + " root_node = -1\n", + " possible_points = set()\n", + " neighbors = [[] for _ in range(num_points)]\n", + "\n", + " for i in range(num_rows):\n", + " for j in range(num_columns - 1):\n", + " h_arc = self.__h_arcs[i][j]\n", + " if h_arc.Max() == 1:\n", + " head = i * num_columns + j\n", + " tail = i * num_columns + j + 1\n", + " neighbors[head].append(tail)\n", + " neighbors[tail].append(head)\n", + " possible_points.add(head)\n", + " possible_points.add(tail)\n", + " if root_node == -1 and h_arc.Min() == 1:\n", + " root_node = head\n", + "\n", + " for i in range(num_rows - 1):\n", + " for j in range(num_columns):\n", + " v_arc = self.__v_arcs[j][i]\n", + " if v_arc.Max() == 1:\n", + " head = i * num_columns + j\n", + " tail = (i + 1) * num_columns + j\n", + " neighbors[head].append(tail)\n", + " neighbors[tail].append(head)\n", + " possible_points.add(head)\n", + " possible_points.add(tail)\n", + " if root_node == -1 and v_arc.Min() == 1:\n", + " root_node = head\n", + "\n", + " if root_node == -1:\n", + " return\n", + "\n", + " visited_points = set()\n", + " to_process = deque()\n", + "\n", + " # Compute reachable points\n", + " to_process.append(root_node)\n", + " while to_process:\n", + " candidate = to_process.popleft()\n", + " visited_points.add(candidate)\n", + " for neighbor in neighbors[candidate]:\n", + " if not neighbor in visited_points:\n", + " to_process.append(neighbor)\n", + " visited_points.add(neighbor)\n", + "\n", + " if len(visited_points) < len(possible_points):\n", + " for point in visited_points:\n", + " possible_points.remove(point)\n", + "\n", + " # Loop on unreachable points and zero all neighboring arcs.\n", + " for point in possible_points:\n", + " i = point // num_columns\n", + " j = point % num_columns\n", + " neighbors = NeighboringArcs(i, j, self.__h_arcs, self.__v_arcs)\n", + " for var in neighbors:\n", + " var.SetMax(0)\n", + "\n", + "\n", + "def SlitherLink(data):\n", + " num_rows = len(data)\n", + " num_columns = len(data[0])\n", + "\n", + " solver = pywrapcp.Solver('slitherlink')\n", + " h_arcs = [[\n", + " solver.BoolVar('h_arcs[%i][%i]' % (i, j)) for j in range(num_columns)\n", + " ] for i in range(num_rows + 1)]\n", + "\n", + " v_arcs = [[\n", + " solver.BoolVar('v_arcs[%i][%i]' % (i, j)) for j in range(num_rows)\n", + " ] for i in range(num_columns + 1)]\n", + "\n", + " # Constraint on the sum or arcs\n", + " for i in range(num_rows):\n", + " for j in range(num_columns):\n", + " if data[i][j] != -1:\n", + " sq = [h_arcs[i][j], h_arcs[i + 1][j], v_arcs[j][i], v_arcs[j + 1][i]]\n", + " solver.Add(solver.SumEquality(sq, data[i][j]))\n", + "\n", + " # Single loop: each node has a degree 0 or 2\n", + " zero_or_two = [0, 2]\n", + " for i in range(num_rows + 1):\n", + " for j in range(num_columns + 1):\n", + " neighbors = NeighboringArcs(i, j, h_arcs, v_arcs)\n", + " solver.Add(solver.Sum(neighbors).Member(zero_or_two))\n", + "\n", + " # Single loop: sum or arcs on row or column is even\n", + " for i in range(num_columns):\n", + " column = [h_arcs[j][i] for j in range(num_rows + 1)]\n", + " solver.Add(BooleanSumEven(solver, column))\n", + "\n", + " for i in range(num_rows):\n", + " row = [v_arcs[j][i] for j in range(num_columns + 1)]\n", + " solver.Add(BooleanSumEven(solver, row))\n", + "\n", + " # Single loop: main constraint\n", + " solver.Add(GridSinglePath(solver, h_arcs, v_arcs))\n", + "\n", + " # Special rule on corners: value == 3 implies 2 border arcs used.\n", + " if data[0][0] == 3:\n", + " h_arcs[0][0].SetMin(1)\n", + " v_arcs[0][0].SetMin(1)\n", + " if data[0][num_columns - 1] == 3:\n", + " h_arcs[0][num_columns - 1].SetMin(1)\n", + " v_arcs[num_columns][0].SetMin(1)\n", + " if data[num_rows - 1][0] == 3:\n", + " h_arcs[num_rows][0].SetMin(1)\n", + " v_arcs[0][num_rows - 1].SetMin(1)\n", + " if data[num_rows - 1][num_columns - 1] == 3:\n", + " h_arcs[num_rows][num_columns - 1].SetMin(1)\n", + " v_arcs[num_columns][num_rows - 1].SetMin(1)\n", + "\n", + " # Search\n", + " all_vars = []\n", + " for row in h_arcs:\n", + " all_vars.extend(row)\n", + " for column in v_arcs:\n", + " all_vars.extend(column)\n", + "\n", + " db = solver.Phase(all_vars, solver.CHOOSE_FIRST_UNBOUND,\n", + " solver.ASSIGN_MAX_VALUE)\n", + "\n", + " log = solver.SearchLog(1000000)\n", + "\n", + " solver.NewSearch(db, log)\n", + " while solver.NextSolution():\n", + " PrintSolution(data, h_arcs, v_arcs)\n", + " solver.EndSearch()\n", + "\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/contrib/sports_schedule_sat.ipynb b/examples/notebook/contrib/sports_schedule_sat.ipynb new file mode 100644 index 0000000000..601abe23b2 --- /dev/null +++ b/examples/notebook/contrib/sports_schedule_sat.ipynb @@ -0,0 +1,575 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Based on sports_scheduling_sat.cc, Copyright 2010-2019 Google LLC\n", + "#\n", + "# Translated to Python by James E. Marca August 2019\n", + "#\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"Sports scheduling problem.\n", + "\n", + "We want to solve the problem of scheduling of team matches in a\n", + "double round robin tournament. Given a number of teams, we want\n", + "each team to encounter all other teams, twice, once at home, and\n", + "once away. Furthermore, you cannot meet the same team twice in the\n", + "same half-season.\n", + "\n", + "Finally, there are constraints on the sequence of home or aways:\n", + " - You cannot have 3 consecutive homes or three consecutive aways.\n", + " - A break is a sequence of two homes or two aways, the overall objective\n", + " of the optimization problem is to minimize the total number of breaks.\n", + " - If team A meets team B, the reverse match cannot happen less that 6 weeks\n", + " after.\n", + "\n", + "Translation to python:\n", + "\n", + "This version is essentially a straight translation of\n", + "sports_scheduling_sat.cc from the C++ example, SecondModel version.\n", + "\n", + "This code gets an F from code climate for code quality and\n", + "maintainability.\n", + "\n", + "Originally developed with pool vs pool constraints on the mailing\n", + "list, but that has been dropped for this version to keep it in sync\n", + "with the C++ version.\n", + "\n", + "Added command line options to set numbers of teams, numbers of days,\n", + "etc.\n", + "\n", + "Added CSV output.\n", + "\n", + "For a version with pool constraints, plus tests, etc, see\n", + "https://github.com/jmarca/sports_scheduling\n", + "\n", + "\"\"\"\n", + "import argparse\n", + "import os\n", + "import re\n", + "import csv\n", + "import math\n", + "\n", + "from ortools.sat.python import cp_model\n", + "\n", + "\n", + "def csv_dump_results(solver, fixtures, num_teams, num_matchdays, csv_basename):\n", + " matchdays = range(num_matchdays)\n", + " teams = range(num_teams)\n", + "\n", + " vcsv = []\n", + " for d in matchdays:\n", + " game = 0\n", + " for home in range(num_teams):\n", + " for away in range(num_teams):\n", + " if solver.Value(fixtures[d][home][away]):\n", + " game += 1\n", + " # each row: day,game,home,away\n", + " row = {\n", + " 'day': d + 1,\n", + " 'game': game,\n", + " 'home': home + 1,\n", + " 'away': away + 1\n", + " }\n", + " vcsv.append(row)\n", + "\n", + " # check for any existing file\n", + " idx = 1\n", + " checkname = csv_basename\n", + " match = re.search(r\"\\.csv\", checkname)\n", + " if not match:\n", + " print(\n", + " 'looking for a .csv ending in passed in CSV file name. Did not find it, so appending .csv to',\n", + " csv_basename)\n", + " csv_basename += \".csv\"\n", + "\n", + " checkname = csv_basename\n", + " while os.path.exists(checkname):\n", + " checkname = re.sub(r\"\\.csv\", \"_{}.csv\".format(idx), csv_basename)\n", + " idx += 1\n", + " # or just get rid of it, but that is often undesireable\n", + " # os.unlink(csv_basename)\n", + "\n", + " with open(checkname, 'w', newline='') as csvfile:\n", + " fieldnames = ['day', 'game', 'home', 'away']\n", + " writer = csv.DictWriter(csvfile, fieldnames=fieldnames)\n", + "\n", + " writer.writeheader()\n", + " for row in vcsv:\n", + " writer.writerow(row)\n", + "\n", + "\n", + "def screen_dump_results(solver, fixtures, num_teams, num_matchdays):\n", + " matchdays = range(num_matchdays)\n", + " teams = range(num_teams)\n", + "\n", + " total_games = 0\n", + " for d in matchdays:\n", + " game = 0\n", + " for home in teams:\n", + " for away in teams:\n", + " match_on = solver.Value(fixtures[d][home][away])\n", + " if match_on:\n", + " game += 1\n", + " print('day %i game %i home %i away %i' %\n", + " (d + 1, game, home + 1, away + 1))\n", + " total_games += game\n", + "\n", + "\n", + "def assign_matches(num_teams,\n", + " num_matchdays,\n", + " num_matches_per_day,\n", + " max_home_stand,\n", + " time_limit=None,\n", + " num_cpus=None,\n", + " csv=None,\n", + " debug=None):\n", + " \"\"\"Assign matches between teams in a league.\n", + "\n", + " Keyword arguments:\n", + " num_teams -- the number of teams\n", + " num_matchdays -- the number of match days to play. Should be greater than one day. Note that if num_matchdays is exactly some multipe (`n`) of `num_teams - 1` then each team with play every other team exactly `n` times. If the number of match days is less than or greater than a perfect multiple, then some teams will not play each other `n` times.\n", + " num_matches_per_day -- how many matches can be played in a day. The assumption is one match per day, and really this code was not tested with different values.\n", + " max_home_stand -- how many home games are allowed to be in a row.\n", + " time_limit -- the time in minutes to allow the solver to work on the problem.\n", + " num_cpus -- the number of processors to use for the solution\n", + " csv -- a file name to save the output to a CSV file\n", + " debug -- boolean value stating whether to ask the solver to show its progress or not\n", + "\n", + " \"\"\"\n", + "\n", + " model = cp_model.CpModel()\n", + "\n", + " print('num_teams', num_teams, 'num_matchdays', num_matchdays,\n", + " 'num_matches_per_day', num_matches_per_day, 'max_home_stand',\n", + " max_home_stand)\n", + "\n", + " matchdays = range(num_matchdays)\n", + " matches = range(num_matches_per_day)\n", + " teams = range(num_teams)\n", + " # how many possible unique games?\n", + " unique_games = (num_teams) * (num_teams - 1) / 2\n", + "\n", + " # how many games are possible to play\n", + " total_games = num_matchdays * num_matches_per_day\n", + "\n", + " # maximum possible games versus an opponent. example, if 20\n", + " # possible total games, and 28 unique combinations, then 20 // 28\n", + " # +1 = 1. If 90 total games (say 5 per day for 18 match days) and\n", + " # 10 teams for 45 possible unique combinations of teams, then 90\n", + " # // 45 + 1 = 3. Hmm. Should be 2\n", + " matchups = int((total_games // unique_games) + 1)\n", + " # print(matchups)\n", + " # there is a special case, if total games / unique games == total\n", + " # games // unique games, then the constraint can be ==, not <=\n", + " matchups_exact = False\n", + " if (total_games % unique_games == 0):\n", + " matchups_exact = True\n", + " matchups = int(total_games // unique_games)\n", + "\n", + " print('expected matchups per pair', matchups, 'exact?', matchups_exact)\n", + "\n", + " days_to_play = int(unique_games // num_matches_per_day)\n", + " print('unique_games', unique_games, '\\nnum matches per day',\n", + " num_matches_per_day, '\\ndays to play', days_to_play,\n", + " '\\ntotal games possible', total_games)\n", + "\n", + " fixtures = [\n", + " ] # all possible games, list of lists of lists: fixture[day][iteam][jteam]\n", + " at_home = [\n", + " ] # whether or not a team plays at home on matchday, list of lists\n", + "\n", + " # Does team i receive team j at home on day d?\n", + " for d in matchdays:\n", + " # hackity hack, append a new list for all possible fixtures for a team on day d\n", + " fixtures.append([])\n", + " for i in teams:\n", + " # hackity hack, append a list of possible fixtures for team i\n", + " fixtures[d].append([])\n", + " for j in teams:\n", + " # for each possible away opponent for home team i, add a fixture\n", + " #\n", + " # note that the fixture is not only that team i plays\n", + " # team j, but also that team i is the home team and\n", + " # team j is the away team.\n", + " fixtures[d][i].append(\n", + " model.NewBoolVar(\n", + " 'fixture: home team %i, opponent %i, matchday %i' %\n", + " (i, j, d)))\n", + " if i == j:\n", + " # It is not possible for team i to play itself,\n", + " # but it is cleaner to add the fixture than it is\n", + " # to skip it---the list stays the length of the\n", + " # number of teams. The C++ version adds a \"FalseVar\" instead\n", + " model.Add(fixtures[d][i][j] == 0) # forbid playing self\n", + "\n", + " # Is team t at home on day d?\n", + " for d in matchdays:\n", + " # hackity hack, append a new list for whether or not a team is at home on day d\n", + " at_home.append([])\n", + " for i in teams:\n", + " # is team i playing at home on day d?\n", + " at_home[d].append(\n", + " model.NewBoolVar('team %i is home on matchday %i' % (i, d)))\n", + "\n", + " # each day, team t plays either home or away, but only once\n", + " for d in matchdays:\n", + " for t in teams:\n", + " # for each team, each day, list possible opponents\n", + " possible_opponents = []\n", + " for opponent in teams:\n", + " if t == opponent:\n", + " continue\n", + " # t is home possibility\n", + " possible_opponents.append(fixtures[d][t][opponent])\n", + " # t is away possibility\n", + " possible_opponents.append(fixtures[d][opponent][t])\n", + " model.Add(\n", + " sum(possible_opponents) == 1) # can only play one game per day\n", + "\n", + " # \"Each fixture happens once per season\" is not a valid constraint\n", + " # in this formulation. in the C++ program, there are exactly a\n", + " # certain number of games such that every team plays every other\n", + " # team once at home, and once away. In this case, this is not the\n", + " # case, because there are a variable number of games. Instead,\n", + " # the constraint is that each fixture happens Matchups/2 times per\n", + " # season, where Matchups is the number of times each team plays every\n", + " # other team.\n", + " fixture_repeats = int(math.ceil(matchups / 2))\n", + " print('fixture repeats expected is', fixture_repeats)\n", + "\n", + " for t in teams:\n", + " for opponent in teams:\n", + " if t == opponent:\n", + " continue\n", + " possible_days = []\n", + " for d in matchdays:\n", + " possible_days.append(fixtures[d][t][opponent])\n", + " if matchups % 2 == 0 and matchups_exact:\n", + " model.Add(sum(possible_days) == fixture_repeats)\n", + " else:\n", + " # not a hard constraint, because not exactly the right\n", + " # number of matches to be played\n", + " model.Add(sum(possible_days) <= fixture_repeats)\n", + "\n", + " # Next, each matchup between teams happens at most \"matchups\"\n", + " # times per season. Again this is different that C++ version, in\n", + " # which the number of games is such that each team plays every\n", + " # other team exactly two times. Here this is not the case,\n", + " # because the number of games in the season is not fixed.\n", + " #\n", + " # in C++ version, the season is split into two halves. In this\n", + " # case, splitting the season into \"Matchups\" sections, with each\n", + " # team playing every other team once per section.\n", + " #\n", + " # The very last section of games is special, as there might not be\n", + " # enough games for every team to play every other team once.\n", + " #\n", + " for t in teams:\n", + " for opponent in teams:\n", + " if t == opponent:\n", + " continue\n", + " prior_home = []\n", + " for m in range(matchups):\n", + " current_home = []\n", + " pairings = []\n", + " # if m = matchups - 1, then last time through\n", + " days = int(days_to_play)\n", + " if m == matchups - 1:\n", + " days = int(\n", + " min(days_to_play, num_matchdays - m * days_to_play))\n", + " # print('days',days)\n", + " for d in range(days):\n", + " theday = int(d + m * days_to_play)\n", + " # print('theday',theday)\n", + " pairings.append(fixtures[theday][t][opponent])\n", + " pairings.append(fixtures[theday][opponent][t])\n", + " # current_home.append(fixtures[theday][t][opponent])\n", + " if m == matchups - 1 and not matchups_exact:\n", + " # in the last group of games, if the number of\n", + " # games left to play isn't quite right, then it\n", + " # will not be possible for each team to play every\n", + " # other team, and so the sum will be <= 1, rather\n", + " # than == 1\n", + " #\n", + " # print('last matchup',m,'relaxed pairings constraint')\n", + " model.Add(sum(pairings) <= 1)\n", + " else:\n", + " # if it is not the last group of games, then every\n", + " # team must play every other team exactly once.\n", + " #\n", + " # print('matchup',m,'hard pairings constraint')\n", + " model.Add(sum(pairings) == 1)\n", + "\n", + " # maintain consistency between fixtures and at_home[day][team]\n", + " for d in matchdays:\n", + " for t in teams:\n", + " for opponent in teams:\n", + " if t == opponent:\n", + " continue\n", + " # if the [t][opp] fixture is true, then at home is true for t\n", + " model.AddImplication(fixtures[d][t][opponent], at_home[d][t])\n", + " # if the [t][opp] fixture is true, then at home false for opponent\n", + " model.AddImplication(fixtures[d][t][opponent],\n", + " at_home[d][opponent].Not())\n", + "\n", + " # balance home and away games via the following \"breaks\" logic\n", + " # forbid sequence of \"max_home_stand\" home games or away games in a row\n", + " # In sports like baseball, homestands can be quite long.\n", + " for t in teams:\n", + " for d in range(num_matchdays - max_home_stand):\n", + " model.AddBoolOr([\n", + " at_home[d + offset][t] for offset in range(max_home_stand + 1)\n", + " ])\n", + " model.AddBoolOr([\n", + " at_home[d + offset][t].Not()\n", + " for offset in range(max_home_stand + 1)\n", + " ])\n", + " # note, this works because AddBoolOr means at least one\n", + " # element must be true. if it was just AddBoolOr([home0,\n", + " # home1, ..., homeN]), then that would mean that one or\n", + " # all of these could be true, and you could have an\n", + " # infinite sequence of home games. However, that home\n", + " # constraint is matched with an away constraint. So the\n", + " # combination says:\n", + " #\n", + " # AddBoolOr([home0, ... homeN]) at least one of these is true\n", + " # AddBoolOr([away0, ... awayN]) at least one of these is true\n", + " #\n", + " # taken together, at least one home from 0 to N is true,\n", + " # which means at least one away0 to awayN is false. At\n", + " # the same time, at least one away is true, which means\n", + " # that the corresponding home is false. So together, this\n", + " # prevents a sequence of one more than max_home_stand to\n", + " # take place.\n", + "\n", + " # objective using breaks concept\n", + " breaks = []\n", + " for t in teams:\n", + " for d in range(num_matchdays - 1):\n", + " breaks.append(\n", + " model.NewBoolVar(\n", + " 'two home or two away for team %i, starting on matchday %i'\n", + " % (t, d)))\n", + "\n", + " model.AddBoolOr([at_home[d][t], at_home[d + 1][t], breaks[-1]])\n", + " model.AddBoolOr(\n", + " [at_home[d][t].Not(), at_home[d + 1][t].Not(), breaks[-1]])\n", + "\n", + " model.AddBoolOr(\n", + " [at_home[d][t].Not(), at_home[d + 1][t], breaks[-1].Not()])\n", + " model.AddBoolOr(\n", + " [at_home[d][t], at_home[d + 1][t].Not(), breaks[-1].Not()])\n", + "\n", + " # I couldn't figure this out, so I wrote a little program\n", + " # and proved it. These effectively are identical to\n", + " #\n", + " # model.Add(at_home[d][t] == at_home[d+1][t]).OnlyEnforceIf(breaks[-1])\n", + " # model.Add(at_home[d][t] != at_home[d+1][t]).OnlyEnforceIf(breaks[-1].Not())\n", + " #\n", + " # except they are a little more efficient, I believe. Wrote it up in a blog post\n", + " #\n", + " # my write-up is at https://activimetrics.com/blog/ortools/cp_sat/addboolor/\n", + "\n", + " # constrain breaks\n", + " #\n", + " # Another deviation from the C++ code. In the C++, the breaks sum is\n", + " # required to be >= (2 * num_teams - 4), which is exactly the number\n", + " # of match days.\n", + " #\n", + " # I said on the mailing list that I didn't know why this was, and\n", + " # Laurent pointed out that there was a known bound. I looked it\n", + " # up and found some references (see thread at\n", + " # https://groups.google.com/d/msg/or-tools-discuss/ITdlPs6oRaY/FvwgB5LgAQAJ)\n", + " #\n", + " # The reference states that \"schedules with n teams with even n\n", + " # have at least n − 2 breaks\", and further that \"for odd n\n", + " # schedules without any breaks are constructed.\"\n", + " #\n", + " # That research doesn't *quite* apply here, as the authors were\n", + " # assuming a single round-robin tournament\n", + " #.\n", + " # Here there is not an exact round-robin tournament multiple, but\n", + " # still the implication is that the number of breaks cannot be\n", + " # less than the number of matchdays.\n", + " #\n", + " # Literature aside, I'm finding in practice that if you don't have\n", + " # an exact round robin multiple, and if num_matchdays is odd, the\n", + " # best you can do is num_matchdays + 1. If you have even days,\n", + " # then you can do num_matchdays. This can be tested for small\n", + " # numbers of teams and days, in which the solver is able to search\n", + " # all possible combinations in a reasonable time limit.\n", + "\n", + " optimal_value = matchups * (num_teams - 2)\n", + " if not matchups_exact:\n", + " # fiddle a bit, based on experiments with low values of N and D\n", + " if num_matchdays % 2:\n", + " # odd number of days\n", + " optimal_value = min(num_matchdays + 1, optimal_value)\n", + " else:\n", + " optimal_value = min(num_matchdays, optimal_value)\n", + "\n", + " print('expected optimal value is', optimal_value)\n", + " model.Add(sum(breaks) >= optimal_value)\n", + "\n", + " model.Minimize(sum(breaks))\n", + " # run the solver\n", + " solver = cp_model.CpSolver()\n", + " solver.parameters.max_time_in_seconds = time_limit\n", + " solver.parameters.log_search_progress = debug\n", + " solver.parameters.num_search_workers = num_cpus\n", + "\n", + " # solution_printer = SolutionPrinter() # since we stop at first\n", + " # solution, this isn't really\n", + " # necessary I think\n", + " status = solver.Solve(model)\n", + " print('Solve status: %s' % solver.StatusName(status))\n", + " print('Statistics')\n", + " print(' - conflicts : %i' % solver.NumConflicts())\n", + " print(' - branches : %i' % solver.NumBranches())\n", + " print(' - wall time : %f s' % solver.WallTime())\n", + "\n", + " if status == cp_model.INFEASIBLE:\n", + " return status\n", + "\n", + " if status == cp_model.UNKNOWN:\n", + " print('Not enough time allowed to compute a solution')\n", + " print('Add more time using the --timelimit command line option')\n", + " return status\n", + "\n", + " print('Optimal objective value: %i' % solver.ObjectiveValue())\n", + "\n", + " screen_dump_results(solver, fixtures, num_teams, num_matchdays)\n", + "\n", + " if status != cp_model.OPTIMAL and solver.WallTime() >= time_limit:\n", + " print('Please note that solver reached maximum time allowed %i.' %\n", + " time_limit)\n", + " print(\n", + " 'A better solution than %i might be found by adding more time using the --timelimit command line option'\n", + " % solver.ObjectiveValue())\n", + "\n", + " if csv:\n", + " csv_dump_results(solver, fixtures, num_teams, num_matchdays, csv)\n", + "\n", + " # # print break results, to get a clue what they are doing\n", + " # print('Breaks')\n", + " # for b in breaks:\n", + " # print(' %s is %i' % (b.Name(), solver.Value(b)))\n", + "\n", + "\n", + "\"\"\"Entry point of the program.\"\"\"\n", + "parser = argparse.ArgumentParser(\n", + " description='Solve sports league match play assignment problem')\n", + "parser.add_argument('-t,--teams',\n", + " type=int,\n", + " dest='num_teams',\n", + " required=True,\n", + " help='Number of teams in the league')\n", + "\n", + "parser.add_argument(\n", + " '-d,--days',\n", + " type=int,\n", + " dest='num_matchdays',\n", + " required=True,\n", + " help=\n", + " 'Number of days on which matches are played. Default is enough days such that every team can play every other team, or (number of teams - 1)'\n", + ")\n", + "\n", + "parser.add_argument(\n", + " '--matches_per_day',\n", + " type=int,\n", + " dest='num_matches_per_day',\n", + " help=\n", + " 'Number of matches played per day. Default is number of teams divided by 2. If greater than the number of teams, then this implies some teams will play each other more than once. In that case, home and away should alternate between the teams in repeated matchups.'\n", + ")\n", + "\n", + "parser.add_argument(\n", + " '--csv',\n", + " type=str,\n", + " dest='csv',\n", + " default='output.csv',\n", + " help='A file to dump the team assignments. Default is output.csv')\n", + "\n", + "parser.add_argument(\n", + " '--timelimit',\n", + " type=int,\n", + " dest='time_limit',\n", + " default=60,\n", + " help='Maximum run time for solver, in seconds. Default is 60 seconds.')\n", + "\n", + "parser.add_argument(\n", + " '--cpu',\n", + " type=int,\n", + " dest='cpu',\n", + " help=\n", + " 'Number of workers (CPUs) to use for solver. Default is 6 or number of CPUs available, whichever is lower'\n", + ")\n", + "\n", + "parser.add_argument('--debug',\n", + " action='store_true',\n", + " help=\"Turn on some print statements.\")\n", + "\n", + "parser.add_argument(\n", + " '--max_home_stand',\n", + " type=int,\n", + " dest='max_home_stand',\n", + " default=2,\n", + " help=\n", + " \"Maximum consecutive home or away games. Default to 2, which means three home or away games in a row is forbidden.\"\n", + ")\n", + "\n", + "args = parser.parse_args()\n", + "\n", + "# set default for num_matchdays\n", + "num_matches_per_day = args.num_matches_per_day\n", + "if not num_matches_per_day:\n", + " num_matches_per_day = args.num_teams // 2\n", + "\n", + "ncpu = len(os.sched_getaffinity(0))\n", + "cpu = args.cpu\n", + "if not cpu:\n", + " cpu = min(6, ncpu)\n", + " print('Setting number of search workers to %i' % cpu)\n", + "\n", + "if cpu > ncpu:\n", + " print(\n", + " 'You asked for %i workers to be used, but the os only reports %i CPUs available. This might slow down processing'\n", + " % (cpu, ncpu))\n", + "\n", + "if cpu != 6:\n", + " # don't whinge at user if cpu is set to 6\n", + " if cpu < ncpu:\n", + " print(\n", + " 'Using %i workers, but there are %i CPUs available. You might get faster results by using the command line option --cpu %i, but be aware ORTools CP-SAT solver is tuned to 6 CPUs'\n", + " % (cpu, ncpu, ncpu))\n", + "\n", + " if cpu > 6:\n", + " print(\n", + " 'Using %i workers. Be aware ORTools CP-SAT solver is tuned to 6 CPUs'\n", + " % cpu)\n", + "\n", + "# assign_matches()\n", + "assign_matches(args.num_teams, args.num_matchdays, num_matches_per_day,\n", + " args.max_home_stand, args.time_limit, cpu, args.csv,\n", + " args.debug)\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/contrib/stable_marriage.ipynb b/examples/notebook/contrib/stable_marriage.ipynb new file mode 100644 index 0000000000..2c6f49097a --- /dev/null +++ b/examples/notebook/contrib/stable_marriage.ipynb @@ -0,0 +1,195 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010 Hakan Kjellerstrand hakank@gmail.com\n", + "#\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"\n", + "\n", + " Stable marriage problem in Google CP Solver.\n", + "\n", + " Problem and OPL model from Pascal Van Hentenryck\n", + " 'The OPL Optimization Programming Language', page 43ff.\n", + "\n", + " Also, see\n", + " http://www.comp.rgu.ac.uk/staff/ha/ZCSP/additional_problems/stable_marriage/stable_marriage.pdf\n", + "\n", + " Note: This model is translated from my Comet model\n", + " http://www.hakank.org/comet/stable_marriage.co\n", + " I have kept some of the constraint from that code.\n", + "\n", + " Compare with the following models:\n", + " * MiniZinc: http://www.hakank.org/minizinc/stable_marriage.mzn\n", + " * Comet : http://www.hakank.org/comet/stable_marriage.co\n", + " * ECLiPSe : http://www.hakank.org/eclipse/stable_marriage.ecl\n", + " * Gecode : http://hakank.org/gecode/stable_marriage.cpp\n", + " * SICStus : http://hakank.org/sicstus/stable_marriage.pl\n", + "\n", + " This model was created by Hakan Kjellerstrand (hakank@gmail.com)\n", + " Also see my other Google CP Solver models:\n", + " http://www.hakank.org/google_or_tools/\n", + "\n", + "\"\"\"\n", + "from __future__ import print_function\n", + "import sys\n", + "\n", + "from ortools.constraint_solver import pywrapcp\n", + "\n", + "\n", + "\n", + "# Create the solver\n", + "solver = pywrapcp.Solver(\"Stable marriage\")\n", + "\n", + "#\n", + "# data\n", + "#\n", + "print(\"Problem name:\", problem_name)\n", + "\n", + "rankMen = ranks[\"rankMen\"]\n", + "rankWomen = ranks[\"rankWomen\"]\n", + "\n", + "n = len(rankMen)\n", + "\n", + "#\n", + "# declare variables\n", + "#\n", + "wife = [solver.IntVar(0, n - 1, \"wife[%i]\" % i) for i in range(n)]\n", + "husband = [solver.IntVar(0, n - 1, \"husband[%i]\" % i) for i in range(n)]\n", + "\n", + "#\n", + "# constraints\n", + "#\n", + "\n", + "# forall(m in Men)\n", + "# cp.post(husband[wife[m]] == m);\n", + "for m in range(n):\n", + " solver.Add(solver.Element(husband, wife[m]) == m)\n", + "\n", + "# forall(w in Women)\n", + "# cp.post(wife[husband[w]] == w);\n", + "for w in range(n):\n", + " solver.Add(solver.Element(wife, husband[w]) == w)\n", + "\n", + "# forall(m in Men, o in Women)\n", + "# cp.post(rankMen[m,o] < rankMen[m, wife[m]] => rankWomen[o,husband[o]] <\n", + "# rankWomen[o,m]);\n", + "for m in range(n):\n", + " for o in range(n):\n", + " b1 = solver.IsGreaterCstVar(\n", + " solver.Element(rankMen[m], wife[m]), rankMen[m][o])\n", + " b2 = (\n", + " solver.IsLessCstVar(\n", + " solver.Element(rankWomen[o], husband[o]), rankWomen[o][m]))\n", + " solver.Add(b1 - b2 <= 0)\n", + "\n", + "# forall(w in Women, o in Men)\n", + "# cp.post(rankWomen[w,o] < rankWomen[w,husband[w]] => rankMen[o,wife[o]] <\n", + "# rankMen[o,w]);\n", + "for w in range(n):\n", + " for o in range(n):\n", + " b1 = solver.IsGreaterCstVar(\n", + " solver.Element(rankWomen[w], husband[w]), rankWomen[w][o])\n", + " b2 = solver.IsLessCstVar(\n", + " solver.Element(rankMen[o], wife[o]), rankMen[o][w])\n", + " solver.Add(b1 - b2 <= 0)\n", + "\n", + "#\n", + "# solution and search\n", + "#\n", + "solution = solver.Assignment()\n", + "solution.Add(wife)\n", + "solution.Add(husband)\n", + "\n", + "db = solver.Phase(wife + husband, solver.CHOOSE_FIRST_UNBOUND,\n", + " solver.ASSIGN_MIN_VALUE)\n", + "\n", + "solver.NewSearch(db)\n", + "num_solutions = 0\n", + "solutions = []\n", + "while solver.NextSolution():\n", + " # solutions.append([x[i].Value() for i in range(x_len)])\n", + " print(\"wife : \", [wife[i].Value() for i in range(n)])\n", + " print(\"husband: \", [husband[i].Value() for i in range(n)])\n", + " print()\n", + " num_solutions += 1\n", + "\n", + "solver.EndSearch()\n", + "\n", + "print()\n", + "print(\"num_solutions:\", num_solutions)\n", + "print(\"failures:\", solver.Failures())\n", + "print(\"branches:\", solver.Branches())\n", + "print(\"WallTime:\", solver.WallTime())\n", + "print(\"#############\")\n", + "print()\n", + "\n", + "\n", + "#\n", + "# From Van Hentenryck's OPL book\n", + "#van_hentenryck = {\n", + " \"rankWomen\": [[1, 2, 4, 3, 5], [3, 5, 1, 2, 4], [5, 4, 2, 1, 3],\n", + " [1, 3, 5, 4, 2], [4, 2, 3, 5, 1]],\n", + " \"rankMen\": [[5, 1, 2, 4, 3], [4, 1, 3, 2, 5], [5, 3, 2, 4, 1],\n", + " [1, 5, 4, 3, 2], [4, 3, 2, 1, 5]]\n", + "}\n", + "\n", + "#\n", + "# Data from MathWorld\n", + "# http://mathworld.wolfram.com/StableMarriageProblem.html\n", + "#\n", + "mathworld = {\n", + " \"rankWomen\": [[3, 1, 5, 2, 8, 7, 6, 9, 4], [9, 4, 8, 1, 7, 6, 3, 2, 5],\n", + " [3, 1, 8, 9, 5, 4, 2, 6, 7], [8, 7, 5, 3, 2, 6, 4, 9, 1],\n", + " [6, 9, 2, 5, 1, 4, 7, 3, 8], [2, 4, 5, 1, 6, 8, 3, 9, 7],\n", + " [9, 3, 8, 2, 7, 5, 4, 6, 1], [6, 3, 2, 1, 8, 4, 5, 9, 7],\n", + " [8, 2, 6, 4, 9, 1, 3, 7, 5]],\n", + " \"rankMen\": [[7, 3, 8, 9, 6, 4, 2, 1, 5], [5, 4, 8, 3, 1, 2, 6, 7, 9],\n", + " [4, 8, 3, 9, 7, 5, 6, 1, 2], [9, 7, 4, 2, 5, 8, 3, 1, 6],\n", + " [2, 6, 4, 9, 8, 7, 5, 1, 3], [2, 7, 8, 6, 5, 3, 4, 1, 9],\n", + " [1, 6, 2, 3, 8, 5, 4, 9, 7], [5, 6, 9, 1, 2, 8, 4, 3, 7],\n", + " [6, 1, 4, 7, 5, 8, 3, 9, 2]]\n", + "}\n", + "\n", + "#\n", + "# Data from\n", + "# http://www.csee.wvu.edu/~ksmani/courses/fa01/random/lecnotes/lecture5.pdf\n", + "#\n", + "problem3 = {\n", + " \"rankWomen\": [[1, 2, 3, 4], [4, 3, 2, 1], [1, 2, 3, 4], [3, 4, 1, 2]],\n", + " \"rankMen\": [[1, 2, 3, 4], [2, 1, 3, 4], [1, 4, 3, 2], [4, 3, 1, 2]]\n", + "}\n", + "\n", + "#\n", + "# Data from\n", + "# http://www.comp.rgu.ac.uk/staff/ha/ZCSP/additional_problems/stable_marriage/stable_marriage.pdf\n", + "# page 4\n", + "#\n", + "problem4 = {\n", + " \"rankWomen\": [[1, 5, 4, 6, 2, 3], [4, 1, 5, 2, 6, 3], [6, 4, 2, 1, 5, 3],\n", + " [1, 5, 2, 4, 3, 6], [4, 2, 1, 5, 6, 3], [2, 6, 3, 5, 1, 4]],\n", + " \"rankMen\": [[1, 4, 2, 5, 6, 3], [3, 4, 6, 1, 5, 2], [1, 6, 4, 2, 3, 5],\n", + " [6, 5, 3, 4, 2, 1], [3, 1, 2, 4, 5, 6], [2, 3, 1, 6, 5, 4]]\n", + "}\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/contrib/stable_marriage_sat.ipynb b/examples/notebook/contrib/stable_marriage_sat.ipynb new file mode 100644 index 0000000000..d4fab40e46 --- /dev/null +++ b/examples/notebook/contrib/stable_marriage_sat.ipynb @@ -0,0 +1,112 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2018 Gergo Rozner\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http:#www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"Solves the stable marriage problem in CP-SAT.\"\"\"\n", + "\n", + "from ortools.sat.python import cp_model\n", + "\n", + "\n", + "class SolutionPrinter(cp_model.CpSolverSolutionCallback):\n", + "\n", + " def __init__(self, wife, husband):\n", + " cp_model.CpSolverSolutionCallback.__init__(self)\n", + " self.__wife = wife\n", + " self.__husband = husband\n", + " self.__solution_count = 0\n", + " self.__n = len(wife)\n", + "\n", + " def OnSolutionCallback(self):\n", + " self.__solution_count += 1\n", + " print(\"Solution %i\" % self.__solution_count)\n", + " print(\"wife : \", [self.Value(self.__wife[i]) for i in range(self.__n)])\n", + " print(\"husband: \", [self.Value(self.__husband[i]) for i in range(self.__n)])\n", + " print()\n", + "\n", + " def SolutionCount(self):\n", + " return self.__solution_count\n", + "\n", + "\n", + "mrank = ranks[\"rankMen\"]\n", + "wrank = ranks[\"rankWomen\"]\n", + "\n", + "n = pair_num\n", + "\n", + "model = cp_model.CpModel()\n", + "\n", + "wife = [model.NewIntVar(0, n - 1, \"wife[%i]\" % i) for i in range(n)]\n", + "husband = [model.NewIntVar(0, n - 1, \"husband[%i]\" % i) for i in range(n)]\n", + "\n", + "for m in range(n):\n", + " model.AddElement(wife[m], husband, m)\n", + "\n", + "for w in range(n):\n", + " model.AddElement(husband[w], wife, w)\n", + "\n", + "#mrank[w][m] < mrank[w][husband[w]] => wrank[m][wife[m]] < wrank[m][w]\n", + "for w in range(n):\n", + " for m in range(n):\n", + " husband_rank = model.NewIntVar(1, n, \"\")\n", + " model.AddElement(husband[w], mrank[w], husband_rank)\n", + "\n", + " wife_rank = model.NewIntVar(1, n, \"\")\n", + " model.AddElement(wife[m], wrank[m], wife_rank)\n", + "\n", + " husband_dominated = model.NewBoolVar(\"\")\n", + " model.Add(mrank[w][m] < husband_rank).OnlyEnforceIf(husband_dominated)\n", + " model.Add(mrank[w][m] >= husband_rank).OnlyEnforceIf(\n", + " husband_dominated.Not())\n", + "\n", + " wife_dominates = model.NewBoolVar(\"\")\n", + " model.Add(wife_rank < wrank[m][w]).OnlyEnforceIf(wife_dominates)\n", + " model.Add(wife_rank >= wrank[m][w]).OnlyEnforceIf(wife_dominates.Not())\n", + "\n", + " model.AddImplication(husband_dominated, wife_dominates)\n", + "\n", + "#wrank[m][w] < wrank[m][wife[m]] => mrank[w][husband[w]] < mrank[w][m]\n", + "for m in range(n):\n", + " for w in range(n):\n", + " wife_rank = model.NewIntVar(1, n, \"\")\n", + " model.AddElement(wife[m], wrank[m], wife_rank)\n", + "\n", + " husband_rank = model.NewIntVar(1, n, \"\")\n", + " model.AddElement(husband[w], mrank[w], husband_rank)\n", + "\n", + " wife_dominated = model.NewBoolVar(\"\")\n", + " model.Add(wrank[m][w] < wife_rank).OnlyEnforceIf(wife_dominated)\n", + " model.Add(wrank[m][w] >= wife_rank).OnlyEnforceIf(wife_dominated.Not())\n", + "\n", + " husband_dominates = model.NewBoolVar(\"\")\n", + " model.Add(husband_rank < mrank[w][m]).OnlyEnforceIf(husband_dominates)\n", + " model.Add(husband_rank >= mrank[w][m]).OnlyEnforceIf(\n", + " husband_dominates.Not())\n", + "\n", + " model.AddImplication(wife_dominated, husband_dominates)\n", + "\n", + "solver = cp_model.CpSolver()\n", + "solution_printer = SolutionPrinter(wife, husband)\n", + "solver.SearchForAllSolutions(model, solution_printer)\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/contrib/steel.ipynb b/examples/notebook/contrib/steel.ipynb new file mode 100644 index 0000000000..97938fba85 --- /dev/null +++ b/examples/notebook/contrib/steel.ipynb @@ -0,0 +1,200 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010 Pierre Schaus pschaus@gmail.com, lperron@google.com\n", + "#\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\n", + "from __future__ import print_function\n", + "import argparse\n", + "from ortools.constraint_solver import pywrapcp\n", + "\n", + "parser = argparse.ArgumentParser()\n", + "parser.add_argument(\n", + " '--data',\n", + " default='examples/data/steel_mill/steel_mill_slab.txt',\n", + " help='path to data file')\n", + "parser.add_argument(\n", + " '--time_limit', default=20000, type=int, help='global time limit')\n", + "\n", + "#----------------helper for binpacking posting----------------\n", + "\n", + "\n", + "def BinPacking(solver, binvars, weights, loadvars):\n", + " \"\"\"post the load constraint on bins.\n", + "\n", + " constraints forall j: loadvars[j] == sum_i (binvars[i] == j) * weights[i])\n", + " \"\"\"\n", + " pack = solver.Pack(binvars, len(binvars))\n", + " pack.AddWeightedSumEqualVarDimension(weights, loadvars)\n", + " solver.Add(pack)\n", + " solver.Add(solver.SumEquality(loadvars, sum(weights)))\n", + "\n", + "\n", + "#------------------------------data reading-------------------\n", + "\n", + "\n", + "def ReadData(filename):\n", + " \"\"\"Read data from .\"\"\"\n", + " f = open(filename)\n", + " capacity = [int(nb) for nb in f.readline().split()]\n", + " capacity.pop(0)\n", + " capacity = [0] + capacity\n", + " max_capacity = max(capacity)\n", + " nb_colors = int(f.readline())\n", + " nb_slabs = int(f.readline())\n", + " wc = [[int(j) for j in f.readline().split()] for i in range(nb_slabs)]\n", + " weights = [x[0] for x in wc]\n", + " colors = [x[1] for x in wc]\n", + " loss = [\n", + " min([x for x in capacity if x >= c]) - c for c in range(max_capacity + 1)\n", + " ]\n", + " color_orders = [[o\n", + " for o in range(nb_slabs)\n", + " if colors[o] == c]\n", + " for c in range(1, nb_colors + 1)]\n", + " print('Solving steel mill with', nb_slabs, 'slabs')\n", + " return (nb_slabs, capacity, max_capacity, weights, colors, loss, color_orders)\n", + "\n", + "\n", + "#------------------dedicated search for this problem-----------\n", + "\n", + "\n", + "class SteelDecisionBuilder(pywrapcp.PyDecisionBuilder):\n", + " \"\"\"Dedicated Decision Builder for steel mill slab.\n", + "\n", + " Search for the steel mill slab problem with Dynamic Symmetry\n", + " Breaking during search is an adaptation (for binary tree) from the\n", + " paper of Pascal Van Hentenryck and Laurent Michel CPAIOR-2008.\n", + "\n", + " The value heuristic comes from the paper\n", + " Solving Steel Mill Slab Problems with Constraint-Based Techniques:\n", + " CP, LNS, and CBLS,\n", + " Schaus et. al. to appear in Constraints 2010\n", + " \"\"\"\n", + "\n", + " def __init__(self, x, nb_slabs, weights, losstab, loads):\n", + " pywrapcp.PyDecisionBuilder.__init__(self)\n", + " self.__x = x\n", + " self.__nb_slabs = nb_slabs\n", + " self.__weights = weights\n", + " self.__losstab = losstab\n", + " self.__loads = loads\n", + " self.__maxcapa = len(losstab) - 1\n", + "\n", + " def Next(self, solver):\n", + " var, weight = self.NextVar()\n", + " if var:\n", + " v = self.MaxBound()\n", + " if v + 1 == var.Min():\n", + " # Symmetry breaking. If you need to assign to a new bin,\n", + " # select the first one.\n", + " solver.Add(var == v + 1)\n", + " return self.Next(solver)\n", + " else:\n", + " # value heuristic (important for difficult problem):\n", + " # try first to place the order in the slab that will induce\n", + " # the least increase of the loss\n", + " loads = self.getLoads()\n", + " l, v = min((self.__losstab[loads[i] + weight], i)\n", + " for i in range(var.Min(),\n", + " var.Max() + 1)\n", + " if var.Contains(i) and loads[i] + weight <= self.__maxcapa)\n", + " decision = solver.AssignVariableValue(var, v)\n", + " return decision\n", + " else:\n", + " return None\n", + "\n", + " def getLoads(self):\n", + " load = [0] * len(self.__loads)\n", + " for w, x in zip(self.__weights, self.__x):\n", + " if x.Bound():\n", + " load[x.Min()] += w\n", + " return load\n", + "\n", + " def MaxBound(self):\n", + " \"\"\" returns the max value bound to a variable, -1 if no variables bound\"\"\"\n", + " return max([-1] + [\n", + " self.__x[o].Min()\n", + " for o in range(self.__nb_slabs)\n", + " if self.__x[o].Bound()\n", + " ])\n", + "\n", + " def NextVar(self):\n", + " \"\"\" mindom size heuristic with tie break on the weights of orders \"\"\"\n", + " res = [(self.__x[o].Size(), -self.__weights[o], self.__x[o])\n", + " for o in range(self.__nb_slabs)\n", + " if self.__x[o].Size() > 1]\n", + " if res:\n", + " res.sort()\n", + " return res[0][2], -res[0][1] # returns the order var and its weight\n", + " else:\n", + " return None, None\n", + "\n", + " def DebugString(self):\n", + " return 'SteelMillDecisionBuilder(' + str(self.__x) + ')'\n", + "\n", + "\n", + "#------------------solver and variable declaration-------------\n", + "(nb_slabs, capacity, max_capacity, weights, colors, loss, color_orders) =\\\n", + " ReadData(args.data)\n", + "nb_colors = len(color_orders)\n", + "solver = pywrapcp.Solver('Steel Mill Slab')\n", + "x = [solver.IntVar(0, nb_slabs - 1, 'x' + str(i)) for i in range(nb_slabs)]\n", + "load_vars = [\n", + " solver.IntVar(0, max_capacity - 1, 'load_vars' + str(i))\n", + " for i in range(nb_slabs)\n", + "]\n", + "\n", + "#-------------------post of the constraints--------------\n", + "\n", + "# Bin Packing.\n", + "BinPacking(solver, x, weights, load_vars)\n", + "# At most two colors per slab.\n", + "for s in range(nb_slabs):\n", + " solver.Add(\n", + " solver.SumLessOrEqual([\n", + " solver.Max([solver.IsEqualCstVar(x[c], s)\n", + " for c in o])\n", + " for o in color_orders\n", + " ], 2))\n", + "\n", + "#----------------Objective-------------------------------\n", + "\n", + "objective_var = \\\n", + " solver.Sum([load_vars[s].IndexOf(loss) for s in range(nb_slabs)]).Var()\n", + "objective = solver.Minimize(objective_var, 1)\n", + "\n", + "#------------start the search and optimization-----------\n", + "\n", + "db = SteelDecisionBuilder(x, nb_slabs, weights, loss, load_vars)\n", + "search_log = solver.SearchLog(100000, objective_var)\n", + "global_limit = solver.TimeLimit(args.time_limit)\n", + "solver.NewSearch(db, [objective, search_log, global_limit])\n", + "while solver.NextSolution():\n", + " print('Objective:', objective_var.Value(),\\\n", + " 'check:', sum(loss[load_vars[s].Min()] for s in range(nb_slabs)))\n", + "solver.EndSearch()\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/contrib/steel_lns.ipynb b/examples/notebook/contrib/steel_lns.ipynb new file mode 100644 index 0000000000..46acf1c3bb --- /dev/null +++ b/examples/notebook/contrib/steel_lns.ipynb @@ -0,0 +1,272 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010 Pierre Schaus pschaus@gmail.com, lperron@google.com\n", + "#\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "from __future__ import print_function\n", + "import argparse\n", + "from ortools.constraint_solver import pywrapcp\n", + "import random\n", + "\n", + "parser = argparse.ArgumentParser()\n", + "\n", + "parser.add_argument(\n", + " '--data',\n", + " default='examples/data/steel_mill/steel_mill_slab.txt',\n", + " help='path to data file')\n", + "parser.add_argument(\n", + " '--time_limit', default=20000, type=int, help='global time limit')\n", + "parser.add_argument(\n", + " '--lns_fragment_size',\n", + " default=10,\n", + " type=int,\n", + " help='size of the random lns fragment')\n", + "parser.add_argument(\n", + " '--lns_random_seed',\n", + " default=0,\n", + " type=int,\n", + " help='seed for the lns random generator')\n", + "parser.add_argument(\n", + " '--lns_fail_limit',\n", + " default=30,\n", + " type=int,\n", + " help='fail limit when exploring fragments')\n", + "\n", + "# ---------- helper for binpacking posting ----------\n", + "\n", + "\n", + "def BinPacking(solver, binvars, weights, loadvars):\n", + " \"\"\"post the load constraint on bins.\n", + "\n", + " constraints forall j: loadvars[j] == sum_i (binvars[i] == j) * weights[i])\n", + " \"\"\"\n", + " pack = solver.Pack(binvars, len(binvars))\n", + " pack.AddWeightedSumEqualVarDimension(weights, loadvars)\n", + " solver.Add(pack)\n", + " solver.Add(solver.SumEquality(loadvars, sum(weights)))\n", + "\n", + "\n", + "# ---------- data reading ----------\n", + "\n", + "\n", + "def ReadData(filename):\n", + " \"\"\"Read data from .\"\"\"\n", + " f = open(filename)\n", + " capacity = [int(nb) for nb in f.readline().split()]\n", + " capacity.pop(0)\n", + " capacity = [0] + capacity\n", + " max_capacity = max(capacity)\n", + " nb_colors = int(f.readline())\n", + " nb_slabs = int(f.readline())\n", + " wc = [[int(j) for j in f.readline().split()] for i in range(nb_slabs)]\n", + " weights = [x[0] for x in wc]\n", + " colors = [x[1] for x in wc]\n", + " loss = [\n", + " min([x for x in capacity if x >= c]) - c for c in range(max_capacity + 1)\n", + " ]\n", + " color_orders = [[o\n", + " for o in range(nb_slabs)\n", + " if colors[o] == c]\n", + " for c in range(1, nb_colors + 1)]\n", + " print('Solving steel mill with', nb_slabs, 'slabs')\n", + " return (nb_slabs, capacity, max_capacity, weights, colors, loss, color_orders)\n", + "\n", + "\n", + "# ---------- dedicated search for this problem ----------\n", + "\n", + "\n", + "class SteelDecisionBuilder(pywrapcp.PyDecisionBuilder):\n", + " \"\"\"Dedicated Decision Builder for steel mill slab.\n", + "\n", + " Search for the steel mill slab problem with Dynamic Symmetry\n", + " Breaking during search is an adaptation (for binary tree) from the\n", + " paper of Pascal Van Hentenryck and Laurent Michel CPAIOR-2008.\n", + "\n", + " The value heuristic comes from the paper\n", + " Solving Steel Mill Slab Problems with Constraint-Based Techniques:\n", + " CP, LNS, and CBLS,\n", + " Schaus et. al. to appear in Constraints 2010\n", + " \"\"\"\n", + "\n", + " def __init__(self, x, nb_slabs, weights, loss_array, loads):\n", + " pywrapcp.PyDecisionBuilder.__init__(self)\n", + " self.__x = x\n", + " self.__nb_slabs = nb_slabs\n", + " self.__weights = weights\n", + " self.__loss_array = loss_array\n", + " self.__loads = loads\n", + " self.__max_capacity = len(loss_array) - 1\n", + "\n", + " def Next(self, solver):\n", + " var, weight = self.NextVar()\n", + " if var:\n", + " v = self.MaxBound()\n", + " if v + 1 == var.Min():\n", + " # Symmetry breaking. If you need to assign to a new bin,\n", + " # select the first one.\n", + " solver.Add(var == v + 1)\n", + " return self.Next(solver)\n", + " else:\n", + " # value heuristic (important for difficult problem):\n", + " # try first to place the order in the slab that will induce\n", + " # the least increase of the loss\n", + " loads = self.getLoads()\n", + " l, v = min(\n", + " (self.__loss_array[loads[i] + weight], i)\n", + " for i in range(var.Min(),\n", + " var.Max() + 1)\n", + " if var.Contains(i) and loads[i] + weight <= self.__max_capacity)\n", + " decision = solver.AssignVariableValue(var, v)\n", + " return decision\n", + " else:\n", + " return None\n", + "\n", + " def getLoads(self):\n", + " load = [0] * len(self.__loads)\n", + " for (w, x) in zip(self.__weights, self.__x):\n", + " if x.Bound():\n", + " load[x.Min()] += w\n", + " return load\n", + "\n", + " def MaxBound(self):\n", + " \"\"\" returns the max value bound to a variable, -1 if no variables bound\"\"\"\n", + " return max([-1] + [\n", + " self.__x[o].Min()\n", + " for o in range(self.__nb_slabs)\n", + " if self.__x[o].Bound()\n", + " ])\n", + "\n", + " def NextVar(self):\n", + " \"\"\" mindom size heuristic with tie break on the weights of orders \"\"\"\n", + " res = [(self.__x[o].Size(), -self.__weights[o], self.__x[o])\n", + " for o in range(self.__nb_slabs)\n", + " if self.__x[o].Size() > 1]\n", + " if res:\n", + " res.sort()\n", + " return (res[0][2], -res[0][1]) # returns the order var and its weight\n", + " else:\n", + " return (None, None)\n", + "\n", + " def DebugString(self):\n", + " return 'SteelMillDecisionBuilder(' + str(self.__x) + ')'\n", + "\n", + "\n", + "# ----------- LNS Operator ----------\n", + "\n", + "\n", + "class SteelRandomLns(pywrapcp.BaseLns):\n", + " \"\"\"Random LNS for Steel.\"\"\"\n", + "\n", + " def __init__(self, x, rand, lns_size):\n", + " pywrapcp.BaseLns.__init__(self, x)\n", + " self.__random = rand\n", + " self.__lns_size = lns_size\n", + "\n", + " def InitFragments(self):\n", + " pass\n", + "\n", + " def NextFragment(self):\n", + " while self.FragmentSize() < self.__lns_size:\n", + " pos = self.__random.randint(0, self.Size() - 1)\n", + " self.AppendToFragment(pos)\n", + " return True\n", + "\n", + "\n", + "# ----------- Main Function -----------\n", + "\n", + "\n", + "# ----- solver and variable declaration -----\n", + "(nb_slabs, capacity, max_capacity, weights, colors, loss, color_orders) =\\\n", + " ReadData(args.data)\n", + "nb_colors = len(color_orders)\n", + "solver = pywrapcp.Solver('Steel Mill Slab')\n", + "x = [solver.IntVar(0, nb_slabs - 1, 'x' + str(i)) for i in range(nb_slabs)]\n", + "load_vars = [\n", + " solver.IntVar(0, max_capacity - 1, 'load_vars' + str(i))\n", + " for i in range(nb_slabs)\n", + "]\n", + "\n", + "# ----- post of the constraints -----\n", + "\n", + "# Bin Packing.\n", + "BinPacking(solver, x, weights, load_vars)\n", + "# At most two colors per slab.\n", + "for s in range(nb_slabs):\n", + " solver.Add(\n", + " solver.SumLessOrEqual([\n", + " solver.Max([solver.IsEqualCstVar(x[c], s)\n", + " for c in o])\n", + " for o in color_orders\n", + " ], 2))\n", + "\n", + "# ----- Objective -----\n", + "\n", + "objective_var = \\\n", + " solver.Sum([load_vars[s].IndexOf(loss) for s in range(nb_slabs)]).Var()\n", + "objective = solver.Minimize(objective_var, 1)\n", + "\n", + "# ----- start the search and optimization -----\n", + "\n", + "assign_db = SteelDecisionBuilder(x, nb_slabs, weights, loss, load_vars)\n", + "first_solution = solver.Assignment()\n", + "first_solution.Add(x)\n", + "first_solution.AddObjective(objective_var)\n", + "store_db = solver.StoreAssignment(first_solution)\n", + "first_solution_db = solver.Compose([assign_db, store_db])\n", + "print('searching for initial solution,', end=' ')\n", + "solver.Solve(first_solution_db)\n", + "print('initial cost =', first_solution.ObjectiveValue())\n", + "\n", + "# To search a fragment, we use a basic randomized decision builder.\n", + "# We can also use assign_db instead of inner_db.\n", + "inner_db = solver.Phase(x, solver.CHOOSE_RANDOM, solver.ASSIGN_MIN_VALUE)\n", + "# The most important aspect is to limit the time exploring each fragment.\n", + "inner_limit = solver.FailuresLimit(args.lns_fail_limit)\n", + "continuation_db = solver.SolveOnce(inner_db, [inner_limit])\n", + "\n", + "# Now, we create the LNS objects.\n", + "rand = random.Random()\n", + "rand.seed(args.lns_random_seed)\n", + "local_search_operator = SteelRandomLns(x, rand, args.lns_fragment_size)\n", + "# This is in fact equivalent to the following predefined LNS operator:\n", + "# local_search_operator = solver.RandomLNSOperator(x,\n", + "# args.lns_fragment_size,\n", + "# args.lns_random_seed)\n", + "local_search_parameters = solver.LocalSearchPhaseParameters(\n", + " objective_var, local_search_operator, continuation_db)\n", + "local_search_db = solver.LocalSearchPhase(first_solution,\n", + " local_search_parameters)\n", + "global_limit = solver.TimeLimit(args.time_limit)\n", + "\n", + "print('using LNS to improve the initial solution')\n", + "\n", + "search_log = solver.SearchLog(100000, objective_var)\n", + "solver.NewSearch(local_search_db, [objective, search_log, global_limit])\n", + "while solver.NextSolution():\n", + " print('Objective:', objective_var.Value(),\\\n", + " 'check:', sum(loss[load_vars[s].Min()] for s in range(nb_slabs)))\n", + "solver.EndSearch()\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/contrib/stigler.ipynb b/examples/notebook/contrib/stigler.ipynb new file mode 100644 index 0000000000..22a1a6fff6 --- /dev/null +++ b/examples/notebook/contrib/stigler.ipynb @@ -0,0 +1,355 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2011 Hakan Kjellerstrand hakank@gmail.com\n", + "#\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"\n", + "\n", + " Original Stigler's 1939 diet problem Google or-tools.\n", + "\n", + " From GLPK:s example stigler.mod\n", + " '''\n", + " STIGLER, original Stigler's 1939 diet problem\n", + "\n", + " The Stigler Diet is an optimization problem named for George Stigler,\n", + " a 1982 Nobel Laureate in economics, who posed the following problem:\n", + " For a moderately active man weighing 154 pounds, how much of each of\n", + " 77 foods should be eaten on a daily basis so that the man's intake of\n", + " nine nutrients will be at least equal to the recommended dietary\n", + " allowances (RDSs) suggested by the National Research Council in 1943,\n", + " with the cost of the diet being minimal?\n", + "\n", + " The nutrient RDAs required to be met in Stigler's experiment were\n", + " calories, protein, calcium, iron, vitamin A, thiamine, riboflavin,\n", + " niacin, and ascorbic acid. The result was an annual budget allocated\n", + " to foods such as evaporated milk, cabbage, dried navy beans, and beef\n", + " liver at a cost of approximately $0.11 a day in 1939 U.S. dollars.\n", + "\n", + " While the name 'Stigler Diet' was applied after the experiment by\n", + " outsiders, according to Stigler, 'No one recommends these diets for\n", + " anyone, let alone everyone.' The Stigler diet has been much ridiculed\n", + " for its lack of variety and palatability, however his methodology has\n", + " received praise and is considered to be some of the earliest work in\n", + " linear programming.\n", + "\n", + " The Stigler diet question is a linear programming problem. Lacking\n", + " any sophisticated method of solving such a problem, Stigler was\n", + " forced to utilize heuristic methods in order to find a solution. The\n", + " diet question originally asked in which quantities a 154 pound male\n", + " would have to consume 77 different foods in order to fulfill the\n", + " recommended intake of 9 different nutrients while keeping expense at\n", + " a minimum. Through 'trial and error, mathematical insight and\n", + " agility,' Stigler was able to eliminate 62 of the foods from the\n", + " original 77 (these foods were removed based because they lacked\n", + " nutrients in comparison to the remaining 15). From the reduced list,\n", + " Stigler calculated the required amounts of each of the remaining 15\n", + " foods to arrive at a cost-minimizing solution to his question.\n", + " According to Stigler's calculations, the annual cost of his solution\n", + " was $39.93 in 1939 dollars. When corrected for inflation using the\n", + " consumer price index, the cost of the diet in 2005 dollars is\n", + " $561.43. The specific combination of foods and quantities is as\n", + " follows:\n", + "\n", + " Stigler's 1939 Diet\n", + "\n", + " Food Annual Quantities Annual Cost\n", + " ---------------- ----------------- -----------\n", + " Wheat Flour 370 lb. $13.33\n", + " Evaporated Milk 57 cans 3.84\n", + " Cabbage 111 lb. 4.11\n", + " Spinach 23 lb. 1.85\n", + " Dried Navy Beans 285 lb. 16.80\n", + " ----------------------------------------------\n", + " Total Annual Cost $39.93\n", + "\n", + " The 9 nutrients that Stigler's diet took into consideration and their\n", + " respective recommended daily amounts were:\n", + "\n", + " Table of nutrients considered in Stigler's diet\n", + "\n", + " Nutrient Daily Recommended Intake\n", + " ------------------------- ------------------------\n", + " Calories 3,000 Calories\n", + " Protein 70 grams\n", + " Calcium .8 grams\n", + " Iron 12 milligrams\n", + " Vitamin A 5,000 IU\n", + " Thiamine (Vitamin B1) 1.8 milligrams\n", + " Riboflavin (Vitamin B2) 2.7 milligrams\n", + " Niacin 18 milligrams\n", + " Ascorbic Acid (Vitamin C) 75 milligrams\n", + "\n", + " Seven years after Stigler made his initial estimates, the development\n", + " of George Dantzig's Simplex algorithm made it possible to solve the\n", + " problem without relying on heuristic methods. The exact value was\n", + " determined to be $39.69 (using the original 1939 data). Dantzig's\n", + " algorithm describes a method of traversing the vertices of a polytope\n", + " of N+1 dimensions in order to find the optimal solution to a specific\n", + " situation.\n", + "\n", + " (From Wikipedia, the free encyclopedia.)\n", + "\n", + " Translated from GAMS by Andrew Makhorin .\n", + "\n", + " For the original GAMS model stigler1939.gms see [3].\n", + "\n", + " References:\n", + "\n", + " 1. George J. Stigler, 'The Cost of Subsistence,' J. Farm Econ. 27,\n", + " 1945, pp. 303-14.\n", + "\n", + " 2. National Research Council, 'Recommended Daily Allowances,' Reprint\n", + " and Circular Series No. 115, January, 1943.\n", + "\n", + " 3. Erwin Kalvelagen, 'Model building with GAMS,' Chapter 2, 'Building\n", + " linear programming models,' pp. 128-34.\n", + " '''\n", + "\n", + " This model was created by Hakan Kjellerstrand (hakank@gmail.com)\n", + " Also see my other Google CP Solver models:\n", + " http://www.hakank.org/google_or_tools/\n", + "\"\"\"\n", + "from __future__ import print_function\n", + "import sys\n", + "from ortools.linear_solver import pywraplp\n", + "\n", + "\n", + "\n", + "# Create the solver.\n", + "\n", + "print(\"Solver: \", sol)\n", + "\n", + "# using GLPK\n", + "if sol == \"GLPK\":\n", + " solver = pywraplp.Solver(\"CoinsGridGLPK\",\n", + " pywraplp.Solver.GLPK_MIXED_INTEGER_PROGRAMMING)\n", + "else:\n", + " # Using CLP\n", + " solver = pywraplp.Solver(\"CoinsGridCLP\",\n", + " pywraplp.Solver.CBC_MIXED_INTEGER_PROGRAMMING)\n", + "\n", + "#\n", + "# data\n", + "#\n", + "# commodities\n", + "num_commodities = 77\n", + "C = list(range(num_commodities))\n", + "\n", + "# days in a year\n", + "days = 365.25\n", + "\n", + "# nutrients\n", + "num_nutrients = 9\n", + "N = list(range(num_nutrients))\n", + "\n", + "nutrients = [\n", + " \"calories\", # Calories, unit = 1000\n", + " \"protein\", # Protein, unit = grams\n", + " \"calcium\", # Calcium, unit = grams\n", + " \"iron\", # Iron, unit = milligrams\n", + " \"vitaminA\", # Vitamin A, unit = 1000 International Units\n", + " \"thiamine\", # Thiamine, Vit. B1, unit = milligrams\n", + " \"riboflavin\", # Riboflavin, Vit. B2, unit = milligrams\n", + " \"niacin\", # Niacin (Nicotinic Acid), unit = milligrams\n", + " \"ascorbicAcid\" # Ascorbic Acid, Vit. C, unit = milligrams\n", + "]\n", + "\n", + "commodities = [[\"Wheat Flour (Enriched)\", \"10 lb.\"], [\"Macaroni\", \"1 lb.\"],\n", + " [\"Wheat Cereal (Enriched)\",\n", + " \"28 oz.\"], [\"Corn Flakes\", \"8 oz.\"], [\"Corn Meal\", \"1 lb.\"],\n", + " [\"Hominy Grits\", \"24 oz.\"], [\"Rice\", \"1 lb.\"],\n", + " [\"Rolled Oats\", \"1 lb.\"], [\"White Bread (Enriched)\", \"1 lb.\"],\n", + " [\"Whole Wheat Bread\", \"1 lb.\"], [\"Rye Bread\", \"1 lb.\"],\n", + " [\"Pound Cake\", \"1 lb.\"], [\"Soda Crackers\", \"1 lb.\"],\n", + " [\"Milk\", \"1 qt.\"], [\"Evaporated Milk (can)\", \"14.5 oz.\"],\n", + " [\"Butter\", \"1 lb.\"], [\"Oleomargarine\", \"1 lb.\"],\n", + " [\"Eggs\", \"1 doz.\"], [\"Cheese (Cheddar)\", \"1 lb.\"],\n", + " [\"Cream\", \"1/2 pt.\"], [\"Peanut Butter\", \"1 lb.\"],\n", + " [\"Mayonnaise\", \"1/2 pt.\"], [\"Crisco\", \"1 lb.\"],\n", + " [\"Lard\", \"1 lb.\"], [\"Sirloin Steak\", \"1 lb.\"],\n", + " [\"Round Steak\", \"1 lb.\"], [\"Rib Roast\", \"1 lb.\"],\n", + " [\"Chuck Roast\", \"1 lb.\"], [\"Plate\", \"1 lb.\"],\n", + " [\"Liver (Beef)\", \"1 lb.\"], [\"Leg of Lamb\", \"1 lb.\"],\n", + " [\"Lamb Chops (Rib)\", \"1 lb.\"], [\"Pork Chops\", \"1 lb.\"],\n", + " [\"Pork Loin Roast\", \"1 lb.\"], [\"Bacon\", \"1 lb.\"],\n", + " [\"Ham - smoked\", \"1 lb.\"], [\"Salt Pork\", \"1 lb.\"],\n", + " [\"Roasting Chicken\", \"1 lb.\"], [\"Veal Cutlets\", \"1 lb.\"],\n", + " [\"Salmon, Pink (can)\", \"16 oz.\"], [\"Apples\", \"1 lb.\"],\n", + " [\"Bananas\", \"1 lb.\"], [\"Lemons\", \"1 doz.\"],\n", + " [\"Oranges\", \"1 doz.\"], [\"Green Beans\", \"1 lb.\"],\n", + " [\"Cabbage\", \"1 lb.\"], [\"Carrots\", \"1 bunch\"],\n", + " [\"Celery\", \"1 stalk\"], [\"Lettuce\", \"1 head\"],\n", + " [\"Onions\", \"1 lb.\"], [\"Potatoes\", \"15 lb.\"],\n", + " [\"Spinach\", \"1 lb.\"], [\"Sweet Potatoes\", \"1 lb.\"],\n", + " [\"Peaches (can)\", \"No. 2 1/2\"], [\"Pears (can)\", \"No. 2 1/2,\"],\n", + " [\"Pineapple (can)\", \"No. 2 1/2\"], [\"Asparagus (can)\", \"No. 2\"],\n", + " [\"Grean Beans (can)\", \"No. 2\"],\n", + " [\"Pork and Beans (can)\", \"16 oz.\"], [\"Corn (can)\", \"No. 2\"],\n", + " [\"Peas (can)\", \"No. 2\"], [\"Tomatoes (can)\", \"No. 2\"],\n", + " [\"Tomato Soup (can)\", \"10 1/2 oz.\"],\n", + " [\"Peaches, Dried\", \"1 lb.\"], [\"Prunes, Dried\", \"1 lb.\"],\n", + " [\"Raisins, Dried\", \"15 oz.\"], [\"Peas, Dried\", \"1 lb.\"],\n", + " [\"Lima Beans, Dried\", \"1 lb.\"], [\"Navy Beans, Dried\", \"1 lb.\"],\n", + " [\"Coffee\", \"1 lb.\"], [\"Tea\", \"1/4 lb.\"], [\"Cocoa\", \"8 oz.\"],\n", + " [\"Chocolate\", \"8 oz.\"], [\"Sugar\", \"10 lb.\"],\n", + " [\"Corn Sirup\", \"24 oz.\"], [\"Molasses\", \"18 oz.\"],\n", + " [\"Strawberry Preserve\", \"1 lb.\"]]\n", + "\n", + "# price and weight are the two first columns\n", + "data = [\n", + " [36.0, 12600.0, 44.7, 1411.0, 2.0, 365.0, 0.0, 55.4, 33.3, 441.0, 0.0],\n", + " [14.1, 3217.0, 11.6, 418.0, 0.7, 54.0, 0.0, 3.2, 1.9, 68.0, 0.0],\n", + " [24.2, 3280.0, 11.8, 377.0, 14.4, 175.0, 0.0, 14.4, 8.8, 114.0, 0.0],\n", + " [7.1, 3194.0, 11.4, 252.0, 0.1, 56.0, 0.0, 13.5, 2.3, 68.0, 0.0],\n", + " [4.6, 9861.0, 36.0, 897.0, 1.7, 99.0, 30.9, 17.4, 7.9, 106.0, 0.0],\n", + " [8.5, 8005.0, 28.6, 680.0, 0.8, 80.0, 0.0, 10.6, 1.6, 110.0, 0.0],\n", + " [7.5, 6048.0, 21.2, 460.0, 0.6, 41.0, 0.0, 2.0, 4.8, 60.0, 0.0],\n", + " [7.1, 6389.0, 25.3, 907.0, 5.1, 341.0, 0.0, 37.1, 8.9, 64.0, 0.0],\n", + " [7.9, 5742.0, 15.6, 488.0, 2.5, 115.0, 0.0, 13.8, 8.5, 126.0, 0.0],\n", + " [9.1, 4985.0, 12.2, 484.0, 2.7, 125.0, 0.0, 13.9, 6.4, 160.0, 0.0],\n", + " [9.2, 4930.0, 12.4, 439.0, 1.1, 82.0, 0.0, 9.9, 3.0, 66.0, 0.0],\n", + " [24.8, 1829.0, 8.0, 130.0, 0.4, 31.0, 18.9, 2.8, 3.0, 17.0, 0.0],\n", + " [15.1, 3004.0, 12.5, 288.0, 0.5, 50.0, 0.0, 0.0, 0.0, 0.0, 0.0],\n", + " [11.0, 8867.0, 6.1, 310.0, 10.5, 18.0, 16.8, 4.0, 16.0, 7.0, 177.0],\n", + " [6.7, 6035.0, 8.4, 422.0, 15.1, 9.0, 26.0, 3.0, 23.5, 11.0, 60.0],\n", + " [20.8, 1473.0, 10.8, 9.0, 0.2, 3.0, 44.2, 0.0, 0.2, 2.0, 0.0],\n", + " [16.1, 2817.0, 20.6, 17.0, 0.6, 6.0, 55.8, 0.2, 0.0, 0.0, 0.0],\n", + " [32.6, 1857.0, 2.9, 238.0, 1.0, 52.0, 18.6, 2.8, 6.5, 1.0, 0.0],\n", + " [24.2, 1874.0, 7.4, 448.0, 16.4, 19.0, 28.1, 0.8, 10.3, 4.0, 0.0],\n", + " [14.1, 1689.0, 3.5, 49.0, 1.7, 3.0, 16.9, 0.6, 2.5, 0.0, 17.0],\n", + " [17.9, 2534.0, 15.7, 661.0, 1.0, 48.0, 0.0, 9.6, 8.1, 471.0, 0.0],\n", + " [16.7, 1198.0, 8.6, 18.0, 0.2, 8.0, 2.7, 0.4, 0.5, 0.0, 0.0],\n", + " [20.3, 2234.0, 20.1, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],\n", + " [9.8, 4628.0, 41.7, 0.0, 0.0, 0.0, 0.2, 0.0, 0.5, 5.0, 0.0],\n", + " [39.6, 1145.0, 2.9, 166.0, 0.1, 34.0, 0.2, 2.1, 2.9, 69.0, 0.0],\n", + " [36.4, 1246.0, 2.2, 214.0, 0.1, 32.0, 0.4, 2.5, 2.4, 87.0, 0.0],\n", + " [29.2, 1553.0, 3.4, 213.0, 0.1, 33.0, 0.0, 0.0, 2.0, 0.0, 0.0],\n", + " [22.6, 2007.0, 3.6, 309.0, 0.2, 46.0, 0.4, 1.0, 4.0, 120.0, 0.0],\n", + " [14.6, 3107.0, 8.5, 404.0, 0.2, 62.0, 0.0, 0.9, 0.0, 0.0, 0.0],\n", + " [26.8, 1692.0, 2.2, 333.0, 0.2, 139.0, 169.2, 6.4, 50.8, 316.0, 525.0],\n", + " [27.6, 1643.0, 3.1, 245.0, 0.1, 20.0, 0.0, 2.8, 3.0, 86.0, 0.0],\n", + " [36.6, 1239.0, 3.3, 140.0, 0.1, 15.0, 0.0, 1.7, 2.7, 54.0, 0.0],\n", + " [30.7, 1477.0, 3.5, 196.0, 0.2, 80.0, 0.0, 17.4, 2.7, 60.0, 0.0],\n", + " [24.2, 1874.0, 4.4, 249.0, 0.3, 37.0, 0.0, 18.2, 3.6, 79.0, 0.0],\n", + " [25.6, 1772.0, 10.4, 152.0, 0.2, 23.0, 0.0, 1.8, 1.8, 71.0, 0.0],\n", + " [27.4, 1655.0, 6.7, 212.0, 0.2, 31.0, 0.0, 9.9, 3.3, 50.0, 0.0],\n", + " [16.0, 2835.0, 18.8, 164.0, 0.1, 26.0, 0.0, 1.4, 1.8, 0.0, 0.0],\n", + " [30.3, 1497.0, 1.8, 184.0, 0.1, 30.0, 0.1, 0.9, 1.8, 68.0, 46.0],\n", + " [42.3, 1072.0, 1.7, 156.0, 0.1, 24.0, 0.0, 1.4, 2.4, 57.0, 0.0],\n", + " [13.0, 3489.0, 5.8, 705.0, 6.8, 45.0, 3.5, 1.0, 4.9, 209.0, 0.0],\n", + " [4.4, 9072.0, 5.8, 27.0, 0.5, 36.0, 7.3, 3.6, 2.7, 5.0, 544.0],\n", + " [6.1, 4982.0, 4.9, 60.0, 0.4, 30.0, 17.4, 2.5, 3.5, 28.0, 498.0],\n", + " [26.0, 2380.0, 1.0, 21.0, 0.5, 14.0, 0.0, 0.5, 0.0, 4.0, 952.0],\n", + " [30.9, 4439.0, 2.2, 40.0, 1.1, 18.0, 11.1, 3.6, 1.3, 10.0, 1993.0],\n", + " [7.1, 5750.0, 2.4, 138.0, 3.7, 80.0, 69.0, 4.3, 5.8, 37.0, 862.0],\n", + " [3.7, 8949.0, 2.6, 125.0, 4.0, 36.0, 7.2, 9.0, 4.5, 26.0, 5369.0],\n", + " [4.7, 6080.0, 2.7, 73.0, 2.8, 43.0, 188.5, 6.1, 4.3, 89.0, 608.0],\n", + " [7.3, 3915.0, 0.9, 51.0, 3.0, 23.0, 0.9, 1.4, 1.4, 9.0, 313.0],\n", + " [8.2, 2247.0, 0.4, 27.0, 1.1, 22.0, 112.4, 1.8, 3.4, 11.0, 449.0],\n", + " [3.6, 11844.0, 5.8, 166.0, 3.8, 59.0, 16.6, 4.7, 5.9, 21.0, 1184.0],\n", + " [34.0, 16810.0, 14.3, 336.0, 1.8, 118.0, 6.7, 29.4, 7.1, 198.0, 2522.0],\n", + " [8.1, 4592.0, 1.1, 106.0, 0.0, 138.0, 918.4, 5.7, 13.8, 33.0, 2755.0],\n", + " [5.1, 7649.0, 9.6, 138.0, 2.7, 54.0, 290.7, 8.4, 5.4, 83.0, 1912.0],\n", + " [16.8, 4894.0, 3.7, 20.0, 0.4, 10.0, 21.5, 0.5, 1.0, 31.0, 196.0],\n", + " [20.4, 4030.0, 3.0, 8.0, 0.3, 8.0, 0.8, 0.8, 0.8, 5.0, 81.0],\n", + " [21.3, 3993.0, 2.4, 16.0, 0.4, 8.0, 2.0, 2.8, 0.8, 7.0, 399.0],\n", + " [27.7, 1945.0, 0.4, 33.0, 0.3, 12.0, 16.3, 1.4, 2.1, 17.0, 272.0],\n", + " [10.0, 5386.0, 1.0, 54.0, 2.0, 65.0, 53.9, 1.6, 4.3, 32.0, 431.0],\n", + " [7.1, 6389.0, 7.5, 364.0, 4.0, 134.0, 3.5, 8.3, 7.7, 56.0, 0.0],\n", + " [10.4, 5452.0, 5.2, 136.0, 0.2, 16.0, 12.0, 1.6, 2.7, 42.0, 218.0],\n", + " [13.8, 4109.0, 2.3, 136.0, 0.6, 45.0, 34.9, 4.9, 2.5, 37.0, 370.0],\n", + " [8.6, 6263.0, 1.3, 63.0, 0.7, 38.0, 53.2, 3.4, 2.5, 36.0, 1253.0],\n", + " [7.6, 3917.0, 1.6, 71.0, 0.6, 43.0, 57.9, 3.5, 2.4, 67.0, 862.0],\n", + " [15.7, 2889.0, 8.5, 87.0, 1.7, 173.0, 86.8, 1.2, 4.3, 55.0, 57.0],\n", + " [9.0, 4284.0, 12.8, 99.0, 2.5, 154.0, 85.7, 3.9, 4.3, 65.0, 257.0],\n", + " [9.4, 4524.0, 13.5, 104.0, 2.5, 136.0, 4.5, 6.3, 1.4, 24.0, 136.0],\n", + " [7.9, 5742.0, 20.0, 1367.0, 4.2, 345.0, 2.9, 28.7, 18.4, 162.0, 0.0],\n", + " [8.9, 5097.0, 17.4, 1055.0, 3.7, 459.0, 5.1, 26.9, 38.2, 93.0, 0.0],\n", + " [5.9, 7688.0, 26.9, 1691.0, 11.4, 792.0, 0.0, 38.4, 24.6, 217.0, 0.0],\n", + " [22.4, 2025.0, 0.0, 0.0, 0.0, 0.0, 0.0, 4.0, 5.1, 50.0, 0.0],\n", + " [17.4, 652.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 2.3, 42.0, 0.0],\n", + " [8.6, 2637.0, 8.7, 237.0, 3.0, 72.0, 0.0, 2.0, 11.9, 40.0, 0.0],\n", + " [16.2, 1400.0, 8.0, 77.0, 1.3, 39.0, 0.0, 0.9, 3.4, 14.0, 0.0],\n", + " [51.7, 8773.0, 34.9, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],\n", + " [13.7, 4996.0, 14.7, 0.0, 0.5, 74.0, 0.0, 0.0, 0.0, 5.0, 0.0],\n", + " [13.6, 3752.0, 9.0, 0.0, 10.3, 244.0, 0.0, 1.9, 7.5, 146.0, 0.0],\n", + " [20.5, 2213.0, 6.4, 11.0, 0.4, 7.0, 0.2, 0.2, 0.4, 3.0, 0.0]\n", + "]\n", + "\n", + "# recommended daily allowance for a moderately active man\n", + "allowance = [3.0, 70.0, 0.8, 12.0, 5.0, 1.8, 2.7, 18.0, 75.0]\n", + "\n", + "#\n", + "# variables\n", + "#\n", + "x = [solver.NumVar(0, 1000, \"x[%i]\" % i) for i in C]\n", + "x_cost = [solver.NumVar(0, 1000, \"x_cost[%i]\" % i) for i in C]\n", + "quant = [solver.NumVar(0, 1000, \"quant[%i]\" % i) for i in C]\n", + "\n", + "# total food bill\n", + "total_cost = solver.NumVar(0, 1000, \"total_cost\")\n", + "\n", + "# cost per day, to minimize\n", + "cost = solver.Sum(x)\n", + "\n", + "#\n", + "# constraints\n", + "#\n", + "solver.Add(total_cost == days * cost) # cost per year\n", + "\n", + "for c in C:\n", + " solver.Add(x_cost[c] == days * x[c])\n", + " solver.Add(quant[c] == 100.0 * days * x[c] / data[c][0])\n", + "\n", + "# nutrient balance\n", + "for n in range(2, num_nutrients + 2):\n", + " solver.Add(solver.Sum([data[c][n] * x[c] for c in C]) >= allowance[n - 2])\n", + "\n", + "objective = solver.Minimize(cost)\n", + "\n", + "#\n", + "# solution and search\n", + "#\n", + "solver.Solve()\n", + "\n", + "print()\n", + "\n", + "print(\"Cost = %0.2f\" % solver.Objective().Value())\n", + "# print 'Cost:', cost.SolutionValue()\n", + "print(\"Total cost: %0.2f\" % total_cost.SolutionValue())\n", + "print()\n", + "for i in C:\n", + " if x[i].SolutionValue() > 0:\n", + " print(\"%-21s %-11s %0.2f %0.2f\" %\n", + " (commodities[i][0], commodities[i][1], x_cost[i].SolutionValue(),\n", + " quant[i].SolutionValue()))\n", + "\n", + "print()\n", + "\n", + "print(\"walltime :\", solver.WallTime(), \"ms\")\n", + "if sol == \"CBC\":\n", + " print(\"iterations:\", solver.Iterations())\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/contrib/strimko2.ipynb b/examples/notebook/contrib/strimko2.ipynb new file mode 100644 index 0000000000..eb3e069e24 --- /dev/null +++ b/examples/notebook/contrib/strimko2.ipynb @@ -0,0 +1,149 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010 Hakan Kjellerstrand hakank@gmail.com\n", + "#\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"Strimko problem in Google CP Solver.\n", + "\n", + " From\n", + " 360: A New Twist on Latin Squares\n", + " http://threesixty360.wordpress.com/2009/08/04/a-new-twist-on-latin-squares/\n", + " '''\n", + " The idea is simple: each row and column of an nxn grid must contain\n", + " the number 1, 2, ... n exactly once (that is, the grid must form a\n", + " Latin square), and each \"stream\" (connected path in the grid) must\n", + " also contain the numbers 1, 2, ..., n exactly once.\n", + " '''\n", + "\n", + " For more information, see:\n", + " * http://www.strimko.com/\n", + " * http://www.strimko.com/rules.htm\n", + " * http://www.strimko.com/about.htm\n", + " * http://www.puzzlersparadise.com/Strimko.htm\n", + "\n", + " I have blogged about this (using MiniZinc model) in\n", + " 'Strimko - Latin squares puzzle with \"streams\"'\n", + " http://www.hakank.org/constraint_programming_blog/2009/08/strimko_latin_squares_puzzle_w_1.html\n", + "\n", + " Compare with the following models:\n", + " * MiniZinc: http://hakank.org/minizinc/strimko2.mzn\n", + " * ECLiPSe: http://hakank.org/eclipse/strimko2.ecl\n", + " * SICStus: http://hakank.org/sicstus/strimko2.pl\n", + " * Gecode: http://hakank.org/gecode/strimko2.cpp\n", + "\n", + " This model was created by Hakan Kjellerstrand (hakank@gmail.com)\n", + " See my other Google CP Solver models: http://www.hakank.org/google_or_tools/\n", + "\"\"\"\n", + "from __future__ import print_function\n", + "import sys\n", + "\n", + "from ortools.constraint_solver import pywrapcp\n", + "\n", + "\n", + "\n", + "# Create the solver.\n", + "solver = pywrapcp.Solver('Strimko')\n", + "\n", + "#\n", + "# default problem\n", + "#\n", + "if streams == '':\n", + " streams = [[1, 1, 2, 2, 2, 2, 2], [1, 1, 2, 3, 3, 3, 2],\n", + " [1, 4, 1, 3, 3, 5, 5], [4, 4, 3, 1, 3, 5, 5],\n", + " [4, 6, 6, 6, 7, 7, 5], [6, 4, 6, 4, 5, 5, 7],\n", + " [6, 6, 4, 7, 7, 7, 7]]\n", + "\n", + " # Note: This is 1-based\n", + " placed = [[2, 1, 1], [2, 3, 7], [2, 5, 6], [2, 7, 4], [3, 2, 7], [3, 6, 1],\n", + " [4, 1, 4], [4, 7, 5], [5, 2, 2], [5, 6, 6]]\n", + "\n", + "n = len(streams)\n", + "num_placed = len(placed)\n", + "\n", + "print('n:', n)\n", + "\n", + "#\n", + "# variables\n", + "#\n", + "\n", + "x = {}\n", + "for i in range(n):\n", + " for j in range(n):\n", + " x[i, j] = solver.IntVar(1, n, 'x[%i,%i]' % (i, j))\n", + "\n", + "x_flat = [x[i, j] for i in range(n) for j in range(n)]\n", + "\n", + "#\n", + "# constraints\n", + "#\n", + "\n", + "# all rows and columns must be unique, i.e. a Latin Square\n", + "for i in range(n):\n", + " row = [x[i, j] for j in range(n)]\n", + " solver.Add(solver.AllDifferent(row))\n", + "\n", + " col = [x[j, i] for j in range(n)]\n", + " solver.Add(solver.AllDifferent(col))\n", + "\n", + "#\n", + "# streams\n", + "#\n", + "for s in range(1, n + 1):\n", + " tmp = [x[i, j] for i in range(n) for j in range(n) if streams[i][j] == s]\n", + " solver.Add(solver.AllDifferent(tmp))\n", + "\n", + "#\n", + "# placed\n", + "#\n", + "for i in range(num_placed):\n", + " # note: also adjust to 0-based\n", + " solver.Add(x[placed[i][0] - 1, placed[i][1] - 1] == placed[i][2])\n", + "\n", + "#\n", + "# search and solution\n", + "#\n", + "db = solver.Phase(x_flat, solver.INT_VAR_DEFAULT, solver.INT_VALUE_DEFAULT)\n", + "\n", + "solver.NewSearch(db)\n", + "\n", + "num_solutions = 0\n", + "while solver.NextSolution():\n", + " for i in range(n):\n", + " for j in range(n):\n", + " print(x[i, j].Value(), end=' ')\n", + " print()\n", + "\n", + " print()\n", + " num_solutions += 1\n", + "\n", + "solver.EndSearch()\n", + "\n", + "print()\n", + "print('num_solutions:', num_solutions)\n", + "print('failures:', solver.Failures())\n", + "print('branches:', solver.Branches())\n", + "print('WallTime:', solver.WallTime(), 'ms')\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/contrib/subset_sum.ipynb b/examples/notebook/contrib/subset_sum.ipynb new file mode 100644 index 0000000000..2d97ca5270 --- /dev/null +++ b/examples/notebook/contrib/subset_sum.ipynb @@ -0,0 +1,118 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010 Hakan Kjellerstrand hakank@gmail.com\n", + "#\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"\n", + "\n", + " Subset sum problem in Google CP Solver.\n", + "\n", + " From Katta G. Murty: 'Optimization Models for Decision Making', page 340\n", + " http://ioe.engin.umich.edu/people/fac/books/murty/opti_model/junior-7.pdf\n", + " '''\n", + " Example 7.8.1\n", + "\n", + " A bank van had several bags of coins, each containing either\n", + " 16, 17, 23, 24, 39, or 40 coins. While the van was parked on the\n", + " street, thieves stole some bags. A total of 100 coins were lost.\n", + " It is required to find how many bags were stolen.\n", + " '''\n", + "\n", + " Compare with the following models:\n", + " * Comet: http://www.hakank.org/comet/subset_sum.co\n", + " * ECLiPSE: http://www.hakank.org/eclipse/subset_sum.ecl\n", + " * Gecode: http://www.hakank.org/gecode/subset_sum.cpp\n", + " * MiniZinc: http://www.hakank.org/minizinc/subset_sum.mzn\n", + " * Tailor/Essence': http://www.hakank.org/tailor/subset_sum.py\n", + " * SICStus: http://hakank.org/sicstus/subset_sum.pl\n", + "\n", + " This model was created by Hakan Kjellerstrand (hakank@gmail.com)\n", + " Also see my other Google CP Solver models:\n", + " http://www.hakank.org/google_or_tools/\n", + "\"\"\"\n", + "from __future__ import print_function\n", + "import sys\n", + "from ortools.constraint_solver import pywrapcp\n", + "\n", + "\n", + "def subset_sum(solver, values, total):\n", + " n = len(values)\n", + " x = [solver.IntVar(0, n) for i in range(n)]\n", + " ss = solver.IntVar(0, n)\n", + "\n", + " solver.Add(ss == solver.Sum(x))\n", + " solver.Add(total == solver.ScalProd(x, values))\n", + "\n", + " return x, ss\n", + "\n", + "\n", + "\n", + "# Create the solver.\n", + "solver = pywrapcp.Solver(\"n-queens\")\n", + "\n", + "#\n", + "# data\n", + "#\n", + "print(\"coins:\", coins)\n", + "print(\"total:\", total)\n", + "print()\n", + "\n", + "#\n", + "# declare variables\n", + "#\n", + "\n", + "#\n", + "# constraints\n", + "#\n", + "x, ss = subset_sum(solver, coins, total)\n", + "\n", + "#\n", + "# solution and search\n", + "#\n", + "solution = solver.Assignment()\n", + "solution.Add(x)\n", + "solution.Add(ss)\n", + "\n", + "# db: DecisionBuilder\n", + "db = solver.Phase(x, solver.CHOOSE_FIRST_UNBOUND, solver.ASSIGN_MIN_VALUE)\n", + "\n", + "solver.NewSearch(db)\n", + "num_solutions = 0\n", + "while solver.NextSolution():\n", + " print(\"ss:\", ss.Value())\n", + " print(\"x: \", [x[i].Value() for i in range(len(x))])\n", + " print()\n", + " num_solutions += 1\n", + "solver.EndSearch()\n", + "\n", + "print()\n", + "print(\"num_solutions:\", num_solutions)\n", + "print(\"failures:\", solver.Failures())\n", + "print(\"branches:\", solver.Branches())\n", + "print(\"WallTime:\", solver.WallTime())\n", + "\n", + "coins = [16, 17, 23, 24, 39, 40]\n", + "total = 100\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/contrib/survo_puzzle.ipynb b/examples/notebook/contrib/survo_puzzle.ipynb new file mode 100644 index 0000000000..845964b804 --- /dev/null +++ b/examples/notebook/contrib/survo_puzzle.ipynb @@ -0,0 +1,180 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010 Hakan Kjellerstrand hakank@gmail.com\n", + "#\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"\n", + "\n", + " Survo puzzle Google CP Solver.\n", + "\n", + " http://en.wikipedia.org/wiki/Survo_Puzzle\n", + " '''\n", + " Survo puzzle is a kind of logic puzzle presented (in April 2006) and studied\n", + " by Seppo Mustonen. The name of the puzzle is associated to Mustonen's\n", + " Survo system which is a general environment for statistical computing and\n", + " related areas.\n", + "\n", + " In a Survo puzzle the task is to fill an m * n table by integers 1,2,...,m*n\n", + " so\n", + " that each of these numbers appears only once and their row and column sums are\n", + " equal to integers given on the bottom and the right side of the table.\n", + " Often some of the integers are given readily in the table in order to\n", + " guarantee uniqueness of the solution and/or for making the task easier.\n", + " '''\n", + "\n", + " See also\n", + " http://www.survo.fi/english/index.html\n", + " http://www.survo.fi/puzzles/index.html\n", + "\n", + " References:\n", + " Mustonen, S. (2006b). \"On certain cross sum puzzles\"\n", + " http://www.survo.fi/papers/puzzles.pdf\n", + " Mustonen, S. (2007b). \"Enumeration of uniquely solvable open Survo puzzles.\"\n", + " http://www.survo.fi/papers/enum_survo_puzzles.pdf\n", + " Kimmo Vehkalahti: \"Some comments on magic squares and Survo puzzles\"\n", + " http://www.helsinki.fi/~kvehkala/Kimmo_Vehkalahti_Windsor.pdf\n", + " R code: http://koti.mbnet.fi/tuimala/tiedostot/survo.R\n", + "\n", + " Compare with the following models:\n", + " * Choco : http://www.hakank.org/choco/SurvoPuzzle.java\n", + " * Comet : http://www.hakank.org/comet/survo_puzzle.co\n", + " * ECLiPSE : http://www.hakank.org/eclipse/survo_puzzle.ecl\n", + " * Gecode : http://www.hakank.org/gecode/survo_puzzle.cpp\n", + " * Gecode/R: http://www.hakank.org/gecode_r/survo_puzzle.rb\n", + " * JaCoP : http://www.hakank.org/JaCoP/SurvoPuzzle.java\n", + " * MiniZinc: http://www.hakank.org/minizinc/survo_puzzle.mzn\n", + " * Tailor/Essence': http://www.hakank.org/tailor/survo_puzzle.eprime\n", + " * Zinc: http://www.hakank.org/minizinc/survo_puzzle.zinc\n", + "\n", + "\n", + " This model was created by Hakan Kjellerstrand (hakank@gmail.com)\n", + " Also see my other Google CP Solver models:\n", + " http://www.hakank.org/google_or_tools/\n", + "\"\"\"\n", + "from __future__ import print_function\n", + "import sys\n", + "from ortools.constraint_solver import pywrapcp\n", + "\n", + "\n", + "\n", + "# Create the solver.\n", + "solver = pywrapcp.Solver(\"Survo puzzle\")\n", + "\n", + "#\n", + "# data\n", + "#\n", + "if r == 0:\n", + " r = 3\n", + " c = 4\n", + " rowsums = [30, 18, 30]\n", + " colsums = [27, 16, 10, 25]\n", + " game = [[0, 6, 0, 0], [8, 0, 0, 0], [0, 0, 3, 0]]\n", + "\n", + "print(\"r:\", r, \"c:\", c)\n", + "\n", + "# declare variables\n", + "x = {}\n", + "for i in range(r):\n", + " for j in range(c):\n", + " x[(i, j)] = solver.IntVar(1, r * c, \"x %i %i\" % (i, j))\n", + "\n", + "#\n", + "# constraints\n", + "#\n", + "\n", + "#\n", + "# set the clues\n", + "#\n", + "for i in range(r):\n", + " for j in range(c):\n", + " if game[i][j] > 0:\n", + " solver.Add(x[i, j] == game[i][j])\n", + "\n", + "xflat = [x[(i, j)] for i in range(r) for j in range(c)]\n", + "solver.Add(solver.AllDifferent(xflat))\n", + "#\n", + "# calculate rowsums and colsums\n", + "#\n", + "for i in range(r):\n", + " solver.Add(rowsums[i] == solver.Sum([x[i, j] for j in range(c)]))\n", + "\n", + "for j in range(c):\n", + " solver.Add(colsums[j] == solver.Sum([x[i, j] for i in range(r)]))\n", + "\n", + "#\n", + "# solution and search\n", + "#\n", + "solution = solver.Assignment()\n", + "solution.Add([x[(i, j)] for i in range(r) for j in range(c)])\n", + "\n", + "collector = solver.AllSolutionCollector(solution)\n", + "solver.Solve(\n", + " solver.Phase(xflat, solver.CHOOSE_FIRST_UNBOUND, solver.ASSIGN_MIN_VALUE),\n", + " [collector])\n", + "\n", + "num_solutions = collector.SolutionCount()\n", + "print(\"\\nnum_solutions: \", num_solutions)\n", + "if num_solutions > 0:\n", + " for s in range(num_solutions):\n", + " xval = [collector.Value(s, x[(i, j)]) for i in range(r) for j in range(c)]\n", + "\n", + " for i in range(r):\n", + " for j in range(c):\n", + " print(\"%2i\" % (xval[i * c + j]), end=\" \")\n", + " print()\n", + " print()\n", + "\n", + " print()\n", + " print(\"num_solutions:\", num_solutions)\n", + " print(\"failures:\", solver.Failures())\n", + " print(\"branches:\", solver.Branches())\n", + " print(\"WallTime:\", solver.WallTime())\n", + "\n", + "else:\n", + " print(\"No solutions found\")\n", + "\n", + "\n", + "#\n", + "# Read a problem instance from a file\n", + "#def read_problem(file):\n", + " f = open(file, \"r\")\n", + " r = int(f.readline())\n", + " c = int(f.readline())\n", + " rowsums = f.readline()\n", + " colsums = f.readline()\n", + " rowsums = [int(t) for t in (rowsums.rstrip()).split(\",\")]\n", + " colsums = [int(t) for t in (colsums.rstrip()).split(\",\")]\n", + " game = []\n", + " for i in range(r):\n", + " x = f.readline()\n", + " x = [int(t) for t in (x.rstrip()).split(\",\")]\n", + " row = [0] * c\n", + " for j in range(c):\n", + " row[j] = int(x[j])\n", + " game.append(row)\n", + " return [r, c, rowsums, colsums, game]\n", + "\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/contrib/toNum.ipynb b/examples/notebook/contrib/toNum.ipynb new file mode 100644 index 0000000000..bbddae6e93 --- /dev/null +++ b/examples/notebook/contrib/toNum.ipynb @@ -0,0 +1,95 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010 Hakan Kjellerstrand hakank@gmail.com\n", + "#\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"\n", + "\n", + " toNum in Google CP Solver.\n", + "\n", + " Convert a number <-> array of int in a specific base.\n", + "\n", + " This model was created by Hakan Kjellerstrand (hakank@gmail.com)\n", + " Also see my other Google CP Solver models:\n", + " http://www.hakank.org/google_or_tools/\n", + "\n", + "\"\"\"\n", + "from __future__ import print_function\n", + "from ortools.constraint_solver import pywrapcp\n", + "\n", + "#\n", + "# converts a number (s) <-> an array of integers (t) in the specific base.\n", + "#\n", + "\n", + "\n", + "def toNum(solver, t, s, base):\n", + " tlen = len(t)\n", + " solver.Add(\n", + " s == solver.Sum([(base**(tlen - i - 1)) * t[i] for i in range(tlen)]))\n", + "\n", + "\n", + "# Create the solver.\n", + "solver = pywrapcp.Solver(\"toNum test\")\n", + "\n", + "# data\n", + "n = 4\n", + "base = 10\n", + "\n", + "# declare variables\n", + "x = [solver.IntVar(0, n - 1, \"x%i\" % i) for i in range(n)]\n", + "y = solver.IntVar(0, 10**n - 1, \"y\")\n", + "\n", + "#\n", + "# constraints\n", + "#\n", + "# solver.Add(solver.AllDifferent([x[i] for i in range(n)]))\n", + "solver.Add(solver.AllDifferent(x))\n", + "# solver.Add(x[0] > 0) # just for fun\n", + "\n", + "toNum(solver, x, y, base)\n", + "\n", + "#\n", + "# solution and search\n", + "#\n", + "solution = solver.Assignment()\n", + "solution.Add([x[i] for i in range(n)])\n", + "solution.Add(y)\n", + "\n", + "collector = solver.AllSolutionCollector(solution)\n", + "solver.Solve(\n", + " solver.Phase([x[i] for i in range(n)], solver.CHOOSE_FIRST_UNBOUND,\n", + " solver.ASSIGN_MIN_VALUE), [collector])\n", + "\n", + "num_solutions = collector.SolutionCount()\n", + "for s in range(num_solutions):\n", + " print(\"x:\", [collector.Value(s, x[i]) for i in range(n)])\n", + " print(\"y:\", collector.Value(s, y))\n", + " print()\n", + "\n", + "print(\"failures:\", solver.Failures())\n", + "print(\"branches:\", solver.Branches())\n", + "print(\"WallTime:\", solver.WallTime())\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/contrib/traffic_lights.ipynb b/examples/notebook/contrib/traffic_lights.ipynb new file mode 100644 index 0000000000..2ec734b6e1 --- /dev/null +++ b/examples/notebook/contrib/traffic_lights.ipynb @@ -0,0 +1,143 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010 Hakan Kjellerstrand hakank@gmail.com\n", + "#\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"\n", + "\n", + " Traffic lights problem in Google CP Solver.\n", + "\n", + " CSPLib problem 16\n", + " http://www.cs.st-andrews.ac.uk/~ianm/CSPLib/prob/prob016/index.html\n", + " '''\n", + " Specification:\n", + " Consider a four way traffic junction with eight traffic lights. Four of the\n", + " traffic\n", + " lights are for the vehicles and can be represented by the variables V1 to V4\n", + " with domains\n", + " {r,ry,g,y} (for red, red-yellow, green and yellow). The other four traffic\n", + " lights are\n", + " for the pedestrians and can be represented by the variables P1 to P4 with\n", + " domains {r,g}.\n", + "\n", + " The constraints on these variables can be modelled by quaternary constraints\n", + " on\n", + " (Vi, Pi, Vj, Pj ) for 1<=i<=4, j=(1+i)mod 4 which allow just the tuples\n", + " {(r,r,g,g), (ry,r,y,r), (g,g,r,r), (y,r,ry,r)}.\n", + "\n", + " It would be interesting to consider other types of junction (e.g. five roads\n", + " intersecting) as well as modelling the evolution over time of the traffic\n", + " light sequence.\n", + " ...\n", + "\n", + " Results\n", + " Only 2^2 out of the 2^12 possible assignments are solutions.\n", + "\n", + " (V1,P1,V2,P2,V3,P3,V4,P4) =\n", + " {(r,r,g,g,r,r,g,g), (ry,r,y,r,ry,r,y,r), (g,g,r,r,g,g,r,r),\n", + " (y,r,ry,r,y,r,ry,r)}\n", + " [(1,1,3,3,1,1,3,3), ( 2,1,4,1, 2,1,4,1), (3,3,1,1,3,3,1,1), (4,1, 2,1,4,1,\n", + " 2,1)}\n", + "\n", + " The problem has relative few constraints, but each is very tight. Local\n", + " propagation\n", + " appears to be rather ineffective on this problem.\n", + "\n", + " '''\n", + "\n", + " Note: In this model we use only the constraint solver.AllowedAssignments().\n", + "\n", + "\n", + " Compare with these models:\n", + " * MiniZinc: http://www.hakank.org/minizinc/traffic_lights.mzn\n", + " * Comet : http://www.hakank.org/comet/traffic_lights.co\n", + " * ECLiPSe : http://www.hakank.org/eclipse/traffic_lights.ecl\n", + " * Gecode : http://hakank.org/gecode/traffic_lights.cpp\n", + " * SICStus : http://hakank.org/sicstus/traffic_lights.pl\n", + "\n", + "\n", + " This model was created by Hakan Kjellerstrand (hakank@gmail.com)\n", + " Also see my other Google CP Solver models:\n", + " http://www.hakank.org/google_or_tools/\n", + "\n", + "\"\"\"\n", + "from __future__ import print_function\n", + "import sys\n", + "\n", + "from ortools.constraint_solver import pywrapcp\n", + "\n", + "\n", + "\n", + "# Create the solver.\n", + "solver = pywrapcp.Solver(\"Traffic lights\")\n", + "\n", + "#\n", + "# data\n", + "#\n", + "n = 4\n", + "r, ry, g, y = list(range(n))\n", + "lights = [\"r\", \"ry\", \"g\", \"y\"]\n", + "\n", + "# The allowed combinations\n", + "allowed = []\n", + "allowed.extend([(r, r, g, g), (ry, r, y, r), (g, g, r, r), (y, r, ry, r)])\n", + "\n", + "#\n", + "# declare variables\n", + "#\n", + "V = [solver.IntVar(0, n - 1, \"V[%i]\" % i) for i in range(n)]\n", + "P = [solver.IntVar(0, n - 1, \"P[%i]\" % i) for i in range(n)]\n", + "\n", + "#\n", + "# constraints\n", + "#\n", + "for i in range(n):\n", + " for j in range(n):\n", + " if j == (1 + i) % n:\n", + " solver.Add(solver.AllowedAssignments((V[i], P[i], V[j], P[j]), allowed))\n", + "\n", + "#\n", + "# Search and result\n", + "#\n", + "db = solver.Phase(V + P, solver.INT_VAR_SIMPLE, solver.INT_VALUE_DEFAULT)\n", + "\n", + "solver.NewSearch(db)\n", + "num_solutions = 0\n", + "while solver.NextSolution():\n", + " for i in range(n):\n", + " print(\"%+2s %+2s\" % (lights[V[i].Value()], lights[P[i].Value()]), end=\" \")\n", + " print()\n", + " num_solutions += 1\n", + "\n", + "solver.EndSearch()\n", + "\n", + "print()\n", + "print(\"num_solutions:\", num_solutions)\n", + "print(\"failures:\", solver.Failures())\n", + "print(\"branches:\", solver.Branches())\n", + "print(\"WallTime:\", solver.WallTime())\n", + "print()\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/contrib/vendor_scheduling.ipynb b/examples/notebook/contrib/vendor_scheduling.ipynb new file mode 100644 index 0000000000..5185f4cc13 --- /dev/null +++ b/examples/notebook/contrib/vendor_scheduling.ipynb @@ -0,0 +1,112 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from __future__ import print_function\n", + "from ortools.constraint_solver import pywrapcp\n", + "\n", + "\n", + "# Create the solver.\n", + "solver = pywrapcp.Solver('Vendors scheduling')\n", + "\n", + "#\n", + "# data\n", + "#\n", + "num_vendors = 9\n", + "num_hours = 10\n", + "num_work_types = 1\n", + "\n", + "trafic = [100, 500, 100, 200, 320, 300, 200, 220, 300, 120]\n", + "max_trafic_per_vendor = 100\n", + "\n", + "# Last columns are :\n", + "# index_of_the_schedule, sum of worked hours (per work type).\n", + "# The index is useful for branching.\n", + "possible_schedules = [[1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 8],\n", + " [1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 4],\n", + " [0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 2, 5],\n", + " [0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 3, 4],\n", + " [1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 4, 3],\n", + " [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 0]]\n", + "\n", + "num_possible_schedules = len(possible_schedules)\n", + "selected_schedules = []\n", + "vendors_stat = []\n", + "hours_stat = []\n", + "\n", + "#\n", + "# declare variables\n", + "#\n", + "x = {}\n", + "\n", + "for i in range(num_vendors):\n", + " tmp = []\n", + " for j in range(num_hours):\n", + " x[i, j] = solver.IntVar(0, num_work_types, 'x[%i,%i]' % (i, j))\n", + " tmp.append(x[i, j])\n", + " selected_schedule = solver.IntVar(0, num_possible_schedules - 1,\n", + " 's[%i]' % i)\n", + " hours = solver.IntVar(0, num_hours, 'h[%i]' % i)\n", + " selected_schedules.append(selected_schedule)\n", + " vendors_stat.append(hours)\n", + " tmp.append(selected_schedule)\n", + " tmp.append(hours)\n", + "\n", + " solver.Add(solver.AllowedAssignments(tmp, possible_schedules))\n", + "\n", + "#\n", + "# Statistics and constraints for each hour\n", + "#\n", + "for j in range(num_hours):\n", + " workers = solver.Sum([x[i, j] for i in range(num_vendors)]).Var()\n", + " hours_stat.append(workers)\n", + " solver.Add(workers * max_trafic_per_vendor >= trafic[j])\n", + "\n", + "#\n", + "# Redundant constraint: sort selected_schedules\n", + "#\n", + "for i in range(num_vendors - 1):\n", + " solver.Add(selected_schedules[i] <= selected_schedules[i + 1])\n", + "\n", + "#\n", + "# Search\n", + "#\n", + "db = solver.Phase(selected_schedules, solver.CHOOSE_FIRST_UNBOUND,\n", + " solver.ASSIGN_MIN_VALUE)\n", + "\n", + "solver.NewSearch(db)\n", + "\n", + "num_solutions = 0\n", + "while solver.NextSolution():\n", + " num_solutions += 1\n", + "\n", + " for i in range(num_vendors):\n", + " print('Vendor %i: ' % i,\n", + " possible_schedules[selected_schedules[i].Value()])\n", + " print()\n", + "\n", + " print('Statistics per day:')\n", + " for j in range(num_hours):\n", + " print('Day%2i: ' % j, end=' ')\n", + " print(hours_stat[j].Value(), end=' ')\n", + " print()\n", + " print()\n", + "\n", + "solver.EndSearch()\n", + "print()\n", + "print('num_solutions:', num_solutions)\n", + "print('failures:', solver.Failures())\n", + "print('branches:', solver.Branches())\n", + "print('WallTime:', solver.WallTime(), 'ms')\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/contrib/volsay.ipynb b/examples/notebook/contrib/volsay.ipynb new file mode 100644 index 0000000000..5d0e060ce5 --- /dev/null +++ b/examples/notebook/contrib/volsay.ipynb @@ -0,0 +1,81 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2011 Hakan Kjellerstrand hakank@gmail.com\n", + "#\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"\n", + "\n", + " Volsay problem in Google or-tools.\n", + "\n", + " From the OPL model volsay.mod\n", + "\n", + " This model was created by Hakan Kjellerstrand (hakank@gmail.com)\n", + " Also see my other Google CP Solver models:\n", + " http://www.hakank.org/google_or_tools/\n", + "\"\"\"\n", + "from __future__ import print_function\n", + "from ortools.linear_solver import pywraplp\n", + "\n", + "\n", + "\n", + "# Create the solver.\n", + "\n", + "# using GLPK\n", + "#solver = pywraplp.Solver('CoinsGridGLPK',\n", + "# pywraplp.Solver.GLPK_LINEAR_PROGRAMMING)\n", + "\n", + "# Using CLP\n", + "solver = pywraplp.Solver('CoinsGridCLP',\n", + " pywraplp.Solver.CLP_LINEAR_PROGRAMMING)\n", + "\n", + "# data\n", + "\n", + "# declare variables\n", + "Gas = solver.NumVar(0, 100000, 'Gas')\n", + "Chloride = solver.NumVar(0, 100000, 'Cloride')\n", + "\n", + "#\n", + "# constraints\n", + "#\n", + "solver.Add(Gas + Chloride <= 50)\n", + "solver.Add(3 * Gas + 4 * Chloride <= 180)\n", + "\n", + "# objective\n", + "objective = solver.Maximize(40 * Gas + 50 * Chloride)\n", + "\n", + "print('NumConstraints:', solver.NumConstraints())\n", + "\n", + "#\n", + "# solution and search\n", + "#\n", + "solver.Solve()\n", + "\n", + "print()\n", + "print('objective = ', solver.Objective().Value())\n", + "print('Gas = ', Gas.SolutionValue(), 'ReducedCost =', Gas.ReducedCost())\n", + "print('Chloride:', Chloride.SolutionValue(), 'ReducedCost =',\n", + " Chloride.ReducedCost())\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/contrib/volsay2.ipynb b/examples/notebook/contrib/volsay2.ipynb new file mode 100644 index 0000000000..6cd0ef0a5b --- /dev/null +++ b/examples/notebook/contrib/volsay2.ipynb @@ -0,0 +1,89 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2011 Hakan Kjellerstrand hakank@gmail.com\n", + "#\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"\n", + "\n", + " Volsay problem in Google or-tools.\n", + "\n", + " From the OPL model volsay.mod\n", + " Using arrays.\n", + "\n", + " This model was created by Hakan Kjellerstrand (hakank@gmail.com)\n", + " Also see my other Google CP Solver models:\n", + " http://www.hakank.org/google_or_tools/\n", + "\"\"\"\n", + "from __future__ import print_function\n", + "from ortools.linear_solver import pywraplp\n", + "\n", + "\n", + "\n", + "# Create the solver.\n", + "\n", + "# using GLPK\n", + "# solver = pywraplp.Solver('CoinsGridGLPK',\n", + "# pywraplp.Solver.GLPK_LINEAR_PROGRAMMING)\n", + "\n", + "# Using CLP\n", + "solver = pywraplp.Solver('CoinsGridCLP',\n", + " pywraplp.Solver.CLP_LINEAR_PROGRAMMING)\n", + "\n", + "# data\n", + "num_products = 2\n", + "Gas = 0\n", + "Chloride = 1\n", + "\n", + "products = ['Gas', 'Chloride']\n", + "\n", + "# declare variables\n", + "production = [\n", + " solver.NumVar(0, 100000, 'production[%i]' % i)\n", + " for i in range(num_products)\n", + "]\n", + "\n", + "#\n", + "# constraints\n", + "#\n", + "solver.Add(production[Gas] + production[Chloride] <= 50)\n", + "solver.Add(3 * production[Gas] + 4 * production[Chloride] <= 180)\n", + "\n", + "# objective\n", + "objective = solver.Maximize(40 * production[Gas] + 50 * production[Chloride])\n", + "\n", + "print('NumConstraints:', solver.NumConstraints())\n", + "\n", + "#\n", + "# solution and search\n", + "#\n", + "solver.Solve()\n", + "\n", + "print()\n", + "print('objective = ', solver.Objective().Value())\n", + "for i in range(num_products):\n", + " print(products[i], '=', production[i].SolutionValue(), end=' ')\n", + " print('ReducedCost = ', production[i].ReducedCost())\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/contrib/volsay3.ipynb b/examples/notebook/contrib/volsay3.ipynb new file mode 100644 index 0000000000..b16dc2705f --- /dev/null +++ b/examples/notebook/contrib/volsay3.ipynb @@ -0,0 +1,102 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2011 Hakan Kjellerstrand hakank@gmail.com\n", + "#\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"\n", + "\n", + " Volsay problem in Google or-tools.\n", + "\n", + " From the OPL model volsay.mod\n", + " Using arrays.\n", + "\n", + " This model was created by Hakan Kjellerstrand (hakank@gmail.com)\n", + " Also see my other Google CP Solver models:\n", + " http://www.hakank.org/google_or_tools/\n", + "\"\"\"\n", + "from __future__ import print_function\n", + "from ortools.linear_solver import pywraplp\n", + "\n", + "\n", + "\n", + "# Create the solver.\n", + "\n", + "# using GLPK\n", + "# solver = pywraplp.Solver('CoinsGridGLPK',\n", + "# pywraplp.Solver.GLPK_LINEAR_PROGRAMMING)\n", + "\n", + "# Using CLP\n", + "solver = pywraplp.Solver('CoinsGridCLP',\n", + " pywraplp.Solver.CLP_LINEAR_PROGRAMMING)\n", + "\n", + "# data\n", + "num_products = 2\n", + "\n", + "products = ['Gas', 'Chloride']\n", + "components = ['nitrogen', 'hydrogen', 'chlorine']\n", + "\n", + "demand = [[1, 3, 0], [1, 4, 1]]\n", + "profit = [30, 40]\n", + "stock = [50, 180, 40]\n", + "\n", + "# declare variables\n", + "production = [\n", + " solver.NumVar(0, 100000, 'production[%i]' % i)\n", + " for i in range(num_products)\n", + "]\n", + "\n", + "#\n", + "# constraints\n", + "#\n", + "for c in range(len(components)):\n", + " solver.Add(\n", + " solver.Sum([demand[p][c] * production[p]\n", + " for p in range(len(products))]) <= stock[c])\n", + "\n", + "# objective\n", + "# Note: there is no support for solver.ScalProd in the LP/IP interface\n", + "objective = solver.Maximize(\n", + " solver.Sum([production[p] * profit[p] for p in range(num_products)]))\n", + "\n", + "print('NumConstraints:', solver.NumConstraints())\n", + "print('NumVariables:', solver.NumVariables())\n", + "print()\n", + "\n", + "#\n", + "# solution and search\n", + "#\n", + "solver.Solve()\n", + "\n", + "print()\n", + "print('objective = ', solver.Objective().Value())\n", + "for i in range(num_products):\n", + " print(products[i], '=', production[i].SolutionValue(), end=' ')\n", + " print('ReducedCost = ', production[i].ReducedCost())\n", + "\n", + "print()\n", + "print('walltime :', solver.WallTime(), 'ms')\n", + "print('iterations:', solver.Iterations())\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/contrib/wedding_optimal_chart.ipynb b/examples/notebook/contrib/wedding_optimal_chart.ipynb new file mode 100644 index 0000000000..2bdd6e1df6 --- /dev/null +++ b/examples/notebook/contrib/wedding_optimal_chart.ipynb @@ -0,0 +1,173 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "#\n", + "# Copyright 2018 Turadg Aleahmad\n", + "#\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\n", + "from __future__ import print_function\n", + "from ortools.constraint_solver import pywrapcp\n", + "from ortools.constraint_solver import solver_parameters_pb2\n", + "\"\"\"Finding an optimal wedding seating chart.\n", + "\n", + "From\n", + "Meghan L. Bellows and J. D. Luc Peterson\n", + "\"Finding an optimal seating chart for a wedding\"\n", + "http://www.improbable.com/news/2012/Optimal-seating-chart.pdf\n", + "http://www.improbable.com/2012/02/12/finding-an-optimal-seating-chart-for-a-wedding\n", + "\n", + "Every year, millions of brides (not to mention their mothers, future\n", + "mothers-in-law, and occasionally grooms) struggle with one of the\n", + "most daunting tasks during the wedding-planning process: the\n", + "seating chart. The guest responses are in, banquet hall is booked,\n", + "menu choices have been made. You think the hard parts are over,\n", + "but you have yet to embark upon the biggest headache of them all.\n", + "In order to make this process easier, we present a mathematical\n", + "formulation that models the seating chart problem. This model can\n", + "be solved to find the optimal arrangement of guests at tables.\n", + "At the very least, it can provide a starting point and hopefully\n", + "minimize stress and arguments.\n", + "\n", + "Adapted from\n", + "https://github.com/google/or-tools/blob/master/examples/csharp/wedding_optimal_chart.cs\n", + "\"\"\"\n", + "\n", + "\n", + "# Instantiate a CP solver.\n", + "parameters = pywrapcp.Solver.DefaultSolverParameters()\n", + "solver = pywrapcp.Solver(\"WeddingOptimalChart\", parameters)\n", + "\n", + "#\n", + "# Data\n", + "#\n", + "\n", + "# Easy problem (from the paper)\n", + "# n = 2 # number of tables\n", + "# a = 10 # maximum number of guests a table can seat\n", + "# b = 1 # minimum number of people each guest knows at their table\n", + "\n", + "# Slightly harder problem (also from the paper)\n", + "n = 5 # number of tables\n", + "a = 4 # maximum number of guests a table can seat\n", + "b = 1 # minimum number of people each guest knows at their table\n", + "\n", + "# Connection matrix: who knows who, and how strong\n", + "# is the relation\n", + "C = [[1, 50, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0],\n", + " [50, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0],\n", + " [1, 1, 1, 50, 1, 1, 1, 1, 10, 0, 0, 0, 0, 0, 0, 0, 0],\n", + " [1, 1, 50, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0],\n", + " [1, 1, 1, 1, 1, 50, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0],\n", + " [1, 1, 1, 1, 50, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0],\n", + " [1, 1, 1, 1, 1, 1, 1, 50, 1, 0, 0, 0, 0, 0, 0, 0, 0],\n", + " [1, 1, 1, 1, 1, 1, 50, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0],\n", + " [1, 1, 10, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0],\n", + " [0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 50, 1, 1, 1, 1, 1, 1],\n", + " [0, 0, 0, 0, 0, 0, 0, 0, 0, 50, 1, 1, 1, 1, 1, 1, 1],\n", + " [0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1],\n", + " [0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1],\n", + " [0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1],\n", + " [0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1],\n", + " [0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1],\n", + " [0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1]]\n", + "\n", + "# Names of the guests. B: Bride side, G: Groom side\n", + "names = [\n", + " \"Deb (B)\", \"John (B)\", \"Martha (B)\", \"Travis (B)\", \"Allan (B)\",\n", + " \"Lois (B)\", \"Jayne (B)\", \"Brad (B)\", \"Abby (B)\", \"Mary Helen (G)\",\n", + " \"Lee (G)\", \"Annika (G)\", \"Carl (G)\", \"Colin (G)\", \"Shirley (G)\",\n", + " \"DeAnn (G)\", \"Lori (G)\"\n", + "]\n", + "\n", + "m = len(C)\n", + "\n", + "NRANGE = range(n)\n", + "MRANGE = range(m)\n", + "\n", + "#\n", + "# Decision variables\n", + "#\n", + "tables = [solver.IntVar(0, n - 1, \"x[%i]\" % i) for i in MRANGE]\n", + "\n", + "z = solver.Sum([\n", + " C[j][k] * (tables[j] == tables[k])\n", + " for j in MRANGE\n", + " for k in MRANGE\n", + " if j < k\n", + "])\n", + "\n", + "#\n", + "# Constraints\n", + "#\n", + "for i in NRANGE:\n", + " minGuests = [(tables[j] == i) * (tables[k] == i)\n", + " for j in MRANGE\n", + " for k in MRANGE\n", + " if j < k and C[j][k] > 0]\n", + " solver.Add(solver.Sum(minGuests) >= b)\n", + "\n", + " maxGuests = [tables[j] == i for j in MRANGE]\n", + " solver.Add(solver.Sum(maxGuests) <= a)\n", + "\n", + "# Symmetry breaking\n", + "solver.Add(tables[0] == 0)\n", + "\n", + "#\n", + "# Objective\n", + "#\n", + "objective = solver.Maximize(z, 1)\n", + "\n", + "#\n", + "# Search\n", + "#\n", + "db = solver.Phase(tables, solver.INT_VAR_DEFAULT, solver.INT_VALUE_DEFAULT)\n", + "\n", + "solver.NewSearch(db, [objective])\n", + "\n", + "while solver.NextSolution():\n", + " print(\"z:\", z)\n", + " print(\"Table: \")\n", + " for j in MRANGE:\n", + " print(tables[j].Value(), \" \")\n", + " print()\n", + "\n", + " for i in NRANGE:\n", + " print(\"Table %d: \" % i)\n", + " for j in MRANGE:\n", + " if tables[j].Value() == i:\n", + " print(names[j] + \" \")\n", + " print()\n", + "\n", + " print()\n", + "\n", + "solver.EndSearch()\n", + "\n", + "print()\n", + "print(\"Solutions: %d\" % solver.Solutions())\n", + "print(\"WallTime: %dms\" % solver.WallTime())\n", + "print(\"Failures: %d\" % solver.Failures())\n", + "print(\"Branches: %d\" % solver.Branches())\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/contrib/who_killed_agatha.ipynb b/examples/notebook/contrib/who_killed_agatha.ipynb new file mode 100644 index 0000000000..bc5f4e1f67 --- /dev/null +++ b/examples/notebook/contrib/who_killed_agatha.ipynb @@ -0,0 +1,221 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010 Hakan Kjellerstrand hakank@gmail.com\n", + "#\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"\n", + "\n", + " Who killed agatha? (The Dreadsbury Mansion Murder Mystery) in Google CP\n", + " Solver.\n", + "\n", + " This is a standard benchmark for theorem proving.\n", + "\n", + " http://www.lsv.ens-cachan.fr/~goubault/H1.dist/H1.1/Doc/h1003.html\n", + " '''\n", + " Someone in Dreadsbury Mansion killed Aunt Agatha.\n", + " Agatha, the butler, and Charles live in Dreadsbury Mansion, and\n", + " are the only ones to live there. A killer always hates, and is no\n", + " richer than his victim. Charles hates noone that Agatha hates. Agatha\n", + " hates everybody except the butler. The butler hates everyone not richer\n", + " than Aunt Agatha. The butler hates everyone whom Agatha hates.\n", + " Noone hates everyone. Who killed Agatha?\n", + " '''\n", + "\n", + " Originally from F. J. Pelletier:\n", + " Seventy-five problems for testing automatic theorem provers.\n", + " Journal of Automated Reasoning, 2: 216, 1986.\n", + "\n", + " Note1: Since Google CP Solver/Pythons (currently) don't have\n", + " special support for logical operations on decision\n", + " variables (i.e. ->, <->, and, or, etc), this model\n", + " use some IP modeling tricks.\n", + "\n", + " Note2: There are 8 different solutions, all stating that Agatha\n", + " killed herself\n", + "\n", + " Compare with the following models:\n", + " * Choco : http://www.hakank.org/choco/WhoKilledAgatha.java\n", + " * Choco : http://www.hakank.org/choco/WhoKilledAgatha_element.java\n", + " * Comet : http://www.hakank.org/comet/who_killed_agatha.co\n", + " * ECLiPSE : http://www.hakank.org/eclipse/who_killed_agatha.ecl\n", + " * Gecode : http://www.hakank.org/gecode/who_killed_agatha.cpp\n", + " * JaCoP : http://www.hakank.org/JaCoP/WhoKilledAgatha.java\n", + " * JaCoP : http://www.hakank.org/JaCoP/WhoKilledAgatha_element.java\n", + " * MiniZinc: http://www.hakank.org/minizinc/who_killed_agatha.mzn\n", + " * Tailor/Essence': http://www.hakank.org/tailor/who_killed_agatha.eprime\n", + " * SICStus : http://hakank.org/sicstus/who_killed_agatha.pl\n", + " * Zinc :http://hakank.org/minizinc/who_killed_agatha.zinc\n", + "\n", + "\n", + " This model was created by Hakan Kjellerstrand (hakank@gmail.com)\n", + " Also see my other Google CP Solver models:\n", + " http://www.hakank.org/google_or_tools/\n", + "\"\"\"\n", + "from __future__ import print_function\n", + "from collections import defaultdict\n", + "\n", + "from ortools.constraint_solver import pywrapcp\n", + "\n", + "\n", + "def var_matrix_array(solver, rows, cols, lb, ub, name):\n", + " x = []\n", + " for i in range(rows):\n", + " t = []\n", + " for j in range(cols):\n", + " t.append(solver.IntVar(lb, ub, \"%s[%i,%i]\" % (name, i, j)))\n", + " x.append(t)\n", + " return x\n", + "\n", + "\n", + "def flatten_matrix(solver, m, rows, cols):\n", + " return [m[i][j] for i in range(rows) for j in range(cols)]\n", + "\n", + "\n", + "def print_flat_matrix(m_flat, rows, cols):\n", + " for i in range(rows):\n", + " for j in range(cols):\n", + " print(m_flat[i * cols + j].Value(), end=\" \")\n", + " print()\n", + " print()\n", + "\n", + "\n", + "\n", + "# Create the solver.\n", + "solver = pywrapcp.Solver(\"Who killed agatha?\")\n", + "\n", + "#\n", + "# data\n", + "#\n", + "n = 3\n", + "agatha = 0\n", + "butler = 1\n", + "charles = 2\n", + "\n", + "#\n", + "# declare variables\n", + "#\n", + "the_killer = solver.IntVar(0, 2, \"the_killer\")\n", + "the_victim = solver.IntVar(0, 2, \"the_victim\")\n", + "\n", + "hates = var_matrix_array(solver, n, n, 0, 1, \"hates\")\n", + "richer = var_matrix_array(solver, n, n, 0, 1, \"richer\")\n", + "\n", + "hates_flat = flatten_matrix(solver, hates, n, n)\n", + "richer_flat = flatten_matrix(solver, richer, n, n)\n", + "\n", + "#\n", + "# constraints\n", + "#\n", + "\n", + "# Agatha, the butler, and Charles live in Dreadsbury Mansion, and\n", + "# are the only ones to live there.\n", + "\n", + "# A killer always hates, and is no richer than his victim.\n", + "# solver.Add(hates[the_killer, the_victim] == 1)\n", + "solver.Add(solver.Element(hates_flat, the_killer * n + the_victim) == 1)\n", + "\n", + "# solver.Add(richer[the_killer, the_victim] == 0)\n", + "solver.Add(solver.Element(richer_flat, the_killer * n + the_victim) == 0)\n", + "\n", + "# define the concept of richer: no one is richer than him-/herself\n", + "for i in range(n):\n", + " solver.Add(richer[i][i] == 0)\n", + "\n", + "# (contd...) if i is richer than j then j is not richer than i\n", + "# (i != j) => (richer[i,j] = 1) <=> (richer[j,i] = 0),\n", + "for i in range(n):\n", + " for j in range(n):\n", + " if i != j:\n", + " solver.Add((richer[i][j] == 1) == (richer[j][i] == 0))\n", + "\n", + "# Charles hates noone that Agatha hates.\n", + "# forall i : Range .\n", + "# (hates[agatha, i] = 1) => (hates[charles, i] = 0),\n", + "for i in range(n):\n", + " solver.Add((hates[agatha][i] == 1) <= (hates[charles][i] == 0))\n", + "\n", + "# Agatha hates everybody except the butler.\n", + "solver.Add(hates[agatha][charles] == 1)\n", + "solver.Add(hates[agatha][agatha] == 1)\n", + "solver.Add(hates[agatha][butler] == 0)\n", + "\n", + "# The butler hates everyone not richer than Aunt Agatha.\n", + "# forall i : Range .\n", + "# (richer[i, agatha] = 0) => (hates[butler, i] = 1),\n", + "for i in range(n):\n", + " solver.Add((richer[i][agatha] == 0) <= (hates[butler][i] == 1))\n", + "\n", + "# The butler hates everyone whom Agatha hates.\n", + "# forall i : Range .\n", + "# (hates[agatha, i] = 1) => (hates[butler, i] = 1),\n", + "for i in range(n):\n", + " solver.Add((hates[agatha][i] == 1) <= (hates[butler][i] == 1))\n", + "\n", + "# Noone hates everyone.\n", + "# forall i : Range .\n", + "# (sum j : Range . hates[i,j]) <= 2,\n", + "for i in range(n):\n", + " solver.Add(solver.Sum([hates[i][j] for j in range(n)]) <= 2)\n", + "\n", + "# Who killed Agatha?\n", + "solver.Add(the_victim == agatha)\n", + "\n", + "#\n", + "# solution and search\n", + "#\n", + "solution = solver.Assignment()\n", + "solution.Add(the_killer)\n", + "solution.Add(the_victim)\n", + "solution.Add(hates_flat)\n", + "solution.Add(richer_flat)\n", + "\n", + "# db: DecisionBuilder\n", + "db = solver.Phase(hates_flat + richer_flat, solver.CHOOSE_FIRST_UNBOUND,\n", + " solver.ASSIGN_MIN_VALUE)\n", + "\n", + "solver.NewSearch(db)\n", + "num_solutions = 0\n", + "while solver.NextSolution():\n", + " print(\"the_killer:\", the_killer.Value())\n", + " the_killers[the_killer.Value()] += 1\n", + " print(\"the_victim:\", the_victim.Value())\n", + " print(\"hates:\")\n", + " print_flat_matrix(hates_flat, n, n)\n", + " print(\"richer:\")\n", + " print_flat_matrix(richer_flat, n, n)\n", + " print()\n", + " num_solutions += 1\n", + "\n", + "solver.EndSearch()\n", + "\n", + "print()\n", + "print(\"num_solutions:\", num_solutions)\n", + "print(\"failures:\", solver.Failures())\n", + "print(\"branches:\", solver.Branches())\n", + "print(\"WallTime:\", solver.WallTime())\n", + "\n", + "the_killers = defaultdict(int)\n", + "p = [\"agatha\", \"butler\", \"charles\"]\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/contrib/xkcd.ipynb b/examples/notebook/contrib/xkcd.ipynb new file mode 100644 index 0000000000..155000ae16 --- /dev/null +++ b/examples/notebook/contrib/xkcd.ipynb @@ -0,0 +1,121 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010 Hakan Kjellerstrand hakank@gmail.com\n", + "#\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"\n", + "\n", + " xkcd problem (Knapsack) in Google CP Solver.\n", + "\n", + " http://xkcd.com/287/\n", + "\n", + " Some amount (or none) of each dish should be ordered to give a total\n", + " of exact 15.05\n", + "\n", + "\n", + " Compare with the following models:\n", + " * Comet: http://www.hakank.org/comet/xkcd.co\n", + " * ECLiPSE: http://www.hakank.org/eclipse/xkcd.ecl\n", + " * Gecode: http://www.hakank.org/gecode/xkcd.cpp\n", + " * Gecode/R: http://www.hakank.org/gecode_r/xkcd.rb\n", + " * MiniZinc: http://www.hakank.org/minizinc/xkcd.mzn\n", + " * Tailor: http://www.hakank.org/minizinc/xkcd.mzn\n", + " * SICtus: http://www.hakank.org/sicstus/xkcd.pl\n", + " * Zinc: http://www.hakank.org/minizinc/xkcd.zinc\n", + "\n", + "\n", + " This model was created by Hakan Kjellerstrand (hakank@gmail.com)\n", + " Also see my other Google CP Solver models:\n", + " http://www.hakank.org/google_cp_solver/\n", + "\"\"\"\n", + "from __future__ import print_function\n", + "from ortools.constraint_solver import pywrapcp\n", + "\n", + "\n", + "\n", + "# Create the solver.\n", + "solver = pywrapcp.Solver(\"xkcd knapsack\")\n", + "\n", + "#\n", + "# data\n", + "#\n", + "num_prices = 6\n", + "# for price and total: multiplied by 100 to be able to use integers\n", + "price = [215, 275, 335, 355, 420, 580]\n", + "total = 1505\n", + "\n", + "products = [\n", + " \"mixed fruit\", \"french fries\", \"side salad\", \"host wings\",\n", + " \"mozzarella sticks\", \"samples place\"\n", + "]\n", + "\n", + "# declare variables\n", + "\n", + "# how many items of each dish\n", + "x = [solver.IntVar(0, 10, \"x%i\" % i) for i in range(num_prices)]\n", + "z = solver.IntVar(0, 1505, \"z\")\n", + "\n", + "#\n", + "# constraints\n", + "#\n", + "solver.Add(z == solver.Sum([x[i] * price[i] for i in range(num_prices)]))\n", + "solver.Add(z == total)\n", + "\n", + "#\n", + "# solution and search\n", + "#\n", + "solution = solver.Assignment()\n", + "solution.Add([x[i] for i in range(num_prices)])\n", + "solution.Add(z)\n", + "\n", + "collector = solver.AllSolutionCollector(solution)\n", + "# collector = solver.FirstSolutionCollector(solution)\n", + "# search_log = solver.SearchLog(100, x[0])\n", + "solver.Solve(\n", + " solver.Phase([x[i] for i in range(num_prices)], solver.INT_VAR_SIMPLE,\n", + " solver.ASSIGN_MIN_VALUE), [collector])\n", + "\n", + "num_solutions = collector.SolutionCount()\n", + "print(\"num_solutions: \", num_solutions)\n", + "if num_solutions > 0:\n", + " for s in range(num_solutions):\n", + " print(\"z:\", collector.Value(s, z) / 100.0)\n", + " xval = [collector.Value(s, x[i]) for i in range(num_prices)]\n", + " print(\"x:\", xval)\n", + " for i in range(num_prices):\n", + " if xval[i] > 0:\n", + " print(xval[i], \"of\", products[i], \":\", price[i] / 100.0)\n", + " print()\n", + "\n", + " print()\n", + " print(\"num_solutions:\", num_solutions)\n", + " print(\"failures:\", solver.Failures())\n", + " print(\"branches:\", solver.Branches())\n", + " print(\"WallTime:\", solver.WallTime())\n", + "\n", + "else:\n", + " print(\"No solutions found\")\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/contrib/young_tableaux.ipynb b/examples/notebook/contrib/young_tableaux.ipynb new file mode 100644 index 0000000000..fce548f79d --- /dev/null +++ b/examples/notebook/contrib/young_tableaux.ipynb @@ -0,0 +1,172 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010 Hakan Kjellerstrand hakank@gmail.com\n", + "#\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"\n", + "\n", + " Young tableaux in Google CP Solver.\n", + "\n", + " See\n", + " http://mathworld.wolfram.com/YoungTableau.html\n", + " and\n", + " http://en.wikipedia.org/wiki/Young_tableau\n", + " '''\n", + " The partitions of 4 are\n", + " {4}, {3,1}, {2,2}, {2,1,1}, {1,1,1,1}\n", + "\n", + " And the corresponding standard Young tableaux are:\n", + "\n", + "1. 1 2 3 4\n", + "\n", + "2. 1 2 3 1 2 4 1 3 4\n", + " 4 3 2\n", + "\n", + "3. 1 2 1 3\n", + " 3 4 2 4\n", + "\n", + "4 1 2 1 3 1 4\n", + " 3 2 2\n", + " 4 4 3\n", + "\n", + "5. 1\n", + " 2\n", + " 3\n", + " 4\n", + " '''\n", + "\n", + " Thanks to Laurent Perron for improving this model.\n", + "\n", + " Compare with the following models:\n", + " * MiniZinc: http://www.hakank.org/minizinc/young_tableaux.mzn\n", + " * Choco : http://www.hakank.org/choco/YoungTableuax.java\n", + " * JaCoP : http://www.hakank.org/JaCoP/YoungTableuax.java\n", + " * Comet : http://www.hakank.org/comet/young_tableaux.co\n", + " * Gecode : http://www.hakank.org/gecode/young_tableaux.cpp\n", + " * ECLiPSe : http://www.hakank.org/eclipse/young_tableaux.ecl\n", + " * Tailor/Essence' : http://www.hakank.org/tailor/young_tableaux.eprime\n", + " * SICStus: http://hakank.org/sicstus/young_tableaux.pl\n", + " * Zinc: http://hakank.org/minizinc/young_tableaux.zinc\n", + "\n", + " This model was created by Hakan Kjellerstrand (hakank@gmail.com)\n", + " Also see my other Google CP Solver models:\n", + " http://www.hakank.org/google_or_tools/\n", + "\"\"\"\n", + "from __future__ import print_function\n", + "import sys\n", + "from ortools.constraint_solver import pywrapcp\n", + "\n", + "\n", + "\n", + "# Create the solver.\n", + "solver = pywrapcp.Solver(\"Problem\")\n", + "\n", + "#\n", + "# data\n", + "#\n", + "print(\"n:\", n)\n", + "\n", + "#\n", + "# declare variables\n", + "#\n", + "x = {}\n", + "for i in range(n):\n", + " for j in range(n):\n", + " x[(i, j)] = solver.IntVar(1, n + 1, \"x(%i,%i)\" % (i, j))\n", + "\n", + "x_flat = [x[(i, j)] for i in range(n) for j in range(n)]\n", + "\n", + "# partition structure\n", + "p = [solver.IntVar(0, n + 1, \"p%i\" % i) for i in range(n)]\n", + "\n", + "#\n", + "# constraints\n", + "#\n", + "\n", + "# 1..n is used exactly once\n", + "for i in range(1, n + 1):\n", + " solver.Add(solver.Count(x_flat, i, 1))\n", + "\n", + "solver.Add(x[(0, 0)] == 1)\n", + "\n", + "# row wise\n", + "for i in range(n):\n", + " for j in range(1, n):\n", + " solver.Add(x[(i, j)] >= x[(i, j - 1)])\n", + "\n", + "# column wise\n", + "for j in range(n):\n", + " for i in range(1, n):\n", + " solver.Add(x[(i, j)] >= x[(i - 1, j)])\n", + "\n", + "# calculate the structure (the partition)\n", + "for i in range(n):\n", + " # MiniZinc/Zinc version:\n", + " # p[i] == sum(j in 1..n) (bool2int(x[i,j] <= n))\n", + "\n", + " b = [solver.IsLessOrEqualCstVar(x[(i, j)], n) for j in range(n)]\n", + " solver.Add(p[i] == solver.Sum(b))\n", + "\n", + "solver.Add(solver.Sum(p) == n)\n", + "\n", + "for i in range(1, n):\n", + " solver.Add(p[i - 1] >= p[i])\n", + "\n", + "#\n", + "# solution and search\n", + "#\n", + "solution = solver.Assignment()\n", + "solution.Add(x_flat)\n", + "solution.Add(p)\n", + "\n", + "# db: DecisionBuilder\n", + "db = solver.Phase(x_flat + p, solver.CHOOSE_FIRST_UNBOUND,\n", + " solver.ASSIGN_MIN_VALUE)\n", + "\n", + "solver.NewSearch(db)\n", + "num_solutions = 0\n", + "while solver.NextSolution():\n", + " print(\"p:\", [p[i].Value() for i in range(n)])\n", + " print(\"x:\")\n", + " for i in range(n):\n", + " for j in range(n):\n", + " val = x_flat[i * n + j].Value()\n", + " if val <= n:\n", + " print(val, end=\" \")\n", + " if p[i].Value() > 0:\n", + " print()\n", + " print()\n", + " num_solutions += 1\n", + "\n", + "solver.EndSearch()\n", + "\n", + "print()\n", + "print(\"num_solutions:\", num_solutions)\n", + "print(\"failures:\", solver.Failures())\n", + "print(\"branches:\", solver.Branches())\n", + "print(\"WallTime:\", solver.WallTime())\n", + "\n", + "n = 5\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/cp_is_fun_sat.ipynb b/examples/notebook/cp_is_fun_sat.ipynb deleted file mode 100644 index 4651ea5151..0000000000 --- a/examples/notebook/cp_is_fun_sat.ipynb +++ /dev/null @@ -1,104 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Copyright 2010-2011 Google\n", - "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", - "# you may not use this file except in compliance with the License.\n", - "# You may obtain a copy of the License at\n", - "#\n", - "# http://www.apache.org/licenses/LICENSE-2.0\n", - "#\n", - "# Unless required by applicable law or agreed to in writing, software\n", - "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", - "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", - "# See the License for the specific language governing permissions and\n", - "# limitations under the License.\n", - "\"\"\"Cryptarithmetic puzzle\n", - "\n", - "First attempt to solve equation CP + IS + FUN = TRUE\n", - "where each letter represents a unique digit.\n", - "\n", - "This problem has 72 different solutions in base 10.\n", - "\"\"\"\n", - "\n", - "from __future__ import print_function\n", - "from ortools.sat.python import cp_model\n", - "\n", - "\n", - "class VarArraySolutionPrinter(cp_model.CpSolverSolutionCallback):\n", - " \"\"\"Print intermediate solutions.\"\"\"\n", - "\n", - " def __init__(self, variables):\n", - " cp_model.CpSolverSolutionCallback.__init__(self)\n", - " self.__variables = variables\n", - " self.__solution_count = 0\n", - "\n", - " def OnSolutionCallback(self):\n", - " self.__solution_count += 1\n", - " for v in self.__variables:\n", - " print('%s=%i' % (v, self.Value(v)), end=' ')\n", - " print()\n", - "\n", - " def SolutionCount(self):\n", - " return self.__solution_count\n", - "\n", - "\n", - "def CPIsFun():\n", - "\n", - " kBase = 10\n", - "\n", - " # Constraint programming engine\n", - " model = cp_model.CpModel()\n", - "\n", - " c = model.NewIntVar(1, 9, 'C')\n", - " p = model.NewIntVar(0, 9, 'P')\n", - " i = model.NewIntVar(1, 9, 'I')\n", - " s = model.NewIntVar(0, 9, 'S')\n", - " f = model.NewIntVar(1, 9, 'F')\n", - " u = model.NewIntVar(0, 9, 'U')\n", - " n = model.NewIntVar(0, 9, 'N')\n", - " t = model.NewIntVar(1, 9, 'T')\n", - " r = model.NewIntVar(0, 9, 'R')\n", - " e = model.NewIntVar(0, 9, 'E')\n", - "\n", - " # We need to group variables in a list to use the constraint AllDifferent.\n", - " letters = [c, p, i, s, f, u, n, t, r, e]\n", - "\n", - " # Verify that we have enough digits.\n", - " assert kBase >= len(letters)\n", - "\n", - " # Define constraints.\n", - " model.AddAllDifferent(letters)\n", - "\n", - " # CP + IS + FUN = TRUE\n", - " model.Add(p + s + n + kBase * (c + i + u) + kBase * kBase * f == e +\n", - " kBase * u + kBase * kBase * r + kBase * kBase * kBase * t)\n", - "\n", - " solver = cp_model.CpSolver()\n", - " status = solver.Solve(model)\n", - "\n", - " ### Solve model.\n", - " solver = cp_model.CpSolver()\n", - " solution_printer = VarArraySolutionPrinter(letters)\n", - " status = solver.SearchForAllSolutions(model, solution_printer)\n", - "\n", - " print()\n", - " print('Statistics')\n", - " print(' - conflicts : %i' % solver.NumConflicts())\n", - " print(' - branches : %i' % solver.NumBranches())\n", - " print(' - wall time : %f ms' % solver.WallTime())\n", - " print(' - solutions found : %i' % solution_printer.SolutionCount())\n", - "\n", - "\n" - ] - } - ], - "metadata": {}, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/examples/notebook/examples/appointments.ipynb b/examples/notebook/examples/appointments.ipynb new file mode 100644 index 0000000000..37fbb6e357 --- /dev/null +++ b/examples/notebook/examples/appointments.ipynb @@ -0,0 +1,176 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010-2018 Google LLC\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"Generates possible daily schedules for workers.\"\"\"\n", + "\n", + "from __future__ import print_function\n", + "from __future__ import division\n", + "\n", + "import argparse\n", + "from ortools.sat.python import cp_model\n", + "from ortools.linear_solver import pywraplp\n", + "\n", + "PARSER = argparse.ArgumentParser()\n", + "PARSER.add_argument(\n", + " '--load_min', default=480, type=int, help='Minimum load in minutes')\n", + "PARSER.add_argument(\n", + " '--load_max', default=540, type=int, help='Maximum load in minutes')\n", + "PARSER.add_argument(\n", + " '--commute_time', default=30, type=int, help='Commute time in minutes')\n", + "PARSER.add_argument(\n", + " '--num_workers', default=98, type=int, help='Maximum number of workers.')\n", + "\n", + "\n", + "class AllSolutionCollector(cp_model.CpSolverSolutionCallback):\n", + " \"\"\"Stores all solutions.\"\"\"\n", + "\n", + " def __init__(self, variables):\n", + " cp_model.CpSolverSolutionCallback.__init__(self)\n", + " self.__variables = variables\n", + " self.__collect = []\n", + "\n", + " def on_solution_callback(self):\n", + " \"\"\"Collect a new combination.\"\"\"\n", + " combination = [self.Value(v) for v in self.__variables]\n", + " self.__collect.append(combination)\n", + "\n", + " def combinations(self):\n", + " \"\"\"Returns all collected combinations.\"\"\"\n", + " return self.__collect\n", + "\n", + "\n", + "def find_combinations(durations, load_min, load_max, commute_time):\n", + " \"\"\"This methods find all valid combinations of appointments.\n", + "\n", + " This methods find all combinations of appointments such that the sum of\n", + " durations + commute times is between load_min and load_max.\n", + "\n", + " Args:\n", + " durations: The durations of all appointments.\n", + " load_min: The min number of worked minutes for a valid selection.\n", + " load_max: The max number of worked minutes for a valid selection.\n", + " commute_time: The commute time between two appointments in minutes.\n", + "\n", + " Returns:\n", + " A matrix where each line is a valid combinations of appointments.\n", + " \"\"\"\n", + " model = cp_model.CpModel()\n", + " variables = [\n", + " model.NewIntVar(0, load_max // (duration + commute_time), '')\n", + " for duration in durations\n", + " ]\n", + " terms = sum(variables[i] * (duration + commute_time)\n", + " for i, duration in enumerate(durations))\n", + " model.AddLinearConstraint(terms, load_min, load_max)\n", + "\n", + " solver = cp_model.CpSolver()\n", + " solution_collector = AllSolutionCollector(variables)\n", + " solver.SearchForAllSolutions(model, solution_collector)\n", + " return solution_collector.combinations()\n", + "\n", + "\n", + "def select(combinations, loads, max_number_of_workers):\n", + " \"\"\"This method selects the optimal combination of appointments.\n", + "\n", + " This method uses Mixed Integer Programming to select the optimal mix of\n", + " appointments.\n", + " \"\"\"\n", + " solver = pywraplp.Solver('Select',\n", + " pywraplp.Solver.CBC_MIXED_INTEGER_PROGRAMMING)\n", + " num_vars = len(loads)\n", + " num_combinations = len(combinations)\n", + " variables = [\n", + " solver.IntVar(0, max_number_of_workers, 's[%d]' % i)\n", + " for i in range(num_combinations)\n", + " ]\n", + " achieved = [\n", + " solver.IntVar(0, 1000, 'achieved[%d]' % i) for i in range(num_vars)\n", + " ]\n", + " transposed = [[\n", + " combinations[type][index] for type in range(num_combinations)\n", + " ] for index in range(num_vars)]\n", + "\n", + " # Maintain the achieved variables.\n", + " for i, coefs in enumerate(transposed):\n", + " ct = solver.Constraint(0.0, 0.0)\n", + " ct.SetCoefficient(achieved[i], -1)\n", + " for j, coef in enumerate(coefs):\n", + " ct.SetCoefficient(variables[j], coef)\n", + "\n", + " # Simple bound.\n", + " solver.Add(solver.Sum(variables) <= max_number_of_workers)\n", + "\n", + " obj_vars = [\n", + " solver.IntVar(0, 1000, 'obj_vars[%d]' % i) for i in range(num_vars)\n", + " ]\n", + " for i in range(num_vars):\n", + " solver.Add(obj_vars[i] >= achieved[i] - loads[i])\n", + " solver.Add(obj_vars[i] >= loads[i] - achieved[i])\n", + "\n", + " solver.Minimize(solver.Sum(obj_vars))\n", + "\n", + " result_status = solver.Solve()\n", + "\n", + " # The problem has an optimal solution.\n", + " if result_status == pywraplp.Solver.OPTIMAL:\n", + " print('Problem solved in %f milliseconds' % solver.WallTime())\n", + " return solver.Objective().Value(), [\n", + " int(v.SolutionValue()) for v in variables\n", + " ]\n", + " return -1, []\n", + "\n", + "\n", + "def get_optimal_schedule(demand, args):\n", + " \"\"\"Computes the optimal schedule for the appointment selection problem.\"\"\"\n", + " combinations = find_combinations([a[2] for a in demand], args.load_min,\n", + " args.load_max, args.commute_time)\n", + " print('found %d possible combinations of appointements' % len(combinations))\n", + "\n", + " cost, selection = select(combinations, [a[0]\n", + " for a in demand], args.num_workers)\n", + " output = [(selection[i], [(combinations[i][t], demand[t][1])\n", + " for t in range(len(demand))\n", + " if combinations[i][t] != 0])\n", + " for i in range(len(selection)) if selection[i] != 0]\n", + " return cost, output\n", + "\n", + "\n", + "\"\"\"Solve the assignment problem.\"\"\"\n", + "demand = [(40, 'A1', 90), (30, 'A2', 120), (25, 'A3', 180)]\n", + "print('appointments: ')\n", + "for a in demand:\n", + " print(' %d * %s : %d min' % (a[0], a[1], a[2]))\n", + "print('commute time = %d' % args.commute_time)\n", + "print('accepted total duration = [%d..%d]' % (args.load_min, args.load_max))\n", + "print('%d workers' % args.num_workers)\n", + "cost, selection = get_optimal_schedule(demand, args)\n", + "print('Optimal solution as a cost of %d' % cost)\n", + "for template in selection:\n", + " print('%d schedules with ' % template[0])\n", + " for t in template[1]:\n", + " print(' %d installation of type %s' % (t[0], t[1]))\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/examples/arc_flow_cutting_stock_sat.ipynb b/examples/notebook/examples/arc_flow_cutting_stock_sat.ipynb new file mode 100644 index 0000000000..d62b29129e --- /dev/null +++ b/examples/notebook/examples/arc_flow_cutting_stock_sat.ipynb @@ -0,0 +1,286 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010-2018 Google LLC\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"Cutting stock problem with the objective to minimize wasted space.\"\"\"\n", + "\n", + "from __future__ import print_function\n", + "\n", + "import argparse\n", + "import collections\n", + "import time\n", + "\n", + "from ortools.linear_solver import pywraplp\n", + "from ortools.sat.python import cp_model\n", + "\n", + "PARSER = argparse.ArgumentParser()\n", + "\n", + "PARSER.add_argument(\n", + " '--solver', default='sat', help='Method used to solve: sat, mip.')\n", + "PARSER.add_argument(\n", + " '--output_proto',\n", + " default='',\n", + " help='Output file to write the cp_model proto to.')\n", + "\n", + "DESIRED_LENGTHS = [\n", + " 2490, 3980, 2490, 3980, 2391, 2391, 2391, 596, 596, 596, 2456, 2456, 3018,\n", + " 938, 3018, 938, 943, 3018, 943, 3018, 2490, 3980, 2490, 3980, 2391, 2391,\n", + " 2391, 596, 596, 596, 2456, 2456, 3018, 938, 3018, 938, 943, 3018, 943,\n", + " 3018, 2890, 3980, 2890, 3980, 2391, 2391, 2391, 596, 596, 596, 2856, 2856,\n", + " 3018, 938, 3018, 938, 943, 3018, 943, 3018, 3290, 3980, 3290, 3980, 2391,\n", + " 2391, 2391, 596, 596, 596, 3256, 3256, 3018, 938, 3018, 938, 943, 3018,\n", + " 943, 3018, 3690, 3980, 3690, 3980, 2391, 2391, 2391, 596, 596, 596, 3656,\n", + " 3656, 3018, 938, 3018, 938, 943, 3018, 943, 3018, 2790, 3980, 2790, 3980,\n", + " 2391, 2391, 2391, 596, 596, 596, 2756, 2756, 3018, 938, 3018, 938, 943,\n", + " 3018, 943, 3018, 2790, 3980, 2790, 3980, 2391, 2391, 2391, 596, 596, 596,\n", + " 2756, 2756, 3018, 938, 3018, 938, 943\n", + "]\n", + "POSSIBLE_CAPACITIES = [4000, 5000, 6000, 7000, 8000]\n", + "\n", + "# Toy problem\n", + "# DESIRED_LENGTHS = [12, 12, 8, 8, 8]\n", + "# POSSIBLE_CAPACITIES = [10, 20]\n", + "\n", + "\n", + "def regroup_and_count(raw_input):\n", + " \"\"\"Regroup all equal capacities in a multiset.\"\"\"\n", + " grouped = collections.defaultdict(int)\n", + " for i in raw_input:\n", + " grouped[i] += 1\n", + " output = []\n", + " for size, count in grouped.items():\n", + " output.append([size, count])\n", + " output.sort(reverse=False)\n", + " return output\n", + "\n", + "\n", + "def price_usage(usage, capacities):\n", + " \"\"\"Compute the best price for a given usage and possible capacities.\"\"\"\n", + " price = max(capacities)\n", + " for capacity in capacities:\n", + " if capacity < usage:\n", + " continue\n", + " price = min(capacity - usage, price)\n", + " return price\n", + "\n", + "\n", + "def create_state_graph(items, max_capacity):\n", + " \"\"\"Create a state graph from a multiset of items, and a maximum capacity.\"\"\"\n", + " states = []\n", + " state_to_index = {}\n", + " states.append(0)\n", + " state_to_index[0] = 0\n", + " transitions = []\n", + "\n", + " for item_index, size_and_count in enumerate(items):\n", + " size, count = size_and_count\n", + " num_states = len(states)\n", + " for state_index in range(num_states):\n", + " current_state = states[state_index]\n", + " current_state_index = state_index\n", + "\n", + " for card in range(count):\n", + " new_state = current_state + size * (card + 1)\n", + " if new_state > max_capacity:\n", + " break\n", + " new_state_index = -1\n", + " if new_state in state_to_index:\n", + " new_state_index = state_to_index[new_state]\n", + " else:\n", + " new_state_index = len(states)\n", + " states.append(new_state)\n", + " state_to_index[new_state] = new_state_index\n", + " # Add the transition\n", + " transitions.append([\n", + " current_state_index, new_state_index, item_index, card + 1\n", + " ])\n", + "\n", + " return states, transitions\n", + "\n", + "\n", + "def solve_cutting_stock_with_arc_flow_and_sat(output_proto):\n", + " \"\"\"Solve the cutting stock with arc-flow and the CP-SAT solver.\"\"\"\n", + " items = regroup_and_count(DESIRED_LENGTHS)\n", + " print('Items:', items)\n", + " num_items = len(DESIRED_LENGTHS)\n", + "\n", + " max_capacity = max(POSSIBLE_CAPACITIES)\n", + " states, transitions = create_state_graph(items, max_capacity)\n", + "\n", + " print('Dynamic programming has generated', len(states), 'states and',\n", + " len(transitions), 'transitions')\n", + "\n", + " incoming_vars = collections.defaultdict(list)\n", + " outgoing_vars = collections.defaultdict(list)\n", + " incoming_sink_vars = []\n", + " item_vars = collections.defaultdict(list)\n", + " item_coeffs = collections.defaultdict(list)\n", + " transition_vars = []\n", + "\n", + " model = cp_model.CpModel()\n", + "\n", + " objective_vars = []\n", + " objective_coeffs = []\n", + "\n", + " for outgoing, incoming, item_index, card in transitions:\n", + " count = items[item_index][1]\n", + " max_count = count // card\n", + " count_var = model.NewIntVar(\n", + " 0, max_count,\n", + " 'i%i_f%i_t%i_C%s' % (item_index, incoming, outgoing, card))\n", + " incoming_vars[incoming].append(count_var)\n", + " outgoing_vars[outgoing].append(count_var)\n", + " item_vars[item_index].append(count_var)\n", + " item_coeffs[item_index].append(card)\n", + " transition_vars.append(count_var)\n", + "\n", + " for state_index, state in enumerate(states):\n", + " if state_index == 0:\n", + " continue\n", + " exit_var = model.NewIntVar(0, num_items, 'e%i' % state_index)\n", + " outgoing_vars[state_index].append(exit_var)\n", + " incoming_sink_vars.append(exit_var)\n", + " price = price_usage(state, POSSIBLE_CAPACITIES)\n", + " objective_vars.append(exit_var)\n", + " objective_coeffs.append(price)\n", + "\n", + " # Flow conservation\n", + " for state_index in range(1, len(states)):\n", + " model.Add(\n", + " sum(incoming_vars[state_index]) == sum(outgoing_vars[state_index]))\n", + "\n", + " # Flow going out of the source must go in the sink\n", + " model.Add(sum(outgoing_vars[0]) == sum(incoming_sink_vars))\n", + "\n", + " # Items must be placed\n", + " for item_index, size_and_count in enumerate(items):\n", + " num_arcs = len(item_vars[item_index])\n", + " model.Add(\n", + " sum(item_vars[item_index][i] * item_coeffs[item_index][i]\n", + " for i in range(num_arcs)) == size_and_count[1])\n", + "\n", + " # Objective is the sum of waste\n", + " model.Minimize(\n", + " sum(objective_vars[i] * objective_coeffs[i]\n", + " for i in range(len(objective_vars))))\n", + "\n", + " # Output model proto to file.\n", + " if output_proto:\n", + " output_file = open(output_proto, 'w')\n", + " output_file.write(str(model.Proto()))\n", + " output_file.close()\n", + "\n", + " # Solve model.\n", + " solver = cp_model.CpSolver()\n", + " solver.parameters.log_search_progress = True\n", + " solver.parameters.num_search_workers = 8\n", + " status = solver.Solve(model)\n", + " print(solver.ResponseStats())\n", + "\n", + "\n", + "def solve_cutting_stock_with_arc_flow_and_mip():\n", + " \"\"\"Solve the cutting stock with arc-flow and a MIP solver.\"\"\"\n", + " items = regroup_and_count(DESIRED_LENGTHS)\n", + " print('Items:', items)\n", + " num_items = len(DESIRED_LENGTHS)\n", + " max_capacity = max(POSSIBLE_CAPACITIES)\n", + " states, transitions = create_state_graph(items, max_capacity)\n", + "\n", + " print('Dynamic programming has generated', len(states), 'states and',\n", + " len(transitions), 'transitions')\n", + "\n", + " incoming_vars = collections.defaultdict(list)\n", + " outgoing_vars = collections.defaultdict(list)\n", + " incoming_sink_vars = []\n", + " item_vars = collections.defaultdict(list)\n", + " item_coeffs = collections.defaultdict(list)\n", + "\n", + " start_time = time.time()\n", + " solver = pywraplp.Solver('Steel',\n", + " pywraplp.Solver.CBC_MIXED_INTEGER_PROGRAMMING)\n", + "\n", + " objective_vars = []\n", + " objective_coeffs = []\n", + "\n", + " var_index = 0\n", + " for outgoing, incoming, item_index, card in transitions:\n", + " count = items[item_index][1]\n", + " count_var = solver.IntVar(\n", + " 0, count, 'a%i_i%i_f%i_t%i_c%i' % (var_index, item_index, incoming,\n", + " outgoing, card))\n", + " var_index += 1\n", + " incoming_vars[incoming].append(count_var)\n", + " outgoing_vars[outgoing].append(count_var)\n", + " item_vars[item_index].append(count_var)\n", + " item_coeffs[item_index].append(card)\n", + "\n", + " for state_index, state in enumerate(states):\n", + " if state_index == 0:\n", + " continue\n", + " exit_var = solver.IntVar(0, num_items, 'e%i' % state_index)\n", + " outgoing_vars[state_index].append(exit_var)\n", + " incoming_sink_vars.append(exit_var)\n", + " price = price_usage(state, POSSIBLE_CAPACITIES)\n", + " objective_vars.append(exit_var)\n", + " objective_coeffs.append(price)\n", + "\n", + " # Flow conservation\n", + " for state_index in range(1, len(states)):\n", + " solver.Add(\n", + " sum(incoming_vars[state_index]) == sum(outgoing_vars[state_index]))\n", + "\n", + " # Flow going out of the source must go in the sink\n", + " solver.Add(sum(outgoing_vars[0]) == sum(incoming_sink_vars))\n", + "\n", + " # Items must be placed\n", + " for item_index, size_and_count in enumerate(items):\n", + " num_arcs = len(item_vars[item_index])\n", + " solver.Add(\n", + " sum(item_vars[item_index][i] * item_coeffs[item_index][i]\n", + " for i in range(num_arcs)) == size_and_count[1])\n", + "\n", + " # Objective is the sum of waste\n", + " solver.Minimize(\n", + " sum(objective_vars[i] * objective_coeffs[i]\n", + " for i in range(len(objective_vars))))\n", + " solver.EnableOutput()\n", + "\n", + " status = solver.Solve()\n", + "\n", + " ### Output the solution.\n", + " if status == pywraplp.Solver.OPTIMAL:\n", + " print('Objective value = %f found in %.2f s' %\n", + " (solver.Objective().Value(), time.time() - start_time))\n", + " else:\n", + " print('No solution')\n", + "\n", + "\n", + "\"\"\"Main function\"\"\"\n", + "if args.solver == 'sat':\n", + " solve_cutting_stock_with_arc_flow_and_sat(args.output_proto)\n", + "else: # 'mip'\n", + " solve_cutting_stock_with_arc_flow_and_mip()\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/assignment_sat.ipynb b/examples/notebook/examples/assignment_sat.ipynb similarity index 62% rename from examples/notebook/assignment_sat.ipynb rename to examples/notebook/examples/assignment_sat.ipynb index 257974c1f9..01340c3cd4 100644 --- a/examples/notebook/assignment_sat.ipynb +++ b/examples/notebook/examples/assignment_sat.ipynb @@ -6,7 +6,7 @@ "metadata": {}, "outputs": [], "source": [ - "# Copyright 2010-2017 Google\n", + "# Copyright 2010-2018 Google LLC\n", "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", "# you may not use this file except in compliance with the License.\n", "# You may obtain a copy of the License at\n", @@ -24,12 +24,15 @@ "\n", "\n", "# Instantiate a cp model.\n", - "cost = [[90, 76, 75, 70, 50, 74, 12, 68], [35, 85, 55, 65, 48, 101, 70, 83], [\n", - " 125, 95, 90, 105, 59, 120, 36, 73\n", - "], [45, 110, 95, 115, 104, 83, 37, 71], [60, 105, 80, 75, 59, 62, 93, 88], [\n", - " 45, 65, 110, 95, 47, 31, 81, 34\n", - "], [38, 51, 107, 41, 69, 99, 115, 48], [47, 85, 57, 71, 92, 77, 109, 36],\n", - " [39, 63, 97, 49, 118, 56, 92, 61], [47, 101, 71, 60, 88, 109, 52, 90]]\n", + "cost = [[90, 76, 75, 70, 50, 74, 12, 68], [35, 85, 55, 65, 48, 101, 70, 83],\n", + " [125, 95, 90, 105, 59,\n", + " 120, 36, 73], [45, 110, 95, 115, 104, 83, 37,\n", + " 71], [60, 105, 80, 75, 59, 62, 93,\n", + " 88], [45, 65, 110, 95, 47, 31, 81, 34],\n", + " [38, 51, 107, 41, 69, 99, 115,\n", + " 48], [47, 85, 57, 71, 92, 77, 109,\n", + " 36], [39, 63, 97, 49, 118, 56,\n", + " 92, 61], [47, 101, 71, 60, 88, 109, 52, 90]]\n", "\n", "sizes = [10, 7, 3, 12, 15, 4, 11, 5]\n", "total_size_max = 15\n", @@ -43,10 +46,10 @@ "total_cost = model.NewIntVar(0, 1000, 'total_cost')\n", "x = []\n", "for i in all_workers:\n", - " t = []\n", - " for j in all_tasks:\n", - " t.append(model.NewBoolVar('x[%i,%i]' % (i, j)))\n", - " x.append(t)\n", + " t = []\n", + " for j in all_tasks:\n", + " t.append(model.NewBoolVar('x[%i,%i]' % (i, j)))\n", + " x.append(t)\n", "\n", "# Constraints\n", "\n", @@ -55,35 +58,36 @@ "\n", "# Total task size for each worker is at most total_size_max\n", "for i in all_workers:\n", - " model.Add(sum(sizes[j] * x[i][j] for j in all_tasks) <= total_size_max)\n", + " model.Add(sum(sizes[j] * x[i][j] for j in all_tasks) <= total_size_max)\n", "\n", "# Total cost\n", - "model.Add(total_cost == sum(\n", - " x[i][j] * cost[i][j] for j in all_tasks for i in all_workers))\n", + "model.Add(total_cost == sum(x[i][j] * cost[i][j]\n", + " for j in all_tasks for i in all_workers))\n", "model.Minimize(total_cost)\n", "\n", "solver = cp_model.CpSolver()\n", "status = solver.Solve(model)\n", "\n", "if status == cp_model.OPTIMAL:\n", - " print('Total cost = %i' % solver.ObjectiveValue())\n", - " print()\n", - " for i in all_workers:\n", - " for j in all_tasks:\n", - " if solver.Value(x[i][j]) == 1:\n", - " print('Worker ', i, ' assigned to task ', j, ' Cost = ', cost[i][j])\n", + " print('Total cost = %i' % solver.ObjectiveValue())\n", + " print()\n", + " for i in all_workers:\n", + " for j in all_tasks:\n", + " if solver.Value(x[i][j]) == 1:\n", + " print('Worker ', i, ' assigned to task ', j, ' Cost = ',\n", + " cost[i][j])\n", "\n", - " print()\n", + " print()\n", "\n", "print('Statistics')\n", "print(' - conflicts : %i' % solver.NumConflicts())\n", "print(' - branches : %i' % solver.NumBranches())\n", - "print(' - wall time : %f ms' % solver.WallTime())\n", + "print(' - wall time : %f s' % solver.WallTime())\n", "\n" ] } ], "metadata": {}, "nbformat": 4, - "nbformat_minor": 2 + "nbformat_minor": 4 } diff --git a/examples/notebook/examples/assignment_with_constraints_sat.ipynb b/examples/notebook/examples/assignment_with_constraints_sat.ipynb new file mode 100644 index 0000000000..29b8ac580b --- /dev/null +++ b/examples/notebook/examples/assignment_with_constraints_sat.ipynb @@ -0,0 +1,136 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010-2018 Google LLC\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"Solve an assignment problem with combination constraints on workers.\"\"\"\n", + "\n", + "from __future__ import print_function\n", + "\n", + "from ortools.sat.python import cp_model\n", + "\n", + "\n", + "def solve_assignment():\n", + " \"\"\"Solve the assignment problem.\"\"\"\n", + " # Data.\n", + " cost = [[90, 76, 75, 70, 50, 74], [35, 85, 55, 65, 48,\n", + " 101], [125, 95, 90, 105, 59, 120],\n", + " [45, 110, 95, 115, 104, 83], [60, 105, 80, 75, 59, 62], [\n", + " 45, 65, 110, 95, 47, 31\n", + " ], [38, 51, 107, 41, 69, 99], [47, 85, 57, 71,\n", + " 92, 77], [39, 63, 97, 49, 118, 56],\n", + " [47, 101, 71, 60, 88, 109], [17, 39, 103, 64, 61,\n", + " 92], [101, 45, 83, 59, 92, 27]]\n", + "\n", + " group1 = [\n", + " [0, 0, 1, 1], # Workers 2, 3\n", + " [0, 1, 0, 1], # Workers 1, 3\n", + " [0, 1, 1, 0], # Workers 1, 2\n", + " [1, 1, 0, 0], # Workers 0, 1\n", + " [1, 0, 1, 0]\n", + " ] # Workers 0, 2\n", + "\n", + " group2 = [\n", + " [0, 0, 1, 1], # Workers 6, 7\n", + " [0, 1, 0, 1], # Workers 5, 7\n", + " [0, 1, 1, 0], # Workers 5, 6\n", + " [1, 1, 0, 0], # Workers 4, 5\n", + " [1, 0, 0, 1]\n", + " ] # Workers 4, 7\n", + "\n", + " group3 = [\n", + " [0, 0, 1, 1], # Workers 10, 11\n", + " [0, 1, 0, 1], # Workers 9, 11\n", + " [0, 1, 1, 0], # Workers 9, 10\n", + " [1, 0, 1, 0], # Workers 8, 10\n", + " [1, 0, 0, 1]\n", + " ] # Workers 8, 11\n", + "\n", + " sizes = [10, 7, 3, 12, 15, 4, 11, 5]\n", + " total_size_max = 15\n", + " num_workers = len(cost)\n", + " num_tasks = len(cost[1])\n", + " all_workers = range(num_workers)\n", + " all_tasks = range(num_tasks)\n", + "\n", + " # Model.\n", + "\n", + " model = cp_model.CpModel()\n", + " # Variables\n", + " selected = [[model.NewBoolVar('x[%i,%i]' % (i, j)) for j in all_tasks]\n", + " for i in all_workers]\n", + " works = [model.NewBoolVar('works[%i]' % i) for i in all_workers]\n", + "\n", + " # Constraints\n", + "\n", + " # Link selected and workers.\n", + " for i in range(num_workers):\n", + " model.AddMaxEquality(works[i], selected[i])\n", + "\n", + " # Each task is assigned to at least one worker.\n", + " for j in all_tasks:\n", + " model.Add(sum(selected[i][j] for i in all_workers) >= 1)\n", + "\n", + " # Total task size for each worker is at most total_size_max\n", + " for i in all_workers:\n", + " model.Add(\n", + " sum(sizes[j] * selected[i][j] for j in all_tasks) <= total_size_max)\n", + "\n", + " # Group constraints.\n", + " model.AddAllowedAssignments([works[0], works[1], works[2], works[3]],\n", + " group1)\n", + " model.AddAllowedAssignments([works[4], works[5], works[6], works[7]],\n", + " group2)\n", + " model.AddAllowedAssignments([works[8], works[9], works[10], works[11]],\n", + " group3)\n", + "\n", + " # Objective\n", + " model.Minimize(\n", + " sum(selected[i][j] * cost[i][j] for j in all_tasks\n", + " for i in all_workers))\n", + "\n", + " # Solve and output solution.\n", + " solver = cp_model.CpSolver()\n", + " status = solver.Solve(model)\n", + "\n", + " if status == cp_model.OPTIMAL:\n", + " print('Total cost = %i' % solver.ObjectiveValue())\n", + " print()\n", + " for i in all_workers:\n", + " for j in all_tasks:\n", + " if solver.BooleanValue(selected[i][j]):\n", + " print('Worker ', i, ' assigned to task ', j, ' Cost = ',\n", + " cost[i][j])\n", + "\n", + " print()\n", + "\n", + " print('Statistics')\n", + " print(' - conflicts : %i' % solver.NumConflicts())\n", + " print(' - branches : %i' % solver.NumBranches())\n", + " print(' - wall time : %f s' % solver.WallTime())\n", + "\n", + "\n", + "solve_assignment()\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/examples/balance_group_sat.ipynb b/examples/notebook/examples/balance_group_sat.ipynb new file mode 100644 index 0000000000..6bc3c5a985 --- /dev/null +++ b/examples/notebook/examples/balance_group_sat.ipynb @@ -0,0 +1,190 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010-2018 Google LLC\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"We are trying to group items in equal sized groups.\n", + "\n", + "Each item has a color and a value. We want the sum of values of each group to\n", + "be as close to the average as possible.\n", + "Furthermore, if one color is an a group, at least k items with this color must\n", + "be in that group.\n", + "\"\"\"\n", + "\n", + "from __future__ import print_function\n", + "from __future__ import division\n", + "\n", + "from ortools.sat.python import cp_model\n", + "\n", + "\n", + "# Create a solution printer.\n", + "class SolutionPrinter(cp_model.CpSolverSolutionCallback):\n", + " \"\"\"Print intermediate solutions.\"\"\"\n", + "\n", + " def __init__(self, values, colors, all_groups, all_items, item_in_group):\n", + " cp_model.CpSolverSolutionCallback.__init__(self)\n", + " self.__solution_count = 0\n", + " self.__values = values\n", + " self.__colors = colors\n", + " self.__all_groups = all_groups\n", + " self.__all_items = all_items\n", + " self.__item_in_group = item_in_group\n", + "\n", + " def on_solution_callback(self):\n", + " print('Solution %i' % self.__solution_count)\n", + " self.__solution_count += 1\n", + "\n", + " print(' objective value = %i' % self.ObjectiveValue())\n", + " groups = {}\n", + " sums = {}\n", + " for g in self.__all_groups:\n", + " groups[g] = []\n", + " sums[g] = 0\n", + " for item in self.__all_items:\n", + " if self.BooleanValue(self.__item_in_group[(item, g)]):\n", + " groups[g].append(item)\n", + " sums[g] += self.__values[item]\n", + "\n", + " for g in self.__all_groups:\n", + " group = groups[g]\n", + " print('group %i: sum = %0.2f [' % (g, sums[g]), end='')\n", + " for item in group:\n", + " value = self.__values[item]\n", + " color = self.__colors[item]\n", + " print(' (%i, %i, %i)' % (item, value, color), end='')\n", + " print(']')\n", + "\n", + "\n", + "# Data.\n", + "num_groups = 10\n", + "num_items = 100\n", + "num_colors = 3\n", + "min_items_of_same_color_per_group = 4\n", + "\n", + "all_groups = range(num_groups)\n", + "all_items = range(num_items)\n", + "all_colors = range(num_colors)\n", + "\n", + "# Values for each items.\n", + "values = [1 + i + (i * i // 200) for i in all_items]\n", + "# Color for each item (simple modulo).\n", + "colors = [i % num_colors for i in all_items]\n", + "\n", + "sum_of_values = sum(values)\n", + "average_sum_per_group = sum_of_values // num_groups\n", + "\n", + "num_items_per_group = num_items // num_groups\n", + "\n", + "# Collect all items in a given color.\n", + "items_per_color = {}\n", + "for c in all_colors:\n", + " items_per_color[c] = []\n", + " for i in all_items:\n", + " if colors[i] == c:\n", + " items_per_color[c].append(i)\n", + "\n", + "print('Model has %i items, %i groups, and %i colors' %\n", + " (num_items, num_groups, num_colors))\n", + "print(' average sum per group = %i' % average_sum_per_group)\n", + "\n", + "# Model.\n", + "\n", + "model = cp_model.CpModel()\n", + "\n", + "item_in_group = {}\n", + "for i in all_items:\n", + " for g in all_groups:\n", + " item_in_group[(i, g)] = model.NewBoolVar('item %d in group %d' %\n", + " (i, g))\n", + "\n", + "# Each group must have the same size.\n", + "for g in all_groups:\n", + " model.Add(\n", + " sum(item_in_group[(i, g)]\n", + " for i in all_items) == num_items_per_group)\n", + "\n", + "# One item must belong to exactly one group.\n", + "for i in all_items:\n", + " model.Add(sum(item_in_group[(i, g)] for g in all_groups) == 1)\n", + "\n", + "# The deviation of the sum of each items in a group against the average.\n", + "e = model.NewIntVar(0, 550, 'epsilon')\n", + "\n", + "# Constrain the sum of values in one group around the average sum per group.\n", + "for g in all_groups:\n", + " model.Add(\n", + " sum(item_in_group[(i, g)] * values[i]\n", + " for i in all_items) <= average_sum_per_group + e)\n", + " model.Add(\n", + " sum(item_in_group[(i, g)] * values[i]\n", + " for i in all_items) >= average_sum_per_group - e)\n", + "\n", + "# color_in_group variables.\n", + "color_in_group = {}\n", + "for g in all_groups:\n", + " for c in all_colors:\n", + " color_in_group[(c, g)] = model.NewBoolVar(\n", + " 'color %d is in group %d' % (c, g))\n", + "\n", + "# Item is in a group implies its color is in that group.\n", + "for i in all_items:\n", + " for g in all_groups:\n", + " model.AddImplication(item_in_group[(i, g)],\n", + " color_in_group[(colors[i], g)])\n", + "\n", + "# If a color is in a group, it must contains at least\n", + "# min_items_of_same_color_per_group items from that color.\n", + "for c in all_colors:\n", + " for g in all_groups:\n", + " literal = color_in_group[(c, g)]\n", + " model.Add(\n", + " sum(item_in_group[(i, g)] for i in items_per_color[c]) >=\n", + " min_items_of_same_color_per_group).OnlyEnforceIf(literal)\n", + "\n", + "# Compute the maximum number of colors in a group.\n", + "max_color = num_items_per_group // min_items_of_same_color_per_group\n", + "# Redundant contraint: The problem does not solve in reasonable time without it.\n", + "if max_color < num_colors:\n", + " for g in all_groups:\n", + " model.Add(\n", + " sum(color_in_group[(c, g)] for c in all_colors) <= max_color)\n", + "\n", + "# Minimize epsilon\n", + "model.Minimize(e)\n", + "\n", + "solver = cp_model.CpSolver()\n", + "solution_printer = SolutionPrinter(values, colors, all_groups, all_items,\n", + " item_in_group)\n", + "status = solver.SolveWithSolutionCallback(model, solution_printer)\n", + "\n", + "if status == cp_model.OPTIMAL:\n", + " print('Optimal epsilon: %i' % solver.ObjectiveValue())\n", + " print('Statistics')\n", + " print(' - conflicts : %i' % solver.NumConflicts())\n", + " print(' - branches : %i' % solver.NumBranches())\n", + " print(' - wall time : %f s' % solver.WallTime())\n", + "else:\n", + " print('No solution found')\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/examples/bus_driver_scheduling_flow_sat.ipynb b/examples/notebook/examples/bus_driver_scheduling_flow_sat.ipynb new file mode 100644 index 0000000000..567afdfba4 --- /dev/null +++ b/examples/notebook/examples/bus_driver_scheduling_flow_sat.ipynb @@ -0,0 +1,1837 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010-2018 Google LLC\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"This model implements a bus driver scheduling problem.\n", + "\n", + "Constraints:\n", + "- max driving time per driver <= 9h\n", + "- max working time per driver <= 12h\n", + "- min working time per driver >= 6.5h (soft)\n", + "- 30 min break after each 4h of driving time per driver\n", + "- 10 min preparation time before the first shift\n", + "- 15 min cleaning time after the last shift\n", + "- 2 min waiting time after each shift for passenger boarding and alighting\n", + "\"\"\"\n", + "from __future__ import print_function\n", + "\n", + "import argparse\n", + "import collections\n", + "import math\n", + "\n", + "from ortools.sat.python import cp_model\n", + "\n", + "PARSER = argparse.ArgumentParser()\n", + "PARSER.add_argument(\n", + " '--instance', default=1, type=int, help='Instance number (1..3).')\n", + "PARSER.add_argument(\n", + " '--output_proto',\n", + " default=\"\",\n", + " help='Output file to write the cp_model'\n", + " 'proto to.')\n", + "PARSER.add_argument('--params', default=\"\", help='Sat solver parameters.')\n", + "\n", + "SAMPLE_SHIFTS_SMALL = [\n", + " #\n", + " # column description:\n", + " # - shift id\n", + " # - shift start time as hh:mm string (for logging and readability purposes)\n", + " # - shift end time as hh:mm string (for logging and readability purposes)\n", + " # - shift start minute\n", + " # - shift end minute\n", + " # - shift duration in minutes\n", + " #\n", + " [0, '05:18', '06:00', 318, 360, 42],\n", + " [1, '05:26', '06:08', 326, 368, 42],\n", + " [2, '05:40', '05:56', 340, 356, 16],\n", + " [3, '06:06', '06:51', 366, 411, 45],\n", + " [4, '06:40', '07:52', 400, 472, 72],\n", + " [5, '06:42', '07:13', 402, 433, 31],\n", + " [6, '06:48', '08:15', 408, 495, 87],\n", + " [7, '06:59', '08:07', 419, 487, 68],\n", + " [8, '07:20', '07:36', 440, 456, 16],\n", + " [9, '07:35', '08:22', 455, 502, 47],\n", + " [10, '07:50', '08:55', 470, 535, 65],\n", + " [11, '08:00', '09:05', 480, 545, 65],\n", + " [12, '08:00', '08:35', 480, 515, 35],\n", + " [13, '08:11', '09:41', 491, 581, 90],\n", + " [14, '08:28', '08:50', 508, 530, 22],\n", + " [15, '08:35', '08:45', 515, 525, 10],\n", + " [16, '08:40', '08:50', 520, 530, 10],\n", + " [17, '09:03', '10:28', 543, 628, 85],\n", + " [18, '09:23', '09:49', 563, 589, 26],\n", + " [19, '09:30', '09:40', 570, 580, 10],\n", + " [20, '09:57', '10:20', 597, 620, 23],\n", + " [21, '10:09', '11:03', 609, 663, 54],\n", + " [22, '10:20', '10:30', 620, 630, 10],\n", + " [23, '11:00', '11:10', 660, 670, 10],\n", + " [24, '11:45', '12:24', 705, 744, 39],\n", + " [25, '12:18', '13:00', 738, 780, 42],\n", + " [26, '13:18', '14:44', 798, 884, 86],\n", + " [27, '13:53', '14:49', 833, 889, 56],\n", + " [28, '14:03', '14:50', 843, 890, 47],\n", + " [29, '14:28', '15:15', 868, 915, 47],\n", + " [30, '14:30', '15:41', 870, 941, 71],\n", + " [31, '14:48', '15:35', 888, 935, 47],\n", + " [32, '15:03', '15:50', 903, 950, 47],\n", + " [33, '15:28', '16:54', 928, 1014, 86],\n", + " [34, '15:38', '16:25', 938, 985, 47],\n", + " [35, '15:40', '15:56', 940, 956, 16],\n", + " [36, '15:58', '16:45', 958, 1005, 47],\n", + " [37, '16:04', '17:30', 964, 1050, 86],\n", + " [38, '16:28', '17:15', 988, 1035, 47],\n", + " [39, '16:36', '17:21', 996, 1041, 45],\n", + " [40, '16:50', '17:00', 1010, 1020, 10],\n", + " [41, '16:54', '18:20', 1014, 1100, 86],\n", + " [42, '17:01', '17:13', 1021, 1033, 12],\n", + " [43, '17:19', '18:31', 1039, 1111, 72],\n", + " [44, '17:23', '18:10', 1043, 1090, 47],\n", + " [45, '17:34', '18:15', 1054, 1095, 41],\n", + " [46, '18:04', '19:29', 1084, 1169, 85],\n", + " [47, '18:34', '19:58', 1114, 1198, 84],\n", + " [48, '19:56', '20:34', 1196, 1234, 38],\n", + " [49, '20:05', '20:48', 1205, 1248, 43]\n", + "] # yapf:disable\n", + "\n", + "SAMPLE_SHIFTS_MEDIUM = [\n", + " [0, '04:30', '04:53', 270, 293, 23],\n", + " [1, '04:46', '04:56', 286, 296, 10],\n", + " [2, '04:52', '05:56', 292, 356, 64],\n", + " [3, '04:53', '05:23', 293, 323, 30],\n", + " [4, '05:07', '05:44', 307, 344, 37],\n", + " [5, '05:10', '06:06', 310, 366, 56],\n", + " [6, '05:18', '06:03', 318, 363, 45],\n", + " [7, '05:30', '05:40', 330, 340, 10],\n", + " [8, '05:30', '05:40', 330, 340, 10],\n", + " [9, '05:33', '06:15', 333, 375, 42],\n", + " [10, '05:40', '05:50', 340, 350, 10],\n", + " [11, '05:43', '06:08', 343, 368, 25],\n", + " [12, '05:54', '07:20', 354, 440, 86],\n", + " [13, '06:04', '06:37', 364, 397, 33],\n", + " [14, '06:13', '06:58', 373, 418, 45],\n", + " [15, '06:14', '07:40', 374, 460, 86],\n", + " [16, '06:15', '07:15', 375, 435, 60],\n", + " [17, '06:16', '06:26', 376, 386, 10],\n", + " [18, '06:17', '06:34', 377, 394, 17],\n", + " [19, '06:20', '06:36', 380, 396, 16],\n", + " [20, '06:22', '07:06', 382, 426, 44],\n", + " [21, '06:24', '07:50', 384, 470, 86],\n", + " [22, '06:27', '06:44', 387, 404, 17],\n", + " [23, '06:30', '06:40', 390, 400, 10],\n", + " [24, '06:31', '06:43', 391, 403, 12],\n", + " [25, '06:33', '07:53', 393, 473, 80],\n", + " [26, '06:34', '07:09', 394, 429, 35],\n", + " [27, '06:40', '06:56', 400, 416, 16],\n", + " [28, '06:44', '07:17', 404, 437, 33],\n", + " [29, '06:46', '06:58', 406, 418, 12],\n", + " [30, '06:49', '07:43', 409, 463, 54],\n", + " [31, '06:50', '07:05', 410, 425, 15],\n", + " [32, '06:52', '07:36', 412, 456, 44],\n", + " [33, '06:54', '07:27', 414, 447, 33],\n", + " [34, '06:56', '08:23', 416, 503, 87],\n", + " [35, '07:04', '07:44', 424, 464, 40],\n", + " [36, '07:11', '08:36', 431, 516, 85],\n", + " [37, '07:17', '07:35', 437, 455, 18],\n", + " [38, '07:22', '08:06', 442, 486, 44],\n", + " [39, '07:27', '08:15', 447, 495, 48],\n", + " [40, '07:35', '07:45', 455, 465, 10],\n", + " [41, '07:43', '08:08', 463, 488, 25],\n", + " [42, '07:50', '08:37', 470, 517, 47],\n", + " [43, '07:58', '08:45', 478, 525, 47],\n", + " [44, '08:00', '08:35', 480, 515, 35],\n", + " [45, '08:06', '08:51', 486, 531, 45],\n", + " [46, '08:10', '08:45', 490, 525, 35],\n", + " [47, '08:15', '08:30', 495, 510, 15],\n", + " [48, '08:16', '09:00', 496, 540, 44],\n", + " [49, '08:18', '09:16', 498, 556, 58],\n", + " [50, '08:20', '08:36', 500, 516, 16],\n", + " [51, '08:27', '09:07', 507, 547, 40],\n", + " [52, '08:30', '08:45', 510, 525, 15],\n", + " [53, '08:35', '09:15', 515, 555, 40],\n", + " [54, '08:46', '09:30', 526, 570, 44],\n", + " [55, '08:51', '09:17', 531, 557, 26],\n", + " [56, '08:55', '09:15', 535, 555, 20],\n", + " [57, '08:58', '09:38', 538, 578, 40],\n", + " [58, '09:00', '09:35', 540, 575, 35],\n", + " [59, '09:00', '09:16', 540, 556, 16],\n", + " [60, '09:20', '09:36', 560, 576, 16],\n", + " [61, '09:31', '09:43', 571, 583, 12],\n", + " [62, '09:33', '10:15', 573, 615, 42],\n", + " [63, '09:54', '10:05', 594, 605, 11],\n", + " [64, '10:11', '10:38', 611, 638, 27],\n", + " [65, '10:18', '11:00', 618, 660, 42],\n", + " [66, '10:21', '10:47', 621, 647, 26],\n", + " [67, '10:25', '11:04', 625, 664, 39],\n", + " [68, '10:26', '11:08', 626, 668, 42],\n", + " [69, '10:44', '12:11', 644, 731, 87],\n", + " [70, '11:00', '11:16', 660, 676, 16],\n", + " [71, '11:15', '11:54', 675, 714, 39],\n", + " [72, '11:16', '11:28', 676, 688, 12],\n", + " [73, '11:20', '11:30', 680, 690, 10],\n", + " [74, '11:21', '11:47', 681, 707, 26],\n", + " [75, '11:25', '12:04', 685, 724, 39],\n", + " [76, '11:34', '11:45', 694, 705, 11],\n", + " [77, '11:35', '12:14', 695, 734, 39],\n", + " [78, '11:41', '12:23', 701, 743, 42],\n", + " [79, '11:44', '12:35', 704, 755, 51],\n", + " [80, '11:46', '11:58', 706, 718, 12],\n", + " [81, '12:00', '12:10', 720, 730, 10],\n", + " [82, '12:04', '12:15', 724, 735, 11],\n", + " [83, '12:04', '13:04', 724, 784, 60],\n", + " [84, '12:11', '12:38', 731, 758, 27],\n", + " [85, '12:15', '12:54', 735, 774, 39],\n", + " [86, '12:25', '13:10', 745, 790, 45],\n", + " [87, '12:30', '12:40', 750, 760, 10],\n", + " [88, '12:34', '13:58', 754, 838, 84],\n", + " [89, '12:38', '13:25', 758, 805, 47],\n", + " [90, '12:48', '13:35', 768, 815, 47],\n", + " [91, '13:00', '13:16', 780, 796, 16],\n", + " [92, '13:05', '13:44', 785, 824, 39],\n", + " [93, '13:08', '13:55', 788, 835, 47],\n", + " [94, '13:14', '14:38', 794, 878, 84],\n", + " [95, '13:23', '13:49', 803, 829, 26],\n", + " [96, '13:25', '14:04', 805, 844, 39],\n", + " [97, '13:28', '14:54', 808, 894, 86],\n", + " [98, '13:31', '13:43', 811, 823, 12],\n", + " [99, '13:34', '14:58', 814, 898, 84],\n", + " [100, '13:38', '14:25', 818, 865, 47],\n", + " [101, '13:38', '15:04', 818, 904, 86],\n", + " [102, '13:39', '14:33', 819, 873, 54],\n", + " [103, '13:40', '13:50', 820, 830, 10],\n", + " [104, '13:43', '14:10', 823, 850, 27],\n", + " [105, '13:48', '14:35', 828, 875, 47],\n", + " [106, '13:48', '14:35', 828, 875, 47],\n", + " [107, '13:53', '14:40', 833, 880, 47],\n", + " [108, '13:58', '15:24', 838, 924, 86],\n", + " [109, '13:58', '14:25', 838, 865, 27],\n", + " [110, '14:00', '14:16', 840, 856, 16],\n", + " [111, '14:13', '15:00', 853, 900, 47],\n", + " [112, '14:20', '15:31', 860, 931, 71],\n", + " [113, '14:25', '15:02', 865, 902, 37],\n", + " [114, '14:34', '14:45', 874, 885, 11],\n", + " [115, '14:40', '15:51', 880, 951, 71],\n", + " [116, '14:40', '14:56', 880, 896, 16],\n", + " [117, '14:46', '14:58', 886, 898, 12],\n", + " [118, '14:49', '15:43', 889, 943, 54],\n", + " [119, '14:52', '15:21', 892, 921, 29],\n", + " [120, '14:58', '16:24', 898, 984, 86],\n", + " [121, '14:59', '15:53', 899, 953, 54],\n", + " [122, '15:00', '15:10', 900, 910, 10],\n", + " [123, '15:00', '15:35', 900, 935, 35],\n", + " [124, '15:08', '15:45', 908, 945, 37],\n", + " [125, '15:12', '15:36', 912, 936, 24],\n", + " [126, '15:18', '16:05', 918, 965, 47],\n", + " [127, '15:24', '16:05', 924, 965, 41],\n", + " [128, '15:31', '15:43', 931, 943, 12],\n", + " [129, '15:35', '15:54', 935, 954, 19],\n", + " [130, '15:36', '16:21', 936, 981, 45],\n", + " [131, '15:39', '16:33', 939, 993, 54],\n", + " [132, '15:48', '16:35', 948, 995, 47],\n", + " [133, '15:50', '17:01', 950, 1021, 71],\n", + " [134, '16:03', '16:50', 963, 1010, 47],\n", + " [135, '16:18', '17:44', 978, 1064, 86],\n", + " [136, '16:24', '17:05', 984, 1025, 41],\n", + " [137, '16:28', '17:15', 988, 1035, 47],\n", + " [138, '16:34', '17:15', 994, 1035, 41],\n", + " [139, '16:38', '17:25', 998, 1045, 47],\n", + " [140, '16:40', '16:56', 1000, 1016, 16],\n", + " [141, '16:45', '17:04', 1005, 1024, 19],\n", + " [142, '16:52', '17:36', 1012, 1056, 44],\n", + " [143, '16:58', '17:45', 1018, 1065, 47],\n", + " [144, '17:04', '18:30', 1024, 1110, 86],\n", + " [145, '17:04', '17:45', 1024, 1065, 41],\n", + " [146, '17:09', '18:03', 1029, 1083, 54],\n", + " [147, '17:18', '18:44', 1038, 1124, 86],\n", + " [148, '17:28', '18:15', 1048, 1095, 47],\n", + " [149, '17:29', '18:41', 1049, 1121, 72],\n", + " [150, '17:36', '18:21', 1056, 1101, 45],\n", + " [151, '17:38', '18:25', 1058, 1105, 47],\n", + " [152, '17:40', '17:56', 1060, 1076, 16],\n", + " [153, '17:45', '18:04', 1065, 1084, 19],\n", + " [154, '17:46', '17:58', 1066, 1078, 12],\n", + " [155, '17:48', '18:35', 1068, 1115, 47],\n", + " [156, '17:49', '18:43', 1069, 1123, 54],\n", + " [157, '17:55', '18:14', 1075, 1094, 19],\n", + " [158, '17:58', '18:45', 1078, 1125, 47],\n", + " [159, '18:00', '19:11', 1080, 1151, 71],\n", + " [160, '18:04', '18:45', 1084, 1125, 41],\n", + " [161, '18:09', '19:03', 1089, 1143, 54],\n", + " [162, '18:13', '19:00', 1093, 1140, 47],\n", + " [163, '18:13', '18:40', 1093, 1120, 27],\n", + " [164, '18:19', '19:13', 1099, 1153, 54],\n", + " [165, '18:28', '19:25', 1108, 1165, 57],\n", + " [166, '18:48', '19:28', 1128, 1168, 40],\n", + " [167, '19:03', '19:45', 1143, 1185, 42],\n", + " [168, '19:20', '19:36', 1160, 1176, 16],\n", + " [169, '19:21', '19:31', 1161, 1171, 10],\n", + " [170, '19:25', '20:04', 1165, 1204, 39],\n", + " [171, '19:26', '20:08', 1166, 1208, 42],\n", + " [172, '19:30', '19:40', 1170, 1180, 10],\n", + " [173, '19:44', '20:33', 1184, 1233, 49],\n", + " [174, '19:48', '21:09', 1188, 1269, 81],\n", + " [175, '19:53', '21:02', 1193, 1262, 69],\n", + " [176, '20:04', '20:29', 1204, 1229, 25],\n", + " [177, '20:17', '21:03', 1217, 1263, 46],\n", + " [178, '20:20', '20:57', 1220, 1257, 37],\n", + " [179, '20:29', '21:18', 1229, 1278, 49],\n", + " [180, '20:35', '21:54', 1235, 1314, 79],\n", + " [181, '20:40', '20:50', 1240, 1250, 10],\n", + " [182, '20:47', '21:42', 1247, 1302, 55],\n", + " [183, '21:00', '21:10', 1260, 1270, 10],\n", + " [184, '21:07', '21:44', 1267, 1304, 37],\n", + " [185, '21:14', '22:03', 1274, 1323, 49],\n", + " [186, '21:39', '21:55', 1299, 1315, 16],\n", + " [187, '21:40', '22:17', 1300, 1337, 37],\n", + " [188, '21:40', '21:50', 1300, 1310, 10],\n", + " [189, '21:48', '22:03', 1308, 1323, 15],\n", + " [190, '22:17', '23:03', 1337, 1383, 46],\n", + " [191, '22:43', '23:08', 1363, 1388, 25],\n", + " [192, '23:35', '01:05', 1415, 1505, 90],\n", + " [193, '23:46', '00:01', 1426, 1441, 15],\n", + " [194, '23:47', '00:33', 1427, 1473, 46],\n", + " [195, '23:52', '00:24', 1432, 1464, 32],\n", + " [196, '23:58', '00:38', 1438, 1478, 40],\n", + " [197, '00:02', '00:12', 1442, 1452, 10],\n", + " [198, '00:07', '00:39', 1447, 1479, 32],\n", + " [199, '00:25', '01:12', 1465, 1512, 47]\n", + "] # yapf:disable\n", + "\n", + "SAMPLE_SHIFTS_LARGE = [\n", + " [0, '04:18', '05:00', 258, 300, 42],\n", + " [1, '04:27', '05:08', 267, 308, 41],\n", + " [2, '04:29', '05:26', 269, 326, 57],\n", + " [3, '04:29', '04:55', 269, 295, 26],\n", + " [4, '04:30', '04:53', 270, 293, 23],\n", + " [5, '04:30', '04:51', 270, 291, 21],\n", + " [6, '04:31', '04:53', 271, 293, 22],\n", + " [7, '04:33', '05:15', 273, 315, 42],\n", + " [8, '04:34', '04:44', 274, 284, 10],\n", + " [9, '04:34', '05:03', 274, 303, 29],\n", + " [10, '04:35', '04:50', 275, 290, 15],\n", + " [11, '04:36', '04:46', 276, 286, 10],\n", + " [12, '04:37', '05:18', 277, 318, 41],\n", + " [13, '04:41', '05:13', 281, 313, 32],\n", + " [14, '04:42', '05:23', 282, 323, 41],\n", + " [15, '04:43', '04:53', 283, 293, 10],\n", + " [16, '04:44', '05:45', 284, 345, 61],\n", + " [17, '04:45', '05:11', 285, 311, 26],\n", + " [18, '04:46', '05:01', 286, 301, 15],\n", + " [19, '04:46', '04:56', 286, 296, 10],\n", + " [20, '04:47', '05:14', 287, 314, 27],\n", + " [21, '04:48', '05:30', 288, 330, 42],\n", + " [22, '04:49', '05:41', 289, 341, 52],\n", + " [23, '04:49', '05:18', 289, 318, 29],\n", + " [24, '04:50', '05:33', 290, 333, 43],\n", + " [25, '04:52', '05:56', 292, 356, 64],\n", + " [26, '04:52', '05:07', 292, 307, 15],\n", + " [27, '04:53', '05:19', 293, 319, 26],\n", + " [28, '04:53', '05:23', 293, 323, 30],\n", + " [29, '04:55', '05:27', 295, 327, 32],\n", + " [30, '04:57', '05:38', 297, 338, 41],\n", + " [31, '05:00', '06:00', 300, 360, 60],\n", + " [32, '05:00', '05:54', 300, 354, 54],\n", + " [33, '05:01', '05:33', 301, 333, 32],\n", + " [34, '05:01', '05:26', 301, 326, 25],\n", + " [35, '05:02', '05:29', 302, 329, 27],\n", + " [36, '05:02', '05:12', 302, 312, 10],\n", + " [37, '05:03', '05:45', 303, 345, 42],\n", + " [38, '05:03', '05:18', 303, 318, 15],\n", + " [39, '05:03', '06:28', 303, 388, 85],\n", + " [40, '05:03', '05:13', 303, 313, 10],\n", + " [41, '05:04', '06:24', 304, 384, 80],\n", + " [42, '05:07', '05:44', 307, 344, 37],\n", + " [43, '05:08', '05:48', 308, 348, 40],\n", + " [44, '05:10', '06:06', 310, 366, 56],\n", + " [45, '05:11', '05:37', 311, 337, 26],\n", + " [46, '05:11', '05:53', 311, 353, 42],\n", + " [47, '05:13', '06:15', 313, 375, 62],\n", + " [48, '05:13', '05:38', 313, 338, 25],\n", + " [49, '05:16', '05:44', 316, 344, 28],\n", + " [50, '05:17', '05:27', 317, 327, 10],\n", + " [51, '05:18', '06:40', 318, 400, 82],\n", + " [52, '05:18', '06:03', 318, 363, 45],\n", + " [53, '05:18', '06:11', 318, 371, 53],\n", + " [54, '05:18', '06:00', 318, 360, 42],\n", + " [55, '05:19', '06:34', 319, 394, 75],\n", + " [56, '05:20', '06:17', 320, 377, 57],\n", + " [57, '05:22', '05:59', 322, 359, 37],\n", + " [58, '05:24', '05:48', 324, 348, 24],\n", + " [59, '05:25', '05:40', 325, 340, 15],\n", + " [60, '05:26', '06:08', 326, 368, 42],\n", + " [61, '05:27', '06:30', 327, 390, 63],\n", + " [62, '05:27', '05:54', 327, 354, 27],\n", + " [63, '05:28', '05:53', 328, 353, 25],\n", + " [64, '05:29', '05:44', 329, 344, 15],\n", + " [65, '05:30', '05:40', 330, 340, 10],\n", + " [66, '05:30', '05:40', 330, 340, 10],\n", + " [67, '05:30', '05:40', 330, 340, 10],\n", + " [68, '05:32', '06:53', 332, 413, 81],\n", + " [69, '05:33', '07:00', 333, 420, 87],\n", + " [70, '05:33', '06:15', 333, 375, 42],\n", + " [71, '05:33', '05:47', 333, 347, 14],\n", + " [72, '05:37', '06:13', 337, 373, 36],\n", + " [73, '05:37', '06:05', 337, 365, 28],\n", + " [74, '05:38', '06:33', 338, 393, 55],\n", + " [75, '05:38', '06:04', 338, 364, 26],\n", + " [76, '05:38', '06:18', 338, 378, 40],\n", + " [77, '05:39', '05:54', 339, 354, 15],\n", + " [78, '05:40', '05:56', 340, 356, 16],\n", + " [79, '05:40', '06:41', 340, 401, 61],\n", + " [80, '05:40', '05:50', 340, 350, 10],\n", + " [81, '05:41', '06:23', 341, 383, 42],\n", + " [82, '05:41', '06:01', 341, 361, 20],\n", + " [83, '05:43', '06:08', 343, 368, 25],\n", + " [84, '05:44', '07:10', 344, 430, 86],\n", + " [85, '05:44', '05:55', 344, 355, 11],\n", + " [86, '05:45', '06:44', 345, 404, 59],\n", + " [87, '05:47', '06:17', 347, 377, 30],\n", + " [88, '05:48', '07:08', 348, 428, 80],\n", + " [89, '05:48', '06:30', 348, 390, 42],\n", + " [90, '05:50', '06:50', 350, 410, 60],\n", + " [91, '05:50', '06:00', 350, 360, 10],\n", + " [92, '05:50', '06:00', 350, 360, 10],\n", + " [93, '05:50', '06:51', 350, 411, 61],\n", + " [94, '05:52', '06:33', 352, 393, 41],\n", + " [95, '05:52', '06:36', 352, 396, 44],\n", + " [96, '05:52', '06:23', 352, 383, 31],\n", + " [97, '05:54', '06:14', 354, 374, 20],\n", + " [98, '05:54', '07:20', 354, 440, 86],\n", + " [99, '05:55', '06:40', 355, 400, 45],\n", + " [100, '05:55', '06:27', 355, 387, 32],\n", + " [101, '05:56', '06:35', 356, 395, 39],\n", + " [102, '05:56', '06:06', 356, 366, 10],\n", + " [103, '05:57', '06:21', 357, 381, 24],\n", + " [104, '05:58', '07:23', 358, 443, 85],\n", + " [105, '05:58', '06:23', 358, 383, 25],\n", + " [106, '05:58', '06:08', 358, 368, 10],\n", + " [107, '05:58', '06:43', 358, 403, 45],\n", + " [108, '06:00', '06:10', 360, 370, 10],\n", + " [109, '06:00', '06:16', 360, 376, 16],\n", + " [110, '06:00', '07:01', 360, 421, 61],\n", + " [111, '06:01', '07:00', 361, 420, 59],\n", + " [112, '06:01', '06:13', 361, 373, 12],\n", + " [113, '06:01', '06:45', 361, 405, 44],\n", + " [114, '06:03', '06:50', 363, 410, 47],\n", + " [115, '06:04', '06:37', 364, 397, 33],\n", + " [116, '06:04', '07:30', 364, 450, 86],\n", + " [117, '06:05', '06:24', 365, 384, 19],\n", + " [118, '06:06', '06:51', 366, 411, 45],\n", + " [119, '06:07', '06:43', 367, 403, 36],\n", + " [120, '06:08', '07:30', 368, 450, 82],\n", + " [121, '06:10', '06:20', 370, 380, 10],\n", + " [122, '06:10', '07:17', 370, 437, 67],\n", + " [123, '06:11', '06:54', 371, 414, 43],\n", + " [124, '06:11', '06:21', 371, 381, 10],\n", + " [125, '06:13', '06:38', 373, 398, 25],\n", + " [126, '06:13', '06:58', 373, 418, 45],\n", + " [127, '06:13', '06:53', 373, 413, 40],\n", + " [128, '06:14', '07:03', 374, 423, 49],\n", + " [129, '06:14', '06:47', 374, 407, 33],\n", + " [130, '06:14', '07:40', 374, 460, 86],\n", + " [131, '06:15', '07:15', 375, 435, 60],\n", + " [132, '06:16', '06:28', 376, 388, 12],\n", + " [133, '06:16', '06:26', 376, 386, 10],\n", + " [134, '06:17', '06:34', 377, 394, 17],\n", + " [135, '06:18', '07:06', 378, 426, 48],\n", + " [136, '06:18', '07:38', 378, 458, 80],\n", + " [137, '06:18', '07:02', 378, 422, 44],\n", + " [138, '06:19', '06:53', 379, 413, 34],\n", + " [139, '06:20', '07:25', 380, 445, 65],\n", + " [140, '06:20', '06:36', 380, 396, 16],\n", + " [141, '06:20', '06:30', 380, 390, 10],\n", + " [142, '06:20', '06:30', 380, 390, 10],\n", + " [143, '06:21', '06:49', 381, 409, 28],\n", + " [144, '06:22', '07:06', 382, 426, 44],\n", + " [145, '06:24', '07:50', 384, 470, 86],\n", + " [146, '06:24', '06:57', 384, 417, 33],\n", + " [147, '06:26', '07:45', 386, 465, 79],\n", + " [148, '06:26', '07:10', 386, 430, 44],\n", + " [149, '06:27', '06:44', 387, 404, 17],\n", + " [150, '06:28', '06:53', 388, 413, 25],\n", + " [151, '06:28', '07:14', 388, 434, 46],\n", + " [152, '06:29', '07:03', 389, 423, 34],\n", + " [153, '06:30', '06:40', 390, 400, 10],\n", + " [154, '06:30', '07:37', 390, 457, 67],\n", + " [155, '06:31', '06:43', 391, 403, 12],\n", + " [156, '06:33', '07:14', 393, 434, 41],\n", + " [157, '06:33', '07:53', 393, 473, 80],\n", + " [158, '06:34', '08:16', 394, 496, 102],\n", + " [159, '06:34', '07:09', 394, 429, 35],\n", + " [160, '06:34', '07:07', 394, 427, 33],\n", + " [161, '06:36', '07:21', 396, 441, 45],\n", + " [162, '06:37', '07:22', 397, 442, 45],\n", + " [163, '06:37', '06:54', 397, 414, 17],\n", + " [164, '06:38', '07:30', 398, 450, 52],\n", + " [165, '06:38', '07:18', 398, 438, 40],\n", + " [166, '06:39', '07:33', 399, 453, 54],\n", + " [167, '06:40', '07:52', 400, 472, 72],\n", + " [168, '06:40', '06:50', 400, 410, 10],\n", + " [169, '06:40', '07:22', 400, 442, 42],\n", + " [170, '06:40', '06:56', 400, 416, 16],\n", + " [171, '06:41', '08:00', 401, 480, 79],\n", + " [172, '06:42', '07:26', 402, 446, 44],\n", + " [173, '06:42', '07:13', 402, 433, 31],\n", + " [174, '06:43', '07:08', 403, 428, 25],\n", + " [175, '06:43', '07:30', 403, 450, 47],\n", + " [176, '06:43', '07:23', 403, 443, 40],\n", + " [177, '06:44', '07:17', 404, 437, 33],\n", + " [178, '06:44', '08:13', 404, 493, 89],\n", + " [179, '06:46', '07:01', 406, 421, 15],\n", + " [180, '06:46', '06:58', 406, 418, 12],\n", + " [181, '06:47', '07:04', 407, 424, 17],\n", + " [182, '06:48', '08:15', 408, 495, 87],\n", + " [183, '06:48', '07:34', 408, 454, 46],\n", + " [184, '06:48', '07:37', 408, 457, 49],\n", + " [185, '06:49', '07:43', 409, 463, 54],\n", + " [186, '06:50', '08:00', 410, 480, 70],\n", + " [187, '06:50', '07:00', 410, 420, 10],\n", + " [188, '06:50', '07:05', 410, 425, 15],\n", + " [189, '06:51', '07:18', 411, 438, 27],\n", + " [190, '06:52', '07:36', 412, 456, 44],\n", + " [191, '06:53', '07:37', 413, 457, 44],\n", + " [192, '06:54', '08:20', 414, 500, 86],\n", + " [193, '06:54', '07:27', 414, 447, 33],\n", + " [194, '06:54', '07:20', 414, 440, 26],\n", + " [195, '06:56', '08:23', 416, 503, 87],\n", + " [196, '06:57', '07:12', 417, 432, 15],\n", + " [197, '06:57', '07:58', 417, 478, 61],\n", + " [198, '06:57', '07:45', 417, 465, 48],\n", + " [199, '06:57', '07:40', 417, 460, 43],\n", + " [200, '06:58', '07:23', 418, 443, 25],\n", + " [201, '06:59', '07:53', 419, 473, 54],\n", + " [202, '06:59', '08:07', 419, 487, 68],\n", + " [203, '07:00', '07:10', 420, 430, 10],\n", + " [204, '07:00', '07:16', 420, 436, 16],\n", + " [205, '07:01', '08:30', 421, 510, 89],\n", + " [206, '07:01', '07:13', 421, 433, 12],\n", + " [207, '07:01', '07:43', 421, 463, 42],\n", + " [208, '07:03', '08:30', 423, 510, 87],\n", + " [209, '07:04', '07:37', 424, 457, 33],\n", + " [210, '07:04', '07:44', 424, 464, 40],\n", + " [211, '07:05', '07:52', 425, 472, 47],\n", + " [212, '07:05', '08:05', 425, 485, 60],\n", + " [213, '07:05', '07:46', 425, 466, 41],\n", + " [214, '07:06', '07:51', 426, 471, 45],\n", + " [215, '07:07', '08:08', 427, 488, 61],\n", + " [216, '07:07', '07:52', 427, 472, 45],\n", + " [217, '07:07', '08:16', 427, 496, 69],\n", + " [218, '07:07', '07:27', 427, 447, 20],\n", + " [219, '07:09', '07:50', 429, 470, 41],\n", + " [220, '07:09', '08:40', 429, 520, 91],\n", + " [221, '07:09', '08:03', 429, 483, 54],\n", + " [222, '07:10', '07:20', 430, 440, 10],\n", + " [223, '07:11', '08:36', 431, 516, 85],\n", + " [224, '07:12', '08:00', 432, 480, 48],\n", + " [225, '07:12', '07:47', 432, 467, 35],\n", + " [226, '07:13', '07:54', 433, 474, 41],\n", + " [227, '07:13', '07:38', 433, 458, 25],\n", + " [228, '07:14', '07:59', 434, 479, 45],\n", + " [229, '07:16', '08:50', 436, 530, 94],\n", + " [230, '07:16', '07:28', 436, 448, 12],\n", + " [231, '07:17', '07:35', 437, 455, 18],\n", + " [232, '07:17', '07:58', 437, 478, 41],\n", + " [233, '07:18', '08:06', 438, 486, 48],\n", + " [234, '07:18', '08:44', 438, 524, 86],\n", + " [235, '07:19', '08:13', 439, 493, 54],\n", + " [236, '07:20', '08:02', 440, 482, 42],\n", + " [237, '07:20', '08:07', 440, 487, 47],\n", + " [238, '07:20', '07:30', 440, 450, 10],\n", + " [239, '07:20', '07:57', 440, 477, 37],\n", + " [240, '07:20', '07:36', 440, 456, 16],\n", + " [241, '07:21', '07:48', 441, 468, 27],\n", + " [242, '07:22', '08:06', 442, 486, 44],\n", + " [243, '07:22', '08:25', 442, 505, 63],\n", + " [244, '07:24', '08:27', 444, 507, 63],\n", + " [245, '07:24', '08:05', 444, 485, 41],\n", + " [246, '07:26', '08:23', 446, 503, 57],\n", + " [247, '07:26', '08:52', 446, 532, 86],\n", + " [248, '07:27', '08:07', 447, 487, 40],\n", + " [249, '07:27', '07:42', 447, 462, 15],\n", + " [250, '07:27', '08:15', 447, 495, 48],\n", + " [251, '07:28', '07:53', 448, 473, 25],\n", + " [252, '07:28', '08:09', 448, 489, 41],\n", + " [253, '07:28', '07:38', 448, 458, 10],\n", + " [254, '07:30', '08:35', 450, 515, 65],\n", + " [255, '07:31', '07:43', 451, 463, 12],\n", + " [256, '07:32', '08:13', 452, 493, 41],\n", + " [257, '07:34', '09:00', 454, 540, 86],\n", + " [258, '07:34', '08:33', 454, 513, 59],\n", + " [259, '07:34', '09:04', 454, 544, 90],\n", + " [260, '07:35', '08:22', 455, 502, 47],\n", + " [261, '07:35', '07:45', 455, 465, 10],\n", + " [262, '07:35', '08:16', 455, 496, 41],\n", + " [263, '07:36', '08:17', 456, 497, 41],\n", + " [264, '07:36', '08:36', 456, 516, 60],\n", + " [265, '07:37', '07:50', 457, 470, 13],\n", + " [266, '07:40', '07:56', 460, 476, 16],\n", + " [267, '07:40', '08:20', 460, 500, 40],\n", + " [268, '07:40', '08:45', 460, 525, 65],\n", + " [269, '07:41', '08:39', 461, 519, 58],\n", + " [270, '07:41', '07:51', 461, 471, 10],\n", + " [271, '07:42', '08:30', 462, 510, 48],\n", + " [272, '07:42', '08:21', 462, 501, 39],\n", + " [273, '07:43', '08:08', 463, 488, 25],\n", + " [274, '07:43', '08:24', 463, 504, 41],\n", + " [275, '07:44', '09:10', 464, 550, 86],\n", + " [276, '07:44', '08:43', 464, 523, 59],\n", + " [277, '07:46', '08:28', 466, 508, 42],\n", + " [278, '07:46', '07:58', 466, 478, 12],\n", + " [279, '07:47', '08:00', 467, 480, 13],\n", + " [280, '07:48', '09:14', 468, 554, 86],\n", + " [281, '07:49', '08:32', 469, 512, 43],\n", + " [282, '07:50', '08:55', 470, 535, 65],\n", + " [283, '07:50', '08:00', 470, 480, 10],\n", + " [284, '07:50', '08:37', 470, 517, 47],\n", + " [285, '07:50', '08:26', 470, 506, 36],\n", + " [286, '07:51', '08:18', 471, 498, 27],\n", + " [287, '07:52', '08:21', 472, 501, 29],\n", + " [288, '07:53', '08:35', 473, 515, 42],\n", + " [289, '07:54', '09:19', 474, 559, 85],\n", + " [290, '07:55', '08:53', 475, 533, 58],\n", + " [291, '07:56', '08:54', 476, 534, 58],\n", + " [292, '07:57', '08:39', 477, 519, 42],\n", + " [293, '07:57', '08:10', 477, 490, 13],\n", + " [294, '07:58', '08:45', 478, 525, 47],\n", + " [295, '07:58', '08:23', 478, 503, 25],\n", + " [296, '08:00', '08:10', 480, 490, 10],\n", + " [297, '08:00', '09:05', 480, 545, 65],\n", + " [298, '08:00', '08:16', 480, 496, 16],\n", + " [299, '08:00', '08:35', 480, 515, 35],\n", + " [300, '08:01', '08:13', 481, 493, 12],\n", + " [301, '08:01', '08:43', 481, 523, 42],\n", + " [302, '08:03', '09:26', 483, 566, 83],\n", + " [303, '08:04', '09:29', 484, 569, 85],\n", + " [304, '08:05', '08:21', 485, 501, 16],\n", + " [305, '08:05', '08:47', 485, 527, 42],\n", + " [306, '08:06', '08:51', 486, 531, 45],\n", + " [307, '08:06', '09:03', 486, 543, 57],\n", + " [308, '08:07', '08:20', 487, 500, 13],\n", + " [309, '08:08', '08:55', 488, 535, 47],\n", + " [310, '08:08', '08:50', 488, 530, 42],\n", + " [311, '08:10', '08:45', 490, 525, 35],\n", + " [312, '08:10', '09:15', 490, 555, 65],\n", + " [313, '08:10', '08:20', 490, 500, 10],\n", + " [314, '08:11', '09:41', 491, 581, 90],\n", + " [315, '08:12', '08:55', 492, 535, 43],\n", + " [316, '08:13', '08:38', 493, 518, 25],\n", + " [317, '08:14', '09:38', 494, 578, 84],\n", + " [318, '08:15', '08:30', 495, 510, 15],\n", + " [319, '08:16', '08:30', 496, 510, 14],\n", + " [320, '08:16', '08:28', 496, 508, 12],\n", + " [321, '08:16', '09:00', 496, 540, 44],\n", + " [322, '08:17', '09:13', 497, 553, 56],\n", + " [323, '08:18', '09:16', 498, 556, 58],\n", + " [324, '08:18', '09:05', 498, 545, 47],\n", + " [325, '08:20', '08:36', 500, 516, 16],\n", + " [326, '08:20', '08:55', 500, 535, 35],\n", + " [327, '08:20', '09:05', 500, 545, 45],\n", + " [328, '08:20', '08:30', 500, 510, 10],\n", + " [329, '08:20', '09:25', 500, 565, 65],\n", + " [330, '08:21', '08:38', 501, 518, 17],\n", + " [331, '08:21', '08:47', 501, 527, 26],\n", + " [332, '08:22', '08:45', 502, 525, 23],\n", + " [333, '08:23', '09:10', 503, 550, 47],\n", + " [334, '08:24', '09:48', 504, 588, 84],\n", + " [335, '08:26', '08:46', 506, 526, 20],\n", + " [336, '08:27', '09:07', 507, 547, 40],\n", + " [337, '08:28', '08:50', 508, 530, 22],\n", + " [338, '08:28', '09:56', 508, 596, 88],\n", + " [339, '08:28', '09:23', 508, 563, 55],\n", + " [340, '08:29', '09:20', 509, 560, 51],\n", + " [341, '08:30', '09:05', 510, 545, 35],\n", + " [342, '08:30', '08:45', 510, 525, 15],\n", + " [343, '08:30', '08:40', 510, 520, 10],\n", + " [344, '08:30', '09:35', 510, 575, 65],\n", + " [345, '08:31', '08:43', 511, 523, 12],\n", + " [346, '08:31', '09:13', 511, 553, 42],\n", + " [347, '08:34', '09:58', 514, 598, 84],\n", + " [348, '08:35', '08:55', 515, 535, 20],\n", + " [349, '08:35', '09:15', 515, 555, 40],\n", + " [350, '08:35', '08:45', 515, 525, 10],\n", + " [351, '08:36', '08:46', 516, 526, 10],\n", + " [352, '08:36', '09:00', 516, 540, 24],\n", + " [353, '08:38', '09:20', 518, 560, 42],\n", + " [354, '08:38', '09:35', 518, 575, 57],\n", + " [355, '08:38', '09:14', 518, 554, 36],\n", + " [356, '08:39', '09:33', 519, 573, 54],\n", + " [357, '08:40', '09:45', 520, 585, 65],\n", + " [358, '08:40', '08:50', 520, 530, 10],\n", + " [359, '08:40', '08:56', 520, 536, 16],\n", + " [360, '08:42', '09:25', 522, 565, 43],\n", + " [361, '08:43', '09:08', 523, 548, 25],\n", + " [362, '08:44', '09:35', 524, 575, 51],\n", + " [363, '08:45', '09:00', 525, 540, 15],\n", + " [364, '08:45', '09:05', 525, 545, 20],\n", + " [365, '08:46', '09:24', 526, 564, 38],\n", + " [366, '08:46', '08:58', 526, 538, 12],\n", + " [367, '08:46', '09:30', 526, 570, 44],\n", + " [368, '08:48', '10:11', 528, 611, 83],\n", + " [369, '08:48', '10:13', 528, 613, 85],\n", + " [370, '08:49', '09:43', 529, 583, 54],\n", + " [371, '08:50', '09:30', 530, 570, 40],\n", + " [372, '08:50', '10:00', 530, 600, 70],\n", + " [373, '08:50', '09:00', 530, 540, 10],\n", + " [374, '08:51', '09:17', 531, 557, 26],\n", + " [375, '08:53', '09:20', 533, 560, 27],\n", + " [376, '08:53', '09:35', 533, 575, 42],\n", + " [377, '08:55', '09:34', 535, 574, 39],\n", + " [378, '08:55', '09:15', 535, 555, 20],\n", + " [379, '08:58', '09:38', 538, 578, 40],\n", + " [380, '08:58', '10:26', 538, 626, 88],\n", + " [381, '08:59', '09:53', 539, 593, 54],\n", + " [382, '08:59', '09:50', 539, 590, 51],\n", + " [383, '09:00', '09:35', 540, 575, 35],\n", + " [384, '09:00', '09:16', 540, 556, 16],\n", + " [385, '09:00', '09:10', 540, 550, 10],\n", + " [386, '09:00', '09:16', 540, 556, 16],\n", + " [387, '09:01', '09:13', 541, 553, 12],\n", + " [388, '09:03', '09:45', 543, 585, 42],\n", + " [389, '09:03', '10:28', 543, 628, 85],\n", + " [390, '09:05', '09:44', 545, 584, 39],\n", + " [391, '09:05', '09:25', 545, 565, 20],\n", + " [392, '09:08', '09:53', 548, 593, 45],\n", + " [393, '09:08', '10:04', 548, 604, 56],\n", + " [394, '09:09', '10:03', 549, 603, 54],\n", + " [395, '09:10', '10:15', 550, 615, 65],\n", + " [396, '09:10', '09:20', 550, 560, 10],\n", + " [397, '09:11', '09:38', 551, 578, 27],\n", + " [398, '09:13', '10:00', 553, 600, 47],\n", + " [399, '09:14', '09:39', 554, 579, 25],\n", + " [400, '09:14', '10:05', 554, 605, 51],\n", + " [401, '09:15', '09:54', 555, 594, 39],\n", + " [402, '09:16', '09:28', 556, 568, 12],\n", + " [403, '09:18', '10:43', 558, 643, 85],\n", + " [404, '09:18', '10:41', 558, 641, 83],\n", + " [405, '09:18', '09:58', 558, 598, 40],\n", + " [406, '09:19', '10:13', 559, 613, 54],\n", + " [407, '09:20', '09:30', 560, 570, 10],\n", + " [408, '09:20', '09:36', 560, 576, 16],\n", + " [409, '09:21', '09:47', 561, 587, 26],\n", + " [410, '09:23', '10:30', 563, 630, 67],\n", + " [411, '09:23', '10:05', 563, 605, 42],\n", + " [412, '09:23', '09:49', 563, 589, 26],\n", + " [413, '09:24', '09:35', 564, 575, 11],\n", + " [414, '09:25', '09:35', 565, 575, 10],\n", + " [415, '09:25', '10:04', 565, 604, 39],\n", + " [416, '09:28', '10:08', 568, 608, 40],\n", + " [417, '09:29', '09:45', 569, 585, 16],\n", + " [418, '09:29', '10:20', 569, 620, 51],\n", + " [419, '09:29', '10:56', 569, 656, 87],\n", + " [420, '09:29', '10:23', 569, 623, 54],\n", + " [421, '09:30', '09:40', 570, 580, 10],\n", + " [422, '09:31', '09:43', 571, 583, 12],\n", + " [423, '09:33', '10:58', 573, 658, 85],\n", + " [424, '09:33', '10:15', 573, 615, 42],\n", + " [425, '09:34', '09:45', 574, 585, 11],\n", + " [426, '09:35', '10:14', 575, 614, 39],\n", + " [427, '09:38', '10:45', 578, 645, 67],\n", + " [428, '09:39', '10:33', 579, 633, 54],\n", + " [429, '09:40', '09:56', 580, 596, 16],\n", + " [430, '09:40', '09:50', 580, 590, 10],\n", + " [431, '09:41', '10:08', 581, 608, 27],\n", + " [432, '09:41', '10:23', 581, 623, 42],\n", + " [433, '09:44', '10:35', 584, 635, 51],\n", + " [434, '09:44', '11:11', 584, 671, 87],\n", + " [435, '09:44', '09:55', 584, 595, 11],\n", + " [436, '09:45', '10:24', 585, 624, 39],\n", + " [437, '09:46', '09:58', 586, 598, 12],\n", + " [438, '09:48', '10:30', 588, 630, 42],\n", + " [439, '09:48', '11:13', 588, 673, 85],\n", + " [440, '09:48', '10:04', 588, 604, 16],\n", + " [441, '09:49', '10:43', 589, 643, 54],\n", + " [442, '09:50', '10:00', 590, 600, 10],\n", + " [443, '09:51', '10:17', 591, 617, 26],\n", + " [444, '09:53', '10:49', 593, 649, 56],\n", + " [445, '09:53', '11:00', 593, 660, 67],\n", + " [446, '09:54', '10:05', 594, 605, 11],\n", + " [447, '09:55', '10:34', 595, 634, 39],\n", + " [448, '09:56', '10:38', 596, 638, 42],\n", + " [449, '09:57', '10:20', 597, 620, 23],\n", + " [450, '09:59', '11:26', 599, 686, 87],\n", + " [451, '09:59', '10:50', 599, 650, 51],\n", + " [452, '09:59', '10:53', 599, 653, 54],\n", + " [453, '10:00', '10:16', 600, 616, 16],\n", + " [454, '10:00', '10:10', 600, 610, 10],\n", + " [455, '10:01', '10:13', 601, 613, 12],\n", + " [456, '10:03', '11:28', 603, 688, 85],\n", + " [457, '10:03', '10:45', 603, 645, 42],\n", + " [458, '10:04', '10:15', 604, 615, 11],\n", + " [459, '10:05', '10:44', 605, 644, 39],\n", + " [460, '10:08', '11:15', 608, 675, 67],\n", + " [461, '10:09', '11:03', 609, 663, 54],\n", + " [462, '10:10', '10:20', 610, 620, 10],\n", + " [463, '10:11', '10:38', 611, 638, 27],\n", + " [464, '10:11', '10:53', 611, 653, 42],\n", + " [465, '10:14', '11:05', 614, 665, 51],\n", + " [466, '10:14', '11:41', 614, 701, 87],\n", + " [467, '10:14', '10:25', 614, 625, 11],\n", + " [468, '10:15', '10:54', 615, 654, 39],\n", + " [469, '10:16', '10:28', 616, 628, 12],\n", + " [470, '10:18', '11:43', 618, 703, 85],\n", + " [471, '10:18', '11:00', 618, 660, 42],\n", + " [472, '10:19', '11:13', 619, 673, 54],\n", + " [473, '10:20', '10:30', 620, 630, 10],\n", + " [474, '10:20', '10:36', 620, 636, 16],\n", + " [475, '10:21', '10:47', 621, 647, 26],\n", + " [476, '10:23', '11:30', 623, 690, 67],\n", + " [477, '10:23', '10:45', 623, 645, 22],\n", + " [478, '10:24', '10:35', 624, 635, 11],\n", + " [479, '10:25', '11:04', 625, 664, 39],\n", + " [480, '10:26', '11:08', 626, 668, 42],\n", + " [481, '10:29', '11:20', 629, 680, 51],\n", + " [482, '10:29', '11:23', 629, 683, 54],\n", + " [483, '10:29', '11:56', 629, 716, 87],\n", + " [484, '10:30', '10:40', 630, 640, 10],\n", + " [485, '10:31', '10:43', 631, 643, 12],\n", + " [486, '10:33', '11:15', 633, 675, 42],\n", + " [487, '10:33', '11:58', 633, 718, 85],\n", + " [488, '10:34', '10:45', 634, 645, 11],\n", + " [489, '10:35', '11:14', 635, 674, 39],\n", + " [490, '10:38', '11:45', 638, 705, 67],\n", + " [491, '10:39', '11:33', 639, 693, 54],\n", + " [492, '10:40', '10:50', 640, 650, 10],\n", + " [493, '10:40', '10:56', 640, 656, 16],\n", + " [494, '10:41', '11:23', 641, 683, 42],\n", + " [495, '10:41', '11:08', 641, 668, 27],\n", + " [496, '10:44', '12:11', 644, 731, 87],\n", + " [497, '10:44', '11:35', 644, 695, 51],\n", + " [498, '10:44', '10:55', 644, 655, 11],\n", + " [499, '10:45', '11:24', 645, 684, 39],\n", + " [500, '10:46', '10:58', 646, 658, 12],\n", + " [501, '10:48', '12:13', 648, 733, 85],\n", + " [502, '10:48', '11:30', 648, 690, 42],\n", + " [503, '10:49', '11:43', 649, 703, 54],\n", + " [504, '10:50', '11:00', 650, 660, 10],\n", + " [505, '10:51', '11:17', 651, 677, 26],\n", + " [506, '10:53', '12:00', 653, 720, 67],\n", + " [507, '10:53', '11:20', 653, 680, 27],\n", + " [508, '10:54', '11:05', 654, 665, 11],\n", + " [509, '10:55', '11:34', 655, 694, 39],\n", + " [510, '10:56', '11:38', 656, 698, 42],\n", + " [511, '10:59', '11:14', 659, 674, 15],\n", + " [512, '10:59', '12:26', 659, 746, 87],\n", + " [513, '10:59', '11:53', 659, 713, 54],\n", + " [514, '10:59', '11:50', 659, 710, 51],\n", + " [515, '11:00', '11:16', 660, 676, 16],\n", + " [516, '11:00', '11:10', 660, 670, 10],\n", + " [517, '11:01', '11:13', 661, 673, 12],\n", + " [518, '11:03', '11:45', 663, 705, 42],\n", + " [519, '11:03', '12:28', 663, 748, 85],\n", + " [520, '11:04', '11:15', 664, 675, 11],\n", + " [521, '11:05', '11:44', 665, 704, 39],\n", + " [522, '11:08', '12:15', 668, 735, 67],\n", + " [523, '11:09', '12:03', 669, 723, 54],\n", + " [524, '11:10', '11:20', 670, 680, 10],\n", + " [525, '11:11', '11:38', 671, 698, 27],\n", + " [526, '11:11', '11:53', 671, 713, 42],\n", + " [527, '11:14', '11:25', 674, 685, 11],\n", + " [528, '11:14', '12:05', 674, 725, 51],\n", + " [529, '11:14', '12:38', 674, 758, 84],\n", + " [530, '11:14', '12:41', 674, 761, 87],\n", + " [531, '11:15', '11:54', 675, 714, 39],\n", + " [532, '11:16', '11:28', 676, 688, 12],\n", + " [533, '11:18', '12:00', 678, 720, 42],\n", + " [534, '11:19', '12:13', 679, 733, 54],\n", + " [535, '11:20', '11:30', 680, 690, 10],\n", + " [536, '11:20', '11:36', 680, 696, 16],\n", + " [537, '11:21', '11:47', 681, 707, 26],\n", + " [538, '11:23', '12:30', 683, 750, 67],\n", + " [539, '11:23', '11:49', 683, 709, 26],\n", + " [540, '11:24', '12:48', 684, 768, 84],\n", + " [541, '11:24', '11:35', 684, 695, 11],\n", + " [542, '11:25', '12:04', 685, 724, 39],\n", + " [543, '11:26', '12:08', 686, 728, 42],\n", + " [544, '11:29', '11:44', 689, 704, 15],\n", + " [545, '11:29', '12:23', 689, 743, 54],\n", + " [546, '11:29', '12:20', 689, 740, 51],\n", + " [547, '11:29', '12:54', 689, 774, 85],\n", + " [548, '11:30', '11:40', 690, 700, 10],\n", + " [549, '11:31', '11:43', 691, 703, 12],\n", + " [550, '11:33', '12:15', 693, 735, 42],\n", + " [551, '11:34', '12:58', 694, 778, 84],\n", + " [552, '11:34', '11:45', 694, 705, 11],\n", + " [553, '11:35', '12:14', 695, 734, 39],\n", + " [554, '11:38', '12:45', 698, 765, 67],\n", + " [555, '11:39', '12:33', 699, 753, 54],\n", + " [556, '11:40', '11:56', 700, 716, 16],\n", + " [557, '11:40', '11:50', 700, 710, 10],\n", + " [558, '11:41', '12:08', 701, 728, 27],\n", + " [559, '11:41', '12:23', 701, 743, 42],\n", + " [560, '11:44', '11:55', 704, 715, 11],\n", + " [561, '11:44', '13:14', 704, 794, 90],\n", + " [562, '11:44', '13:08', 704, 788, 84],\n", + " [563, '11:44', '12:35', 704, 755, 51],\n", + " [564, '11:45', '12:24', 705, 744, 39],\n", + " [565, '11:46', '11:58', 706, 718, 12],\n", + " [566, '11:48', '12:30', 708, 750, 42],\n", + " [567, '11:49', '12:43', 709, 763, 54],\n", + " [568, '11:50', '12:00', 710, 720, 10],\n", + " [569, '11:51', '12:17', 711, 737, 26],\n", + " [570, '11:53', '12:49', 713, 769, 56],\n", + " [571, '11:53', '13:00', 713, 780, 67],\n", + " [572, '11:54', '13:18', 714, 798, 84],\n", + " [573, '11:54', '12:05', 714, 725, 11],\n", + " [574, '11:55', '12:40', 715, 760, 45],\n", + " [575, '11:55', '12:34', 715, 754, 39],\n", + " [576, '11:56', '12:35', 716, 755, 39],\n", + " [577, '11:57', '12:20', 717, 740, 23],\n", + " [578, '11:58', '12:29', 718, 749, 31],\n", + " [579, '11:59', '12:50', 719, 770, 51],\n", + " [580, '11:59', '12:53', 719, 773, 54],\n", + " [581, '11:59', '13:24', 719, 804, 85],\n", + " [582, '11:59', '12:14', 719, 734, 15],\n", + " [583, '12:00', '12:16', 720, 736, 16],\n", + " [584, '12:00', '12:10', 720, 730, 10],\n", + " [585, '12:01', '12:45', 721, 765, 44],\n", + " [586, '12:01', '12:13', 721, 733, 12],\n", + " [587, '12:03', '12:50', 723, 770, 47],\n", + " [588, '12:04', '12:15', 724, 735, 11],\n", + " [589, '12:04', '13:04', 724, 784, 60],\n", + " [590, '12:04', '13:28', 724, 808, 84],\n", + " [591, '12:05', '12:44', 725, 764, 39],\n", + " [592, '12:08', '13:11', 728, 791, 63],\n", + " [593, '12:08', '12:39', 728, 759, 31],\n", + " [594, '12:09', '13:03', 729, 783, 54],\n", + " [595, '12:10', '12:20', 730, 740, 10],\n", + " [596, '12:11', '12:55', 731, 775, 44],\n", + " [597, '12:11', '12:38', 731, 758, 27],\n", + " [598, '12:14', '13:05', 734, 785, 51],\n", + " [599, '12:14', '12:25', 734, 745, 11],\n", + " [600, '12:14', '13:44', 734, 824, 90],\n", + " [601, '12:14', '13:38', 734, 818, 84],\n", + " [602, '12:15', '12:54', 735, 774, 39],\n", + " [603, '12:16', '12:28', 736, 748, 12],\n", + " [604, '12:18', '13:00', 738, 780, 42],\n", + " [605, '12:19', '13:13', 739, 793, 54],\n", + " [606, '12:20', '12:30', 740, 750, 10],\n", + " [607, '12:20', '13:31', 740, 811, 71],\n", + " [608, '12:20', '12:30', 740, 750, 10],\n", + " [609, '12:20', '12:36', 740, 756, 16],\n", + " [610, '12:21', '12:47', 741, 767, 26],\n", + " [611, '12:23', '12:45', 743, 765, 22],\n", + " [612, '12:24', '12:35', 744, 755, 11],\n", + " [613, '12:24', '13:48', 744, 828, 84],\n", + " [614, '12:25', '13:10', 745, 790, 45],\n", + " [615, '12:25', '13:04', 745, 784, 39],\n", + " [616, '12:26', '13:05', 746, 785, 39],\n", + " [617, '12:28', '13:54', 748, 834, 86],\n", + " [618, '12:28', '12:38', 748, 758, 10],\n", + " [619, '12:28', '13:15', 748, 795, 47],\n", + " [620, '12:29', '13:23', 749, 803, 54],\n", + " [621, '12:30', '13:41', 750, 821, 71],\n", + " [622, '12:30', '12:40', 750, 760, 10],\n", + " [623, '12:31', '13:15', 751, 795, 44],\n", + " [624, '12:31', '12:43', 751, 763, 12],\n", + " [625, '12:33', '12:48', 753, 768, 15],\n", + " [626, '12:33', '13:20', 753, 800, 47],\n", + " [627, '12:34', '13:58', 754, 838, 84],\n", + " [628, '12:34', '13:34', 754, 814, 60],\n", + " [629, '12:34', '12:45', 754, 765, 11],\n", + " [630, '12:35', '13:14', 755, 794, 39],\n", + " [631, '12:38', '13:25', 758, 805, 47],\n", + " [632, '12:38', '13:25', 758, 805, 47],\n", + " [633, '12:38', '14:04', 758, 844, 86],\n", + " [634, '12:39', '13:33', 759, 813, 54],\n", + " [635, '12:40', '13:51', 760, 831, 71],\n", + " [636, '12:40', '12:50', 760, 770, 10],\n", + " [637, '12:40', '12:56', 760, 776, 16],\n", + " [638, '12:41', '13:08', 761, 788, 27],\n", + " [639, '12:43', '13:30', 763, 810, 47],\n", + " [640, '12:44', '12:55', 764, 775, 11],\n", + " [641, '12:44', '14:08', 764, 848, 84],\n", + " [642, '12:45', '13:24', 765, 804, 39],\n", + " [643, '12:46', '12:58', 766, 778, 12],\n", + " [644, '12:46', '13:21', 766, 801, 35],\n", + " [645, '12:48', '14:14', 768, 854, 86],\n", + " [646, '12:48', '13:35', 768, 815, 47],\n", + " [647, '12:48', '12:58', 768, 778, 10],\n", + " [648, '12:48', '13:35', 768, 815, 47],\n", + " [649, '12:49', '13:43', 769, 823, 54],\n", + " [650, '12:50', '14:01', 770, 841, 71],\n", + " [651, '12:50', '13:00', 770, 780, 10],\n", + " [652, '12:50', '13:00', 770, 780, 10],\n", + " [653, '12:51', '13:17', 771, 797, 26],\n", + " [654, '12:53', '13:20', 773, 800, 27],\n", + " [655, '12:53', '13:24', 773, 804, 31],\n", + " [656, '12:53', '13:40', 773, 820, 47],\n", + " [657, '12:54', '14:18', 774, 858, 84],\n", + " [658, '12:54', '13:05', 774, 785, 11],\n", + " [659, '12:55', '13:34', 775, 814, 39],\n", + " [660, '12:58', '14:24', 778, 864, 86],\n", + " [661, '12:58', '13:25', 778, 805, 27],\n", + " [662, '12:58', '13:45', 778, 825, 47],\n", + " [663, '12:58', '13:45', 778, 825, 47],\n", + " [664, '12:59', '13:53', 779, 833, 54],\n", + " [665, '13:00', '13:10', 780, 790, 10],\n", + " [666, '13:00', '13:16', 780, 796, 16],\n", + " [667, '13:00', '14:11', 780, 851, 71],\n", + " [668, '13:01', '13:13', 781, 793, 12],\n", + " [669, '13:03', '13:34', 783, 814, 31],\n", + " [670, '13:03', '13:50', 783, 830, 47],\n", + " [671, '13:04', '13:15', 784, 795, 11],\n", + " [672, '13:04', '14:28', 784, 868, 84],\n", + " [673, '13:05', '13:44', 785, 824, 39],\n", + " [674, '13:08', '13:55', 788, 835, 47],\n", + " [675, '13:08', '14:34', 788, 874, 86],\n", + " [676, '13:08', '13:55', 788, 835, 47],\n", + " [677, '13:09', '14:03', 789, 843, 54],\n", + " [678, '13:10', '13:20', 790, 800, 10],\n", + " [679, '13:10', '14:21', 790, 861, 71],\n", + " [680, '13:13', '14:00', 793, 840, 47],\n", + " [681, '13:13', '13:40', 793, 820, 27],\n", + " [682, '13:14', '14:38', 794, 878, 84],\n", + " [683, '13:14', '13:25', 794, 805, 11],\n", + " [684, '13:15', '13:54', 795, 834, 39],\n", + " [685, '13:16', '13:28', 796, 808, 12],\n", + " [686, '13:18', '14:05', 798, 845, 47],\n", + " [687, '13:18', '14:44', 798, 884, 86],\n", + " [688, '13:18', '14:05', 798, 845, 47],\n", + " [689, '13:19', '14:13', 799, 853, 54],\n", + " [690, '13:20', '13:36', 800, 816, 16],\n", + " [691, '13:20', '14:31', 800, 871, 71],\n", + " [692, '13:20', '13:30', 800, 810, 10],\n", + " [693, '13:21', '13:47', 801, 827, 26],\n", + " [694, '13:23', '14:10', 803, 850, 47],\n", + " [695, '13:23', '13:49', 803, 829, 26],\n", + " [696, '13:24', '14:48', 804, 888, 84],\n", + " [697, '13:24', '13:35', 804, 815, 11],\n", + " [698, '13:25', '14:04', 805, 844, 39],\n", + " [699, '13:28', '14:15', 808, 855, 47],\n", + " [700, '13:28', '14:54', 808, 894, 86],\n", + " [701, '13:28', '13:55', 808, 835, 27],\n", + " [702, '13:28', '14:15', 808, 855, 47],\n", + " [703, '13:29', '14:23', 809, 863, 54],\n", + " [704, '13:30', '13:40', 810, 820, 10],\n", + " [705, '13:30', '14:41', 810, 881, 71],\n", + " [706, '13:31', '13:43', 811, 823, 12],\n", + " [707, '13:33', '14:20', 813, 860, 47],\n", + " [708, '13:34', '14:58', 814, 898, 84],\n", + " [709, '13:34', '13:45', 814, 825, 11],\n", + " [710, '13:35', '14:14', 815, 854, 39],\n", + " [711, '13:38', '14:25', 818, 865, 47],\n", + " [712, '13:38', '14:25', 818, 865, 47],\n", + " [713, '13:38', '15:04', 818, 904, 86],\n", + " [714, '13:39', '14:33', 819, 873, 54],\n", + " [715, '13:40', '13:50', 820, 830, 10],\n", + " [716, '13:40', '13:56', 820, 836, 16],\n", + " [717, '13:40', '14:51', 820, 891, 71],\n", + " [718, '13:43', '14:30', 823, 870, 47],\n", + " [719, '13:43', '14:10', 823, 850, 27],\n", + " [720, '13:44', '15:09', 824, 909, 85],\n", + " [721, '13:44', '13:55', 824, 835, 11],\n", + " [722, '13:45', '14:24', 825, 864, 39],\n", + " [723, '13:46', '13:58', 826, 838, 12],\n", + " [724, '13:48', '14:35', 828, 875, 47],\n", + " [725, '13:48', '15:14', 828, 914, 86],\n", + " [726, '13:48', '14:35', 828, 875, 47],\n", + " [727, '13:49', '14:43', 829, 883, 54],\n", + " [728, '13:50', '14:00', 830, 840, 10],\n", + " [729, '13:50', '15:01', 830, 901, 71],\n", + " [730, '13:51', '14:17', 831, 857, 26],\n", + " [731, '13:53', '14:40', 833, 880, 47],\n", + " [732, '13:53', '14:49', 833, 889, 56],\n", + " [733, '13:54', '14:05', 834, 845, 11],\n", + " [734, '13:54', '15:19', 834, 919, 85],\n", + " [735, '13:55', '14:34', 835, 874, 39],\n", + " [736, '13:57', '14:20', 837, 860, 23],\n", + " [737, '13:58', '15:24', 838, 924, 86],\n", + " [738, '13:58', '14:45', 838, 885, 47],\n", + " [739, '13:58', '14:45', 838, 885, 47],\n", + " [740, '13:58', '14:25', 838, 865, 27],\n", + " [741, '13:59', '14:53', 839, 893, 54],\n", + " [742, '14:00', '14:16', 840, 856, 16],\n", + " [743, '14:00', '14:10', 840, 850, 10],\n", + " [744, '14:00', '15:11', 840, 911, 71],\n", + " [745, '14:01', '14:13', 841, 853, 12],\n", + " [746, '14:03', '14:50', 843, 890, 47],\n", + " [747, '14:04', '14:15', 844, 855, 11],\n", + " [748, '14:04', '15:29', 844, 929, 85],\n", + " [749, '14:05', '14:44', 845, 884, 39],\n", + " [750, '14:08', '14:55', 848, 895, 47],\n", + " [751, '14:08', '14:55', 848, 895, 47],\n", + " [752, '14:08', '15:34', 848, 934, 86],\n", + " [753, '14:09', '15:03', 849, 903, 54],\n", + " [754, '14:10', '15:21', 850, 921, 71],\n", + " [755, '14:10', '14:20', 850, 860, 10],\n", + " [756, '14:13', '15:00', 853, 900, 47],\n", + " [757, '14:13', '14:40', 853, 880, 27],\n", + " [758, '14:14', '15:40', 854, 940, 86],\n", + " [759, '14:14', '14:25', 854, 865, 11],\n", + " [760, '14:15', '14:54', 855, 894, 39],\n", + " [761, '14:16', '14:28', 856, 868, 12],\n", + " [762, '14:18', '15:05', 858, 905, 47],\n", + " [763, '14:18', '15:44', 858, 944, 86],\n", + " [764, '14:18', '15:05', 858, 905, 47],\n", + " [765, '14:19', '15:13', 859, 913, 54],\n", + " [766, '14:20', '15:31', 860, 931, 71],\n", + " [767, '14:20', '14:30', 860, 870, 10],\n", + " [768, '14:20', '14:36', 860, 876, 16],\n", + " [769, '14:21', '14:47', 861, 887, 26],\n", + " [770, '14:23', '15:10', 863, 910, 47],\n", + " [771, '14:23', '14:45', 863, 885, 22],\n", + " [772, '14:24', '15:50', 864, 950, 86],\n", + " [773, '14:24', '14:35', 864, 875, 11],\n", + " [774, '14:25', '15:02', 865, 902, 37],\n", + " [775, '14:26', '14:52', 866, 892, 26],\n", + " [776, '14:28', '15:15', 868, 915, 47],\n", + " [777, '14:28', '14:55', 868, 895, 27],\n", + " [778, '14:28', '15:54', 868, 954, 86],\n", + " [779, '14:28', '15:15', 868, 915, 47],\n", + " [780, '14:29', '15:23', 869, 923, 54],\n", + " [781, '14:30', '15:41', 870, 941, 71],\n", + " [782, '14:30', '14:40', 870, 880, 10],\n", + " [783, '14:31', '14:43', 871, 883, 12],\n", + " [784, '14:33', '15:20', 873, 920, 47],\n", + " [785, '14:34', '16:00', 874, 960, 86],\n", + " [786, '14:34', '14:45', 874, 885, 11],\n", + " [787, '14:35', '15:11', 875, 911, 36],\n", + " [788, '14:38', '15:25', 878, 925, 47],\n", + " [789, '14:38', '15:25', 878, 925, 47],\n", + " [790, '14:38', '16:04', 878, 964, 86],\n", + " [791, '14:39', '15:33', 879, 933, 54],\n", + " [792, '14:40', '14:50', 880, 890, 10],\n", + " [793, '14:40', '15:51', 880, 951, 71],\n", + " [794, '14:40', '14:56', 880, 896, 16],\n", + " [795, '14:43', '15:30', 883, 930, 47],\n", + " [796, '14:43', '15:10', 883, 910, 27],\n", + " [797, '14:44', '15:00', 884, 900, 16],\n", + " [798, '14:44', '16:10', 884, 970, 86],\n", + " [799, '14:45', '15:19', 885, 919, 34],\n", + " [800, '14:46', '14:58', 886, 898, 12],\n", + " [801, '14:48', '15:35', 888, 935, 47],\n", + " [802, '14:48', '15:35', 888, 935, 47],\n", + " [803, '14:48', '17:04', 888, 1024, 136],\n", + " [804, '14:49', '15:43', 889, 943, 54],\n", + " [805, '14:50', '16:01', 890, 961, 71],\n", + " [806, '14:50', '15:00', 890, 900, 10],\n", + " [807, '14:51', '15:17', 891, 917, 26],\n", + " [808, '14:52', '15:27', 892, 927, 35],\n", + " [809, '14:52', '15:21', 892, 921, 29],\n", + " [810, '14:53', '15:40', 893, 940, 47],\n", + " [811, '14:54', '15:08', 894, 908, 14],\n", + " [812, '14:54', '16:20', 894, 980, 86],\n", + " [813, '14:58', '16:24', 898, 984, 86],\n", + " [814, '14:58', '15:45', 898, 945, 47],\n", + " [815, '14:58', '15:25', 898, 925, 27],\n", + " [816, '14:58', '15:45', 898, 945, 47],\n", + " [817, '14:59', '15:53', 899, 953, 54],\n", + " [818, '15:00', '15:10', 900, 910, 10],\n", + " [819, '15:00', '15:35', 900, 935, 35],\n", + " [820, '15:00', '16:11', 900, 971, 71],\n", + " [821, '15:00', '15:16', 900, 916, 16],\n", + " [822, '15:01', '15:13', 901, 913, 12],\n", + " [823, '15:02', '15:16', 902, 916, 14],\n", + " [824, '15:03', '15:50', 903, 950, 47],\n", + " [825, '15:04', '16:30', 904, 990, 86],\n", + " [826, '15:08', '16:34', 908, 994, 86],\n", + " [827, '15:08', '15:55', 908, 955, 47],\n", + " [828, '15:08', '15:55', 908, 955, 47],\n", + " [829, '15:08', '15:45', 908, 945, 37],\n", + " [830, '15:09', '16:14', 909, 974, 65],\n", + " [831, '15:09', '16:03', 909, 963, 54],\n", + " [832, '15:10', '16:21', 910, 981, 71],\n", + " [833, '15:10', '15:20', 910, 920, 10],\n", + " [834, '15:11', '15:24', 911, 924, 13],\n", + " [835, '15:12', '15:36', 912, 936, 24],\n", + " [836, '15:13', '16:00', 913, 960, 47],\n", + " [837, '15:13', '15:40', 913, 940, 27],\n", + " [838, '15:14', '16:40', 914, 1000, 86],\n", + " [839, '15:16', '15:28', 916, 928, 12],\n", + " [840, '15:16', '15:55', 916, 955, 39],\n", + " [841, '15:18', '16:05', 918, 965, 47],\n", + " [842, '15:18', '16:44', 918, 1004, 86],\n", + " [843, '15:18', '16:05', 918, 965, 47],\n", + " [844, '15:19', '16:13', 919, 973, 54],\n", + " [845, '15:19', '15:34', 919, 934, 15],\n", + " [846, '15:20', '15:30', 920, 930, 10],\n", + " [847, '15:20', '16:31', 920, 991, 71],\n", + " [848, '15:20', '15:36', 920, 936, 16],\n", + " [849, '15:21', '15:47', 921, 947, 26],\n", + " [850, '15:21', '16:06', 921, 966, 45],\n", + " [851, '15:23', '16:10', 923, 970, 47],\n", + " [852, '15:24', '16:50', 924, 1010, 86],\n", + " [853, '15:24', '16:05', 924, 965, 41],\n", + " [854, '15:27', '15:51', 927, 951, 24],\n", + " [855, '15:27', '15:44', 927, 944, 17],\n", + " [856, '15:28', '16:15', 928, 975, 47],\n", + " [857, '15:28', '16:54', 928, 1014, 86],\n", + " [858, '15:28', '16:15', 928, 975, 47],\n", + " [859, '15:28', '15:55', 928, 955, 27],\n", + " [860, '15:29', '16:23', 929, 983, 54],\n", + " [861, '15:30', '16:41', 930, 1001, 71],\n", + " [862, '15:30', '15:40', 930, 940, 10],\n", + " [863, '15:31', '15:43', 931, 943, 12],\n", + " [864, '15:33', '16:20', 933, 980, 47],\n", + " [865, '15:34', '17:00', 934, 1020, 86],\n", + " [866, '15:34', '16:15', 934, 975, 41],\n", + " [867, '15:35', '15:54', 935, 954, 19],\n", + " [868, '15:36', '16:21', 936, 981, 45],\n", + " [869, '15:38', '16:25', 938, 985, 47],\n", + " [870, '15:38', '16:25', 938, 985, 47],\n", + " [871, '15:38', '16:39', 938, 999, 61],\n", + " [872, '15:39', '16:33', 939, 993, 54],\n", + " [873, '15:40', '15:50', 940, 950, 10],\n", + " [874, '15:40', '16:51', 940, 1011, 71],\n", + " [875, '15:40', '15:56', 940, 956, 16],\n", + " [876, '15:43', '16:10', 943, 970, 27],\n", + " [877, '15:43', '16:30', 943, 990, 47],\n", + " [878, '15:44', '17:10', 944, 1030, 86],\n", + " [879, '15:44', '16:25', 944, 985, 41],\n", + " [880, '15:45', '16:04', 945, 964, 19],\n", + " [881, '15:46', '15:58', 946, 958, 12],\n", + " [882, '15:48', '16:35', 948, 995, 47],\n", + " [883, '15:48', '16:35', 948, 995, 47],\n", + " [884, '15:48', '17:14', 948, 1034, 86],\n", + " [885, '15:49', '16:43', 949, 1003, 54],\n", + " [886, '15:50', '16:00', 950, 960, 10],\n", + " [887, '15:50', '17:01', 950, 1021, 71],\n", + " [888, '15:51', '16:18', 951, 978, 27],\n", + " [889, '15:52', '16:36', 952, 996, 44],\n", + " [890, '15:53', '16:40', 953, 1000, 47],\n", + " [891, '15:54', '17:20', 954, 1040, 86],\n", + " [892, '15:54', '16:35', 954, 995, 41],\n", + " [893, '15:55', '16:14', 955, 974, 19],\n", + " [894, '15:58', '16:25', 958, 985, 27],\n", + " [895, '15:58', '16:45', 958, 1005, 47],\n", + " [896, '15:58', '16:45', 958, 1005, 47],\n", + " [897, '15:58', '17:24', 958, 1044, 86],\n", + " [898, '15:59', '17:11', 959, 1031, 72],\n", + " [899, '15:59', '16:53', 959, 1013, 54],\n", + " [900, '16:00', '16:10', 960, 970, 10],\n", + " [901, '16:00', '16:16', 960, 976, 16],\n", + " [902, '16:01', '16:13', 961, 973, 12],\n", + " [903, '16:03', '16:50', 963, 1010, 47],\n", + " [904, '16:04', '17:30', 964, 1050, 86],\n", + " [905, '16:04', '16:45', 964, 1005, 41],\n", + " [906, '16:05', '16:24', 965, 984, 19],\n", + " [907, '16:06', '16:51', 966, 1011, 45],\n", + " [908, '16:08', '16:55', 968, 1015, 47],\n", + " [909, '16:08', '17:34', 968, 1054, 86],\n", + " [910, '16:08', '16:55', 968, 1015, 47],\n", + " [911, '16:09', '17:03', 969, 1023, 54],\n", + " [912, '16:09', '17:21', 969, 1041, 72],\n", + " [913, '16:10', '16:20', 970, 980, 10],\n", + " [914, '16:13', '16:40', 973, 1000, 27],\n", + " [915, '16:13', '17:00', 973, 1020, 47],\n", + " [916, '16:14', '16:55', 974, 1015, 41],\n", + " [917, '16:14', '17:40', 974, 1060, 86],\n", + " [918, '16:15', '16:34', 975, 994, 19],\n", + " [919, '16:16', '16:28', 976, 988, 12],\n", + " [920, '16:18', '17:05', 978, 1025, 47],\n", + " [921, '16:18', '17:05', 978, 1025, 47],\n", + " [922, '16:18', '17:44', 978, 1064, 86],\n", + " [923, '16:19', '17:31', 979, 1051, 72],\n", + " [924, '16:19', '17:13', 979, 1033, 54],\n", + " [925, '16:20', '16:30', 980, 990, 10],\n", + " [926, '16:20', '16:36', 980, 996, 16],\n", + " [927, '16:21', '16:48', 981, 1008, 27],\n", + " [928, '16:22', '17:06', 982, 1026, 44],\n", + " [929, '16:23', '17:10', 983, 1030, 47],\n", + " [930, '16:24', '17:05', 984, 1025, 41],\n", + " [931, '16:24', '17:50', 984, 1070, 86],\n", + " [932, '16:25', '16:44', 985, 1004, 19],\n", + " [933, '16:28', '17:15', 988, 1035, 47],\n", + " [934, '16:28', '17:15', 988, 1035, 47],\n", + " [935, '16:28', '16:55', 988, 1015, 27],\n", + " [936, '16:28', '17:54', 988, 1074, 86],\n", + " [937, '16:29', '17:23', 989, 1043, 54],\n", + " [938, '16:29', '17:41', 989, 1061, 72],\n", + " [939, '16:30', '16:40', 990, 1000, 10],\n", + " [940, '16:31', '16:43', 991, 1003, 12],\n", + " [941, '16:33', '17:20', 993, 1040, 47],\n", + " [942, '16:34', '17:15', 994, 1035, 41],\n", + " [943, '16:34', '18:00', 994, 1080, 86],\n", + " [944, '16:35', '16:54', 995, 1014, 19],\n", + " [945, '16:36', '17:21', 996, 1041, 45],\n", + " [946, '16:38', '17:25', 998, 1045, 47],\n", + " [947, '16:38', '17:25', 998, 1045, 47],\n", + " [948, '16:38', '18:04', 998, 1084, 86],\n", + " [949, '16:39', '17:33', 999, 1053, 54],\n", + " [950, '16:39', '17:51', 999, 1071, 72],\n", + " [951, '16:40', '16:56', 1000, 1016, 16],\n", + " [952, '16:40', '16:50', 1000, 1010, 10],\n", + " [953, '16:43', '17:10', 1003, 1030, 27],\n", + " [954, '16:43', '17:30', 1003, 1050, 47],\n", + " [955, '16:44', '17:25', 1004, 1045, 41],\n", + " [956, '16:44', '18:10', 1004, 1090, 86],\n", + " [957, '16:45', '17:04', 1005, 1024, 19],\n", + " [958, '16:46', '16:58', 1006, 1018, 12],\n", + " [959, '16:48', '18:14', 1008, 1094, 86],\n", + " [960, '16:48', '17:35', 1008, 1055, 47],\n", + " [961, '16:48', '17:35', 1008, 1055, 47],\n", + " [962, '16:49', '18:01', 1009, 1081, 72],\n", + " [963, '16:49', '17:43', 1009, 1063, 54],\n", + " [964, '16:50', '17:00', 1010, 1020, 10],\n", + " [965, '16:51', '17:18', 1011, 1038, 27],\n", + " [966, '16:52', '17:36', 1012, 1056, 44],\n", + " [967, '16:53', '17:40', 1013, 1060, 47],\n", + " [968, '16:54', '18:20', 1014, 1100, 86],\n", + " [969, '16:54', '17:35', 1014, 1055, 41],\n", + " [970, '16:55', '17:14', 1015, 1034, 19],\n", + " [971, '16:58', '17:25', 1018, 1045, 27],\n", + " [972, '16:58', '17:45', 1018, 1065, 47],\n", + " [973, '16:58', '17:45', 1018, 1065, 47],\n", + " [974, '16:58', '18:24', 1018, 1104, 86],\n", + " [975, '16:59', '18:11', 1019, 1091, 72],\n", + " [976, '16:59', '17:53', 1019, 1073, 54],\n", + " [977, '17:00', '17:16', 1020, 1036, 16],\n", + " [978, '17:00', '17:10', 1020, 1030, 10],\n", + " [979, '17:01', '17:13', 1021, 1033, 12],\n", + " [980, '17:03', '17:50', 1023, 1070, 47],\n", + " [981, '17:04', '18:30', 1024, 1110, 86],\n", + " [982, '17:04', '17:45', 1024, 1065, 41],\n", + " [983, '17:05', '17:24', 1025, 1044, 19],\n", + " [984, '17:06', '17:51', 1026, 1071, 45],\n", + " [985, '17:08', '17:55', 1028, 1075, 47],\n", + " [986, '17:08', '17:55', 1028, 1075, 47],\n", + " [987, '17:08', '18:34', 1028, 1114, 86],\n", + " [988, '17:09', '18:03', 1029, 1083, 54],\n", + " [989, '17:09', '18:21', 1029, 1101, 72],\n", + " [990, '17:10', '17:20', 1030, 1040, 10],\n", + " [991, '17:13', '17:40', 1033, 1060, 27],\n", + " [992, '17:13', '18:00', 1033, 1080, 47],\n", + " [993, '17:14', '17:55', 1034, 1075, 41],\n", + " [994, '17:14', '18:40', 1034, 1120, 86],\n", + " [995, '17:15', '17:34', 1035, 1054, 19],\n", + " [996, '17:16', '17:28', 1036, 1048, 12],\n", + " [997, '17:18', '18:05', 1038, 1085, 47],\n", + " [998, '17:18', '18:05', 1038, 1085, 47],\n", + " [999, '17:18', '18:44', 1038, 1124, 86],\n", + " [1000, '17:19', '18:31', 1039, 1111, 72],\n", + " [1001, '17:19', '18:13', 1039, 1093, 54],\n", + " [1002, '17:20', '17:36', 1040, 1056, 16],\n", + " [1003, '17:20', '17:30', 1040, 1050, 10],\n", + " [1004, '17:21', '17:47', 1041, 1067, 26],\n", + " [1005, '17:22', '18:06', 1042, 1086, 44],\n", + " [1006, '17:23', '18:10', 1043, 1090, 47],\n", + " [1007, '17:24', '18:50', 1044, 1130, 86],\n", + " [1008, '17:24', '18:05', 1044, 1085, 41],\n", + " [1009, '17:25', '17:44', 1045, 1064, 19],\n", + " [1010, '17:28', '17:55', 1048, 1075, 27],\n", + " [1011, '17:28', '18:15', 1048, 1095, 47],\n", + " [1012, '17:28', '18:15', 1048, 1095, 47],\n", + " [1013, '17:28', '18:54', 1048, 1134, 86],\n", + " [1014, '17:29', '18:41', 1049, 1121, 72],\n", + " [1015, '17:29', '18:23', 1049, 1103, 54],\n", + " [1016, '17:30', '17:40', 1050, 1060, 10],\n", + " [1017, '17:31', '17:43', 1051, 1063, 12],\n", + " [1018, '17:33', '18:20', 1053, 1100, 47],\n", + " [1019, '17:34', '18:15', 1054, 1095, 41],\n", + " [1020, '17:34', '19:00', 1054, 1140, 86],\n", + " [1021, '17:35', '17:54', 1055, 1074, 19],\n", + " [1022, '17:36', '18:21', 1056, 1101, 45],\n", + " [1023, '17:38', '18:25', 1058, 1105, 47],\n", + " [1024, '17:38', '19:04', 1058, 1144, 86],\n", + " [1025, '17:38', '18:25', 1058, 1105, 47],\n", + " [1026, '17:39', '18:51', 1059, 1131, 72],\n", + " [1027, '17:39', '18:33', 1059, 1113, 54],\n", + " [1028, '17:40', '17:56', 1060, 1076, 16],\n", + " [1029, '17:40', '17:50', 1060, 1070, 10],\n", + " [1030, '17:43', '18:10', 1063, 1090, 27],\n", + " [1031, '17:43', '18:30', 1063, 1110, 47],\n", + " [1032, '17:44', '18:25', 1064, 1105, 41],\n", + " [1033, '17:44', '19:14', 1064, 1154, 90],\n", + " [1034, '17:45', '18:04', 1065, 1084, 19],\n", + " [1035, '17:46', '17:58', 1066, 1078, 12],\n", + " [1036, '17:48', '18:35', 1068, 1115, 47],\n", + " [1037, '17:48', '18:35', 1068, 1115, 47],\n", + " [1038, '17:48', '19:14', 1068, 1154, 86],\n", + " [1039, '17:49', '19:01', 1069, 1141, 72],\n", + " [1040, '17:49', '18:43', 1069, 1123, 54],\n", + " [1041, '17:50', '18:00', 1070, 1080, 10],\n", + " [1042, '17:51', '18:17', 1071, 1097, 26],\n", + " [1043, '17:52', '18:36', 1072, 1116, 44],\n", + " [1044, '17:53', '18:40', 1073, 1120, 47],\n", + " [1045, '17:54', '18:35', 1074, 1115, 41],\n", + " [1046, '17:54', '18:57', 1074, 1137, 63],\n", + " [1047, '17:55', '18:14', 1075, 1094, 19],\n", + " [1048, '17:58', '18:45', 1078, 1125, 47],\n", + " [1049, '17:58', '18:45', 1078, 1125, 47],\n", + " [1050, '17:58', '18:25', 1078, 1105, 27],\n", + " [1051, '17:58', '19:26', 1078, 1166, 88],\n", + " [1052, '17:59', '18:53', 1079, 1133, 54],\n", + " [1053, '18:00', '19:11', 1080, 1151, 71],\n", + " [1054, '18:00', '18:10', 1080, 1090, 10],\n", + " [1055, '18:00', '18:16', 1080, 1096, 16],\n", + " [1056, '18:01', '18:13', 1081, 1093, 12],\n", + " [1057, '18:03', '18:50', 1083, 1130, 47],\n", + " [1058, '18:04', '18:45', 1084, 1125, 41],\n", + " [1059, '18:04', '19:29', 1084, 1169, 85],\n", + " [1060, '18:05', '18:24', 1085, 1104, 19],\n", + " [1061, '18:06', '18:51', 1086, 1131, 45],\n", + " [1062, '18:08', '18:55', 1088, 1135, 47],\n", + " [1063, '18:08', '19:06', 1088, 1146, 58],\n", + " [1064, '18:08', '18:55', 1088, 1135, 47],\n", + " [1065, '18:09', '19:03', 1089, 1143, 54],\n", + " [1066, '18:10', '18:20', 1090, 1100, 10],\n", + " [1067, '18:10', '19:21', 1090, 1161, 71],\n", + " [1068, '18:13', '19:00', 1093, 1140, 47],\n", + " [1069, '18:13', '18:40', 1093, 1120, 27],\n", + " [1070, '18:14', '19:43', 1094, 1183, 89],\n", + " [1071, '18:14', '18:55', 1094, 1135, 41],\n", + " [1072, '18:15', '18:34', 1095, 1114, 19],\n", + " [1073, '18:16', '18:28', 1096, 1108, 12],\n", + " [1074, '18:17', '18:27', 1097, 1107, 10],\n", + " [1075, '18:18', '19:41', 1098, 1181, 83],\n", + " [1076, '18:18', '18:58', 1098, 1138, 40],\n", + " [1077, '18:18', '19:05', 1098, 1145, 47],\n", + " [1078, '18:19', '19:13', 1099, 1153, 54],\n", + " [1079, '18:20', '19:31', 1100, 1171, 71],\n", + " [1080, '18:20', '18:36', 1100, 1116, 16],\n", + " [1081, '18:20', '18:30', 1100, 1110, 10],\n", + " [1082, '18:22', '19:05', 1102, 1145, 43],\n", + " [1083, '18:23', '19:05', 1103, 1145, 42],\n", + " [1084, '18:24', '19:27', 1104, 1167, 63],\n", + " [1085, '18:24', '19:05', 1104, 1145, 41],\n", + " [1086, '18:25', '18:44', 1105, 1124, 19],\n", + " [1087, '18:28', '19:25', 1108, 1165, 57],\n", + " [1088, '18:28', '18:55', 1108, 1135, 27],\n", + " [1089, '18:28', '19:08', 1108, 1148, 40],\n", + " [1090, '18:28', '19:15', 1108, 1155, 47],\n", + " [1091, '18:29', '19:23', 1109, 1163, 54],\n", + " [1092, '18:30', '19:05', 1110, 1145, 35],\n", + " [1093, '18:30', '18:40', 1110, 1120, 10],\n", + " [1094, '18:31', '18:43', 1111, 1123, 12],\n", + " [1095, '18:33', '19:15', 1113, 1155, 42],\n", + " [1096, '18:34', '19:58', 1114, 1198, 84],\n", + " [1097, '18:34', '19:14', 1114, 1154, 40],\n", + " [1098, '18:35', '18:55', 1115, 1135, 20],\n", + " [1099, '18:36', '19:20', 1116, 1160, 44],\n", + " [1100, '18:38', '19:25', 1118, 1165, 47],\n", + " [1101, '18:38', '19:23', 1118, 1163, 45],\n", + " [1102, '18:38', '19:56', 1118, 1196, 78],\n", + " [1103, '18:39', '19:33', 1119, 1173, 54],\n", + " [1104, '18:40', '18:50', 1120, 1130, 10],\n", + " [1105, '18:40', '19:45', 1120, 1185, 65],\n", + " [1106, '18:40', '18:56', 1120, 1136, 16],\n", + " [1107, '18:43', '19:10', 1123, 1150, 27],\n", + " [1108, '18:43', '19:30', 1123, 1170, 47],\n", + " [1109, '18:44', '19:24', 1124, 1164, 40],\n", + " [1110, '18:45', '19:05', 1125, 1145, 20],\n", + " [1111, '18:46', '18:58', 1126, 1138, 12],\n", + " [1112, '18:48', '19:35', 1128, 1175, 47],\n", + " [1113, '18:48', '20:12', 1128, 1212, 84],\n", + " [1114, '18:48', '20:11', 1128, 1211, 83],\n", + " [1115, '18:48', '19:28', 1128, 1168, 40],\n", + " [1116, '18:49', '19:43', 1129, 1183, 54],\n", + " [1117, '18:50', '19:00', 1130, 1140, 10],\n", + " [1118, '18:51', '19:01', 1131, 1141, 10],\n", + " [1119, '18:53', '19:35', 1133, 1175, 42],\n", + " [1120, '18:53', '19:15', 1133, 1155, 22],\n", + " [1121, '18:53', '20:00', 1133, 1200, 67],\n", + " [1122, '18:55', '19:15', 1135, 1155, 20],\n", + " [1123, '18:55', '19:34', 1135, 1174, 39],\n", + " [1124, '18:58', '19:38', 1138, 1178, 40],\n", + " [1125, '18:59', '19:53', 1139, 1193, 54],\n", + " [1126, '18:59', '19:50', 1139, 1190, 51],\n", + " [1127, '18:59', '19:53', 1139, 1193, 54],\n", + " [1128, '19:00', '19:16', 1140, 1156, 16],\n", + " [1129, '19:00', '19:10', 1140, 1150, 10],\n", + " [1130, '19:00', '19:16', 1140, 1156, 16],\n", + " [1131, '19:01', '19:13', 1141, 1153, 12],\n", + " [1132, '19:03', '20:26', 1143, 1226, 83],\n", + " [1133, '19:03', '19:45', 1143, 1185, 42],\n", + " [1134, '19:05', '19:44', 1145, 1184, 39],\n", + " [1135, '19:05', '19:25', 1145, 1165, 20],\n", + " [1136, '19:08', '20:15', 1148, 1215, 67],\n", + " [1137, '19:08', '19:35', 1148, 1175, 27],\n", + " [1138, '19:09', '19:49', 1149, 1189, 40],\n", + " [1139, '19:09', '20:03', 1149, 1203, 54],\n", + " [1140, '19:10', '19:20', 1150, 1160, 10],\n", + " [1141, '19:10', '19:20', 1150, 1160, 10],\n", + " [1142, '19:11', '19:53', 1151, 1193, 42],\n", + " [1143, '19:14', '20:26', 1154, 1226, 72],\n", + " [1144, '19:14', '19:35', 1154, 1175, 21],\n", + " [1145, '19:14', '19:24', 1154, 1164, 10],\n", + " [1146, '19:14', '20:05', 1154, 1205, 51],\n", + " [1147, '19:15', '19:30', 1155, 1170, 15],\n", + " [1148, '19:15', '19:54', 1155, 1194, 39],\n", + " [1149, '19:18', '20:39', 1158, 1239, 81],\n", + " [1150, '19:18', '20:00', 1158, 1200, 42],\n", + " [1151, '19:19', '20:14', 1159, 1214, 55],\n", + " [1152, '19:20', '19:30', 1160, 1170, 10],\n", + " [1153, '19:20', '19:36', 1160, 1176, 16],\n", + " [1154, '19:21', '19:31', 1161, 1171, 10],\n", + " [1155, '19:23', '20:30', 1163, 1230, 67],\n", + " [1156, '19:23', '19:35', 1163, 1175, 12],\n", + " [1157, '19:24', '19:45', 1164, 1185, 21],\n", + " [1158, '19:24', '19:45', 1164, 1185, 21],\n", + " [1159, '19:25', '20:04', 1165, 1204, 39],\n", + " [1160, '19:26', '20:08', 1166, 1208, 42],\n", + " [1161, '19:29', '20:02', 1169, 1202, 33],\n", + " [1162, '19:29', '20:18', 1169, 1218, 49],\n", + " [1163, '19:29', '20:41', 1169, 1241, 72],\n", + " [1164, '19:30', '19:40', 1170, 1180, 10],\n", + " [1165, '19:33', '20:54', 1173, 1254, 81],\n", + " [1166, '19:33', '20:17', 1173, 1217, 44],\n", + " [1167, '19:34', '19:55', 1174, 1195, 21],\n", + " [1168, '19:35', '20:14', 1175, 1214, 39],\n", + " [1169, '19:38', '20:05', 1178, 1205, 27],\n", + " [1170, '19:38', '20:45', 1178, 1245, 67],\n", + " [1171, '19:39', '20:12', 1179, 1212, 33],\n", + " [1172, '19:40', '19:50', 1180, 1190, 10],\n", + " [1173, '19:40', '19:56', 1180, 1196, 16],\n", + " [1174, '19:41', '20:27', 1181, 1227, 46],\n", + " [1175, '19:43', '19:55', 1183, 1195, 12],\n", + " [1176, '19:44', '20:05', 1184, 1205, 21],\n", + " [1177, '19:44', '20:33', 1184, 1233, 49],\n", + " [1178, '19:44', '21:00', 1184, 1260, 76],\n", + " [1179, '19:45', '20:24', 1185, 1224, 39],\n", + " [1180, '19:48', '20:37', 1188, 1237, 49],\n", + " [1181, '19:48', '21:09', 1188, 1269, 81],\n", + " [1182, '19:50', '20:00', 1190, 1200, 10],\n", + " [1183, '19:52', '20:29', 1192, 1229, 37],\n", + " [1184, '19:53', '20:08', 1193, 1208, 15],\n", + " [1185, '19:53', '21:02', 1193, 1262, 69],\n", + " [1186, '19:53', '20:20', 1193, 1220, 27],\n", + " [1187, '19:54', '20:19', 1194, 1219, 25],\n", + " [1188, '19:55', '20:34', 1195, 1234, 39],\n", + " [1189, '19:56', '20:34', 1196, 1234, 38],\n", + " [1190, '19:59', '20:48', 1199, 1248, 49],\n", + " [1191, '19:59', '21:20', 1199, 1280, 81],\n", + " [1192, '20:00', '20:16', 1200, 1216, 16],\n", + " [1193, '20:00', '20:10', 1200, 1210, 10],\n", + " [1194, '20:03', '20:42', 1203, 1242, 39],\n", + " [1195, '20:03', '21:24', 1203, 1284, 81],\n", + " [1196, '20:04', '20:29', 1204, 1229, 25],\n", + " [1197, '20:05', '20:48', 1205, 1248, 43],\n", + " [1198, '20:07', '20:44', 1207, 1244, 37],\n", + " [1199, '20:08', '20:40', 1208, 1240, 32],\n", + " [1200, '20:08', '20:35', 1208, 1235, 27],\n", + " [1201, '20:10', '20:20', 1210, 1220, 10],\n", + " [1202, '20:10', '20:22', 1210, 1222, 12],\n", + " [1203, '20:11', '20:47', 1211, 1247, 36],\n", + " [1204, '20:14', '21:04', 1214, 1264, 50],\n", + " [1205, '20:14', '21:03', 1214, 1263, 49],\n", + " [1206, '20:17', '21:03', 1217, 1263, 46],\n", + " [1207, '20:18', '21:39', 1218, 1299, 81],\n", + " [1208, '20:20', '20:30', 1220, 1230, 10],\n", + " [1209, '20:20', '20:57', 1220, 1257, 37],\n", + " [1210, '20:20', '20:36', 1220, 1236, 16],\n", + " [1211, '20:22', '20:59', 1222, 1259, 37],\n", + " [1212, '20:22', '20:42', 1222, 1242, 20],\n", + " [1213, '20:24', '20:49', 1224, 1249, 25],\n", + " [1214, '20:27', '21:22', 1227, 1282, 55],\n", + " [1215, '20:29', '21:18', 1229, 1278, 49],\n", + " [1216, '20:30', '21:07', 1230, 1267, 37],\n", + " [1217, '20:30', '20:40', 1230, 1240, 10],\n", + " [1218, '20:30', '20:40', 1230, 1240, 10],\n", + " [1219, '20:30', '21:40', 1230, 1300, 70],\n", + " [1220, '20:32', '21:18', 1232, 1278, 46],\n", + " [1221, '20:35', '21:54', 1235, 1314, 79],\n", + " [1222, '20:37', '21:14', 1237, 1274, 37],\n", + " [1223, '20:38', '21:08', 1238, 1268, 30],\n", + " [1224, '20:40', '20:50', 1240, 1250, 10],\n", + " [1225, '20:40', '21:17', 1240, 1277, 37],\n", + " [1226, '20:40', '20:56', 1240, 1256, 16],\n", + " [1227, '20:44', '21:33', 1244, 1293, 49],\n", + " [1228, '20:47', '21:33', 1247, 1293, 46],\n", + " [1229, '20:47', '21:42', 1247, 1302, 55],\n", + " [1230, '20:50', '21:00', 1250, 1260, 10],\n", + " [1231, '20:50', '22:00', 1250, 1320, 70],\n", + " [1232, '20:50', '22:09', 1250, 1329, 79],\n", + " [1233, '20:50', '21:27', 1250, 1287, 37],\n", + " [1234, '20:52', '21:29', 1252, 1289, 37],\n", + " [1235, '20:53', '21:20', 1253, 1280, 27],\n", + " [1236, '20:56', '21:11', 1256, 1271, 15],\n", + " [1237, '20:59', '21:48', 1259, 1308, 49],\n", + " [1238, '21:00', '21:10', 1260, 1270, 10],\n", + " [1239, '21:00', '21:37', 1260, 1297, 37],\n", + " [1240, '21:02', '21:48', 1262, 1308, 46],\n", + " [1241, '21:05', '22:24', 1265, 1344, 79],\n", + " [1242, '21:07', '21:44', 1267, 1304, 37],\n", + " [1243, '21:07', '22:02', 1267, 1322, 55],\n", + " [1244, '21:08', '21:38', 1268, 1298, 30],\n", + " [1245, '21:10', '22:25', 1270, 1345, 75],\n", + " [1246, '21:10', '21:20', 1270, 1280, 10],\n", + " [1247, '21:10', '21:47', 1270, 1307, 37],\n", + " [1248, '21:14', '22:03', 1274, 1323, 49],\n", + " [1249, '21:17', '22:03', 1277, 1323, 46],\n", + " [1250, '21:20', '22:18', 1280, 1338, 58],\n", + " [1251, '21:20', '21:57', 1280, 1317, 37],\n", + " [1252, '21:20', '21:30', 1280, 1290, 10],\n", + " [1253, '21:22', '21:59', 1282, 1319, 37],\n", + " [1254, '21:24', '21:49', 1284, 1309, 25],\n", + " [1255, '21:27', '22:21', 1287, 1341, 54],\n", + " [1256, '21:30', '22:07', 1290, 1327, 37],\n", + " [1257, '21:30', '22:20', 1290, 1340, 50],\n", + " [1258, '21:30', '21:40', 1290, 1300, 10],\n", + " [1259, '21:32', '22:18', 1292, 1338, 46],\n", + " [1260, '21:32', '22:01', 1292, 1321, 29],\n", + " [1261, '21:35', '22:54', 1295, 1374, 79],\n", + " [1262, '21:37', '22:14', 1297, 1334, 37],\n", + " [1263, '21:39', '21:55', 1299, 1315, 16],\n", + " [1264, '21:40', '22:17', 1300, 1337, 37],\n", + " [1265, '21:40', '21:50', 1300, 1310, 10],\n", + " [1266, '21:41', '22:08', 1301, 1328, 27],\n", + " [1267, '21:47', '22:16', 1307, 1336, 29],\n", + " [1268, '21:47', '22:51', 1307, 1371, 64],\n", + " [1269, '21:47', '22:33', 1307, 1353, 46],\n", + " [1270, '21:48', '22:03', 1308, 1323, 15],\n", + " [1271, '21:50', '22:55', 1310, 1375, 65],\n", + " [1272, '21:50', '22:27', 1310, 1347, 37],\n", + " [1273, '21:50', '22:00', 1310, 1320, 10],\n", + " [1274, '21:52', '22:29', 1312, 1349, 37],\n", + " [1275, '21:53', '22:19', 1313, 1339, 26],\n", + " [1276, '22:00', '22:38', 1320, 1358, 38],\n", + " [1277, '22:00', '22:10', 1320, 1330, 10],\n", + " [1278, '22:02', '22:12', 1322, 1332, 10],\n", + " [1279, '22:02', '22:48', 1322, 1368, 46],\n", + " [1280, '22:04', '22:31', 1324, 1351, 27],\n", + " [1281, '22:05', '23:24', 1325, 1404, 79],\n", + " [1282, '22:07', '22:44', 1327, 1364, 37],\n", + " [1283, '22:07', '22:39', 1327, 1359, 32],\n", + " [1284, '22:09', '22:25', 1329, 1345, 16],\n", + " [1285, '22:10', '23:25', 1330, 1405, 75],\n", + " [1286, '22:13', '22:38', 1333, 1358, 25],\n", + " [1287, '22:13', '22:53', 1333, 1373, 40],\n", + " [1288, '22:17', '22:27', 1337, 1347, 10],\n", + " [1289, '22:17', '23:03', 1337, 1383, 46],\n", + " [1290, '22:19', '22:46', 1339, 1366, 27],\n", + " [1291, '22:22', '22:59', 1342, 1379, 37],\n", + " [1292, '22:24', '22:48', 1344, 1368, 24],\n", + " [1293, '22:27', '22:52', 1347, 1372, 25],\n", + " [1294, '22:27', '23:21', 1347, 1401, 54],\n", + " [1295, '22:28', '23:08', 1348, 1388, 40],\n", + " [1296, '22:30', '23:17', 1350, 1397, 47],\n", + " [1297, '22:32', '22:42', 1352, 1362, 10],\n", + " [1298, '22:32', '23:11', 1352, 1391, 39],\n", + " [1299, '22:34', '23:01', 1354, 1381, 27],\n", + " [1300, '22:35', '23:54', 1355, 1434, 79],\n", + " [1301, '22:37', '23:14', 1357, 1394, 37],\n", + " [1302, '22:43', '23:23', 1363, 1403, 40],\n", + " [1303, '22:43', '23:08', 1363, 1388, 25],\n", + " [1304, '22:47', '23:33', 1367, 1413, 46],\n", + " [1305, '22:47', '22:57', 1367, 1377, 10],\n", + " [1306, '22:49', '23:16', 1369, 1396, 27],\n", + " [1307, '22:52', '23:29', 1372, 1409, 37],\n", + " [1308, '22:53', '23:15', 1373, 1395, 22],\n", + " [1309, '22:55', '23:55', 1375, 1435, 60],\n", + " [1310, '22:57', '23:51', 1377, 1431, 54],\n", + " [1311, '22:58', '23:38', 1378, 1418, 40],\n", + " [1312, '23:02', '23:41', 1382, 1421, 39],\n", + " [1313, '23:02', '23:12', 1382, 1392, 10],\n", + " [1314, '23:04', '23:31', 1384, 1411, 27],\n", + " [1315, '23:05', '00:24', 1385, 1464, 79],\n", + " [1316, '23:07', '23:44', 1387, 1424, 37],\n", + " [1317, '23:13', '23:53', 1393, 1433, 40],\n", + " [1318, '23:13', '23:38', 1393, 1418, 25],\n", + " [1319, '23:17', '00:03', 1397, 1443, 46],\n", + " [1320, '23:17', '23:27', 1397, 1407, 10],\n", + " [1321, '23:19', '23:46', 1399, 1426, 27],\n", + " [1322, '23:22', '23:59', 1402, 1439, 37],\n", + " [1323, '23:25', '00:25', 1405, 1465, 60],\n", + " [1324, '23:27', '00:21', 1407, 1461, 54],\n", + " [1325, '23:28', '00:08', 1408, 1448, 40],\n", + " [1326, '23:32', '23:42', 1412, 1422, 10],\n", + " [1327, '23:34', '00:01', 1414, 1441, 27],\n", + " [1328, '23:35', '01:05', 1415, 1505, 90],\n", + " [1329, '23:37', '00:09', 1417, 1449, 32],\n", + " [1330, '23:43', '00:23', 1423, 1463, 40],\n", + " [1331, '23:43', '00:08', 1423, 1448, 25],\n", + " [1332, '23:46', '00:01', 1426, 1441, 15],\n", + " [1333, '23:47', '23:57', 1427, 1437, 10],\n", + " [1334, '23:47', '00:33', 1427, 1473, 46],\n", + " [1335, '23:52', '00:24', 1432, 1464, 32],\n", + " [1336, '23:55', '00:49', 1435, 1489, 54],\n", + " [1337, '23:57', '00:57', 1437, 1497, 60],\n", + " [1338, '23:58', '00:38', 1438, 1478, 40],\n", + " [1339, '00:02', '00:12', 1442, 1452, 10],\n", + " [1340, '00:07', '00:39', 1447, 1479, 32],\n", + " [1341, '00:13', '00:38', 1453, 1478, 25],\n", + " [1342, '00:13', '00:51', 1453, 1491, 38],\n", + " [1343, '00:15', '01:14', 1455, 1514, 59],\n", + " [1344, '00:17', '01:23', 1457, 1523, 66],\n", + " [1345, '00:23', '00:33', 1463, 1473, 10],\n", + " [1346, '00:24', '00:40', 1464, 1480, 16],\n", + " [1347, '00:25', '01:12', 1465, 1512, 47],\n", + " [1348, '00:28', '01:07', 1468, 1507, 39],\n", + " [1349, '00:33', '01:05', 1473, 1505, 32],\n", + " [1350, '00:43', '01:21', 1483, 1521, 38],\n", + " [1351, '00:44', '00:54', 1484, 1494, 10],\n", + " [1352, '00:47', '01:09', 1487, 1509, 22],\n", + " [1353, '00:47', '01:26', 1487, 1526, 39],\n", + " [1354, '00:54', '01:04', 1494, 1504, 10],\n", + " [1355, '00:57', '01:07', 1497, 1507, 10]\n", + "] # yapf:disable\n", + "\n", + "\n", + "def find_minimum_number_of_drivers(shifts, params):\n", + " \"\"\"Minimize the number of needed drivers.\"\"\"\n", + "\n", + " num_shifts = len(shifts)\n", + "\n", + " # All durations are in minutes.\n", + " max_driving_time = 540 # 8 hours.\n", + " max_driving_time_without_pauses = 240 # 4 hours\n", + " min_pause_after_4h = 30\n", + " min_delay_between_shifts = 2\n", + " max_working_time = 720\n", + " min_working_time = 390 # 6.5 hours\n", + " extra_time = 10 + 25\n", + " max_break = 180\n", + "\n", + " # Computed data.\n", + " total_driving_time = sum(shift[5] for shift in shifts)\n", + " min_num_drivers = int(\n", + " math.ceil(total_driving_time * 1.0 / max_driving_time))\n", + " min_start_time = min(shift[3] for shift in shifts)\n", + " max_end_time = max(shift[4] for shift in shifts)\n", + "\n", + " print('Bus driver scheduling')\n", + " print(' num shifts =', num_shifts)\n", + " print(' total driving time =', total_driving_time, 'minutes')\n", + " print(' min num drivers =', min_num_drivers)\n", + " print(' min start time =', min_start_time)\n", + " print(' max end time =', max_end_time)\n", + "\n", + " # We are going to build a flow from a the start of the day to the end\n", + " # of the day.\n", + " #\n", + " # Along the path, we will accumulate driving time, accrued time since the\n", + " # last break, and total working time.\n", + "\n", + " model = cp_model.CpModel()\n", + "\n", + " # Per node info\n", + " driving_time = {}\n", + " working_time = {}\n", + " no_break_driving_time = {}\n", + "\n", + " incoming_literals = collections.defaultdict(list)\n", + " outgoing_literals = collections.defaultdict(list)\n", + " outgoing_source_literals = []\n", + " incoming_sink_literals = []\n", + "\n", + " all_literals = []\n", + "\n", + " # Create all the shift variables before iterating on the transitions\n", + " # between these shifts.\n", + " for shift in range(num_shifts):\n", + " driving_time[shift] = model.NewIntVar(0, max_driving_time, 'dt_%i' % shift)\n", + " no_break_driving_time[shift] = model.NewIntVar(\n", + " 0, max_driving_time_without_pauses, 'nbdt_%i' % shift)\n", + " working_time[shift] = model.NewIntVar(\n", + " 0, max_working_time, 'wt_%i' % shift)\n", + "\n", + " for shift in range(num_shifts):\n", + " duration = shifts[shift][5]\n", + "\n", + " # Arc from source to shift.\n", + " # - set the working time of the driver\n", + " # - increase driving time and driving time since the last break\n", + " source_lit = model.NewBoolVar('from source to %i' % shift)\n", + " all_literals.append(source_lit)\n", + " outgoing_source_literals.append(source_lit)\n", + " incoming_literals[shift].append(source_lit)\n", + " model.Add(driving_time[shift] == duration).OnlyEnforceIf(source_lit)\n", + " model.Add(no_break_driving_time[shift] == duration).OnlyEnforceIf(\n", + " source_lit)\n", + " model.Add(working_time[shift] == duration + extra_time).OnlyEnforceIf(\n", + " source_lit)\n", + "\n", + " # Arc from shift to sink\n", + " # - checks that working time is greater than min_working_time\n", + " sink_lit = model.NewBoolVar('from %i to sink' % shift)\n", + " all_literals.append(sink_lit)\n", + " outgoing_literals[shift].append(sink_lit)\n", + " incoming_sink_literals.append(sink_lit)\n", + " model.Add(working_time[shift] >= min_working_time).OnlyEnforceIf(sink_lit)\n", + "\n", + " for other in range(num_shifts):\n", + " delay = shifts[other][3] - shifts[shift][4]\n", + " if delay < min_delay_between_shifts:\n", + " continue\n", + " if delay > max_break:\n", + " break # Assumes start times are sorted.\n", + " other_duration = shifts[other][5]\n", + " lit = model.NewBoolVar('from %i to %i' % (shift, other))\n", + " all_literals.append(lit)\n", + "\n", + " # Increase driving time\n", + " model.Add(driving_time[other] ==\n", + " driving_time[shift] + other_duration).OnlyEnforceIf(lit)\n", + "\n", + " # Increase no_break_driving or reset it to 0 depending on the delay\n", + " if delay >= min_pause_after_4h:\n", + " model.Add(no_break_driving_time[other] ==\n", + " other_duration).OnlyEnforceIf(lit)\n", + " else:\n", + " model.Add(\n", + " no_break_driving_time[other] ==\n", + " no_break_driving_time[shift] + other_duration).OnlyEnforceIf(lit)\n", + "\n", + " # Increase working time\n", + " model.Add(working_time[other] == working_time[shift] + delay +\n", + " other_duration).OnlyEnforceIf(lit)\n", + "\n", + " # Add arc\n", + " outgoing_literals[shift].append(lit)\n", + " incoming_literals[other].append(lit)\n", + "\n", + " # Create dag constraint.\n", + " for shift in range(num_shifts):\n", + " model.Add(sum(outgoing_literals[shift]) == 1)\n", + " model.Add(sum(incoming_literals[shift]) == 1)\n", + "\n", + " # Num drivers\n", + " num_drivers = model.NewIntVar(min_num_drivers, min_num_drivers * 3, 'num_drivers')\n", + " model.Add(sum(incoming_sink_literals) == num_drivers)\n", + " model.Add(sum(outgoing_source_literals) == num_drivers)\n", + "\n", + " model.Minimize(num_drivers) \n", + "\n", + " # Solve model.\n", + " solver = cp_model.CpSolver()\n", + " solver.parameters.log_search_progress = True\n", + " #solver.parameters.num_search_workers = 16\n", + " # solver.parameters.boolean_encoding_level = 0\n", + " # solver.parameters.lns_focus_on_decision_variables = True\n", + " status = solver.Solve(model)\n", + "\n", + " if status != cp_model.OPTIMAL and status != cp_model.FEASIBLE:\n", + " return -1\n", + "\n", + " # Display solution\n", + " optimal_num_drivers = int(solver.ObjectiveValue())\n", + " print('minimal number of drivers =', optimal_num_drivers)\n", + " return optimal_num_drivers\n", + "\n", + "\n", + "\"\"\"Optimize the bus driver allocation in two passes.\"\"\"\n", + "print('----------- first pass: minimize the number of drivers')\n", + "shifts = []\n", + "if args.instance == 1:\n", + " shifts = SAMPLE_SHIFTS_SMALL\n", + "elif args.instance == 2:\n", + " shifts = SAMPLE_SHIFTS_MEDIUM\n", + "elif args.instance == 3:\n", + " shifts = SAMPLE_SHIFTS_LARGE\n", + "num_drivers = find_minimum_number_of_drivers(shifts, args.params)\n", + "\n", + "print('----------- second pass: minimize the sum of working times')\n", + "#bus_driver_scheduling(False, num_drivers)\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/examples/bus_driver_scheduling_sat.ipynb b/examples/notebook/examples/bus_driver_scheduling_sat.ipynb new file mode 100644 index 0000000000..4a262c1b2e --- /dev/null +++ b/examples/notebook/examples/bus_driver_scheduling_sat.ipynb @@ -0,0 +1,1972 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010-2018 Google LLC\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"This model implements a bus driver scheduling problem.\n", + "\n", + "Constraints:\n", + "- max driving time per driver <= 9h\n", + "- max working time per driver <= 12h\n", + "- min working time per driver >= 6.5h (soft)\n", + "- 30 min break after each 4h of driving time per driver\n", + "- 10 min preparation time before the first shift\n", + "- 15 min cleaning time after the last shift\n", + "- 2 min waiting time after each shift for passenger boarding and alighting\n", + "\"\"\"\n", + "from __future__ import print_function\n", + "\n", + "import collections\n", + "import math\n", + "\n", + "from ortools.sat.python import cp_model\n", + "\n", + "SAMPLE_SHIFTS_SMALL = [\n", + " #\n", + " # column description:\n", + " # - shift id\n", + " # - shift start time as hh:mm string (for logging and readability purposes)\n", + " # - shift end time as hh:mm string (for logging and readability purposes)\n", + " # - shift start minute\n", + " # - shift end minute\n", + " # - shift duration in minutes\n", + " #\n", + " [0, '05:18', '06:00', 318, 360, 42],\n", + " [1, '05:26', '06:08', 326, 368, 42],\n", + " [2, '05:40', '05:56', 340, 356, 16],\n", + " [3, '06:06', '06:51', 366, 411, 45],\n", + " [4, '06:40', '07:52', 400, 472, 72],\n", + " [5, '06:42', '07:13', 402, 433, 31],\n", + " [6, '06:48', '08:15', 408, 495, 87],\n", + " [7, '06:59', '08:07', 419, 487, 68],\n", + " [8, '07:20', '07:36', 440, 456, 16],\n", + " [9, '07:35', '08:22', 455, 502, 47],\n", + " [10, '07:50', '08:55', 470, 535, 65],\n", + " [11, '08:00', '09:05', 480, 545, 65],\n", + " [12, '08:00', '08:35', 480, 515, 35],\n", + " [13, '08:11', '09:41', 491, 581, 90],\n", + " [14, '08:28', '08:50', 508, 530, 22],\n", + " [15, '08:35', '08:45', 515, 525, 10],\n", + " [16, '08:40', '08:50', 520, 530, 10],\n", + " [17, '09:03', '10:28', 543, 628, 85],\n", + " [18, '09:23', '09:49', 563, 589, 26],\n", + " [19, '09:30', '09:40', 570, 580, 10],\n", + " [20, '09:57', '10:20', 597, 620, 23],\n", + " [21, '10:09', '11:03', 609, 663, 54],\n", + " [22, '10:20', '10:30', 620, 630, 10],\n", + " [23, '11:00', '11:10', 660, 670, 10],\n", + " [24, '11:45', '12:24', 705, 744, 39],\n", + " [25, '12:18', '13:00', 738, 780, 42],\n", + " [26, '13:18', '14:44', 798, 884, 86],\n", + " [27, '13:53', '14:49', 833, 889, 56],\n", + " [28, '14:03', '14:50', 843, 890, 47],\n", + " [29, '14:28', '15:15', 868, 915, 47],\n", + " [30, '14:30', '15:41', 870, 941, 71],\n", + " [31, '14:48', '15:35', 888, 935, 47],\n", + " [32, '15:03', '15:50', 903, 950, 47],\n", + " [33, '15:28', '16:54', 928, 1014, 86],\n", + " [34, '15:38', '16:25', 938, 985, 47],\n", + " [35, '15:40', '15:56', 940, 956, 16],\n", + " [36, '15:58', '16:45', 958, 1005, 47],\n", + " [37, '16:04', '17:30', 964, 1050, 86],\n", + " [38, '16:28', '17:15', 988, 1035, 47],\n", + " [39, '16:36', '17:21', 996, 1041, 45],\n", + " [40, '16:50', '17:00', 1010, 1020, 10],\n", + " [41, '16:54', '18:20', 1014, 1100, 86],\n", + " [42, '17:01', '17:13', 1021, 1033, 12],\n", + " [43, '17:19', '18:31', 1039, 1111, 72],\n", + " [44, '17:23', '18:10', 1043, 1090, 47],\n", + " [45, '17:34', '18:15', 1054, 1095, 41],\n", + " [46, '18:04', '19:29', 1084, 1169, 85],\n", + " [47, '18:34', '19:58', 1114, 1198, 84],\n", + " [48, '19:56', '20:34', 1196, 1234, 38],\n", + " [49, '20:05', '20:48', 1205, 1248, 43]\n", + "] # yapf:disable\n", + "\n", + "SAMPLE_SHIFTS_MEDIUM = [\n", + " [0, '04:30', '04:53', 270, 293, 23],\n", + " [1, '04:46', '04:56', 286, 296, 10],\n", + " [2, '04:52', '05:56', 292, 356, 64],\n", + " [3, '04:53', '05:23', 293, 323, 30],\n", + " [4, '05:07', '05:44', 307, 344, 37],\n", + " [5, '05:10', '06:06', 310, 366, 56],\n", + " [6, '05:18', '06:03', 318, 363, 45],\n", + " [7, '05:30', '05:40', 330, 340, 10],\n", + " [8, '05:30', '05:40', 330, 340, 10],\n", + " [9, '05:33', '06:15', 333, 375, 42],\n", + " [10, '05:40', '05:50', 340, 350, 10],\n", + " [11, '05:43', '06:08', 343, 368, 25],\n", + " [12, '05:54', '07:20', 354, 440, 86],\n", + " [13, '06:04', '06:37', 364, 397, 33],\n", + " [14, '06:13', '06:58', 373, 418, 45],\n", + " [15, '06:14', '07:40', 374, 460, 86],\n", + " [16, '06:15', '07:15', 375, 435, 60],\n", + " [17, '06:16', '06:26', 376, 386, 10],\n", + " [18, '06:17', '06:34', 377, 394, 17],\n", + " [19, '06:20', '06:36', 380, 396, 16],\n", + " [20, '06:22', '07:06', 382, 426, 44],\n", + " [21, '06:24', '07:50', 384, 470, 86],\n", + " [22, '06:27', '06:44', 387, 404, 17],\n", + " [23, '06:30', '06:40', 390, 400, 10],\n", + " [24, '06:31', '06:43', 391, 403, 12],\n", + " [25, '06:33', '07:53', 393, 473, 80],\n", + " [26, '06:34', '07:09', 394, 429, 35],\n", + " [27, '06:40', '06:56', 400, 416, 16],\n", + " [28, '06:44', '07:17', 404, 437, 33],\n", + " [29, '06:46', '06:58', 406, 418, 12],\n", + " [30, '06:49', '07:43', 409, 463, 54],\n", + " [31, '06:50', '07:05', 410, 425, 15],\n", + " [32, '06:52', '07:36', 412, 456, 44],\n", + " [33, '06:54', '07:27', 414, 447, 33],\n", + " [34, '06:56', '08:23', 416, 503, 87],\n", + " [35, '07:04', '07:44', 424, 464, 40],\n", + " [36, '07:11', '08:36', 431, 516, 85],\n", + " [37, '07:17', '07:35', 437, 455, 18],\n", + " [38, '07:22', '08:06', 442, 486, 44],\n", + " [39, '07:27', '08:15', 447, 495, 48],\n", + " [40, '07:35', '07:45', 455, 465, 10],\n", + " [41, '07:43', '08:08', 463, 488, 25],\n", + " [42, '07:50', '08:37', 470, 517, 47],\n", + " [43, '07:58', '08:45', 478, 525, 47],\n", + " [44, '08:00', '08:35', 480, 515, 35],\n", + " [45, '08:06', '08:51', 486, 531, 45],\n", + " [46, '08:10', '08:45', 490, 525, 35],\n", + " [47, '08:15', '08:30', 495, 510, 15],\n", + " [48, '08:16', '09:00', 496, 540, 44],\n", + " [49, '08:18', '09:16', 498, 556, 58],\n", + " [50, '08:20', '08:36', 500, 516, 16],\n", + " [51, '08:27', '09:07', 507, 547, 40],\n", + " [52, '08:30', '08:45', 510, 525, 15],\n", + " [53, '08:35', '09:15', 515, 555, 40],\n", + " [54, '08:46', '09:30', 526, 570, 44],\n", + " [55, '08:51', '09:17', 531, 557, 26],\n", + " [56, '08:55', '09:15', 535, 555, 20],\n", + " [57, '08:58', '09:38', 538, 578, 40],\n", + " [58, '09:00', '09:35', 540, 575, 35],\n", + " [59, '09:00', '09:16', 540, 556, 16],\n", + " [60, '09:20', '09:36', 560, 576, 16],\n", + " [61, '09:31', '09:43', 571, 583, 12],\n", + " [62, '09:33', '10:15', 573, 615, 42],\n", + " [63, '09:54', '10:05', 594, 605, 11],\n", + " [64, '10:11', '10:38', 611, 638, 27],\n", + " [65, '10:18', '11:00', 618, 660, 42],\n", + " [66, '10:21', '10:47', 621, 647, 26],\n", + " [67, '10:25', '11:04', 625, 664, 39],\n", + " [68, '10:26', '11:08', 626, 668, 42],\n", + " [69, '10:44', '12:11', 644, 731, 87],\n", + " [70, '11:00', '11:16', 660, 676, 16],\n", + " [71, '11:15', '11:54', 675, 714, 39],\n", + " [72, '11:16', '11:28', 676, 688, 12],\n", + " [73, '11:20', '11:30', 680, 690, 10],\n", + " [74, '11:21', '11:47', 681, 707, 26],\n", + " [75, '11:25', '12:04', 685, 724, 39],\n", + " [76, '11:34', '11:45', 694, 705, 11],\n", + " [77, '11:35', '12:14', 695, 734, 39],\n", + " [78, '11:41', '12:23', 701, 743, 42],\n", + " [79, '11:44', '12:35', 704, 755, 51],\n", + " [80, '11:46', '11:58', 706, 718, 12],\n", + " [81, '12:00', '12:10', 720, 730, 10],\n", + " [82, '12:04', '12:15', 724, 735, 11],\n", + " [83, '12:04', '13:04', 724, 784, 60],\n", + " [84, '12:11', '12:38', 731, 758, 27],\n", + " [85, '12:15', '12:54', 735, 774, 39],\n", + " [86, '12:25', '13:10', 745, 790, 45],\n", + " [87, '12:30', '12:40', 750, 760, 10],\n", + " [88, '12:34', '13:58', 754, 838, 84],\n", + " [89, '12:38', '13:25', 758, 805, 47],\n", + " [90, '12:48', '13:35', 768, 815, 47],\n", + " [91, '13:00', '13:16', 780, 796, 16],\n", + " [92, '13:05', '13:44', 785, 824, 39],\n", + " [93, '13:08', '13:55', 788, 835, 47],\n", + " [94, '13:14', '14:38', 794, 878, 84],\n", + " [95, '13:23', '13:49', 803, 829, 26],\n", + " [96, '13:25', '14:04', 805, 844, 39],\n", + " [97, '13:28', '14:54', 808, 894, 86],\n", + " [98, '13:31', '13:43', 811, 823, 12],\n", + " [99, '13:34', '14:58', 814, 898, 84],\n", + " [100, '13:38', '14:25', 818, 865, 47],\n", + " [101, '13:38', '15:04', 818, 904, 86],\n", + " [102, '13:39', '14:33', 819, 873, 54],\n", + " [103, '13:40', '13:50', 820, 830, 10],\n", + " [104, '13:43', '14:10', 823, 850, 27],\n", + " [105, '13:48', '14:35', 828, 875, 47],\n", + " [106, '13:48', '14:35', 828, 875, 47],\n", + " [107, '13:53', '14:40', 833, 880, 47],\n", + " [108, '13:58', '15:24', 838, 924, 86],\n", + " [109, '13:58', '14:25', 838, 865, 27],\n", + " [110, '14:00', '14:16', 840, 856, 16],\n", + " [111, '14:13', '15:00', 853, 900, 47],\n", + " [112, '14:20', '15:31', 860, 931, 71],\n", + " [113, '14:25', '15:02', 865, 902, 37],\n", + " [114, '14:34', '14:45', 874, 885, 11],\n", + " [115, '14:40', '15:51', 880, 951, 71],\n", + " [116, '14:40', '14:56', 880, 896, 16],\n", + " [117, '14:46', '14:58', 886, 898, 12],\n", + " [118, '14:49', '15:43', 889, 943, 54],\n", + " [119, '14:52', '15:21', 892, 921, 29],\n", + " [120, '14:58', '16:24', 898, 984, 86],\n", + " [121, '14:59', '15:53', 899, 953, 54],\n", + " [122, '15:00', '15:10', 900, 910, 10],\n", + " [123, '15:00', '15:35', 900, 935, 35],\n", + " [124, '15:08', '15:45', 908, 945, 37],\n", + " [125, '15:12', '15:36', 912, 936, 24],\n", + " [126, '15:18', '16:05', 918, 965, 47],\n", + " [127, '15:24', '16:05', 924, 965, 41],\n", + " [128, '15:31', '15:43', 931, 943, 12],\n", + " [129, '15:35', '15:54', 935, 954, 19],\n", + " [130, '15:36', '16:21', 936, 981, 45],\n", + " [131, '15:39', '16:33', 939, 993, 54],\n", + " [132, '15:48', '16:35', 948, 995, 47],\n", + " [133, '15:50', '17:01', 950, 1021, 71],\n", + " [134, '16:03', '16:50', 963, 1010, 47],\n", + " [135, '16:18', '17:44', 978, 1064, 86],\n", + " [136, '16:24', '17:05', 984, 1025, 41],\n", + " [137, '16:28', '17:15', 988, 1035, 47],\n", + " [138, '16:34', '17:15', 994, 1035, 41],\n", + " [139, '16:38', '17:25', 998, 1045, 47],\n", + " [140, '16:40', '16:56', 1000, 1016, 16],\n", + " [141, '16:45', '17:04', 1005, 1024, 19],\n", + " [142, '16:52', '17:36', 1012, 1056, 44],\n", + " [143, '16:58', '17:45', 1018, 1065, 47],\n", + " [144, '17:04', '18:30', 1024, 1110, 86],\n", + " [145, '17:04', '17:45', 1024, 1065, 41],\n", + " [146, '17:09', '18:03', 1029, 1083, 54],\n", + " [147, '17:18', '18:44', 1038, 1124, 86],\n", + " [148, '17:28', '18:15', 1048, 1095, 47],\n", + " [149, '17:29', '18:41', 1049, 1121, 72],\n", + " [150, '17:36', '18:21', 1056, 1101, 45],\n", + " [151, '17:38', '18:25', 1058, 1105, 47],\n", + " [152, '17:40', '17:56', 1060, 1076, 16],\n", + " [153, '17:45', '18:04', 1065, 1084, 19],\n", + " [154, '17:46', '17:58', 1066, 1078, 12],\n", + " [155, '17:48', '18:35', 1068, 1115, 47],\n", + " [156, '17:49', '18:43', 1069, 1123, 54],\n", + " [157, '17:55', '18:14', 1075, 1094, 19],\n", + " [158, '17:58', '18:45', 1078, 1125, 47],\n", + " [159, '18:00', '19:11', 1080, 1151, 71],\n", + " [160, '18:04', '18:45', 1084, 1125, 41],\n", + " [161, '18:09', '19:03', 1089, 1143, 54],\n", + " [162, '18:13', '19:00', 1093, 1140, 47],\n", + " [163, '18:13', '18:40', 1093, 1120, 27],\n", + " [164, '18:19', '19:13', 1099, 1153, 54],\n", + " [165, '18:28', '19:25', 1108, 1165, 57],\n", + " [166, '18:48', '19:28', 1128, 1168, 40],\n", + " [167, '19:03', '19:45', 1143, 1185, 42],\n", + " [168, '19:20', '19:36', 1160, 1176, 16],\n", + " [169, '19:21', '19:31', 1161, 1171, 10],\n", + " [170, '19:25', '20:04', 1165, 1204, 39],\n", + " [171, '19:26', '20:08', 1166, 1208, 42],\n", + " [172, '19:30', '19:40', 1170, 1180, 10],\n", + " [173, '19:44', '20:33', 1184, 1233, 49],\n", + " [174, '19:48', '21:09', 1188, 1269, 81],\n", + " [175, '19:53', '21:02', 1193, 1262, 69],\n", + " [176, '20:04', '20:29', 1204, 1229, 25],\n", + " [177, '20:17', '21:03', 1217, 1263, 46],\n", + " [178, '20:20', '20:57', 1220, 1257, 37],\n", + " [179, '20:29', '21:18', 1229, 1278, 49],\n", + " [180, '20:35', '21:54', 1235, 1314, 79],\n", + " [181, '20:40', '20:50', 1240, 1250, 10],\n", + " [182, '20:47', '21:42', 1247, 1302, 55],\n", + " [183, '21:00', '21:10', 1260, 1270, 10],\n", + " [184, '21:07', '21:44', 1267, 1304, 37],\n", + " [185, '21:14', '22:03', 1274, 1323, 49],\n", + " [186, '21:39', '21:55', 1299, 1315, 16],\n", + " [187, '21:40', '22:17', 1300, 1337, 37],\n", + " [188, '21:40', '21:50', 1300, 1310, 10],\n", + " [189, '21:48', '22:03', 1308, 1323, 15],\n", + " [190, '22:17', '23:03', 1337, 1383, 46],\n", + " [191, '22:43', '23:08', 1363, 1388, 25],\n", + " [192, '23:35', '01:05', 1415, 1505, 90],\n", + " [193, '23:46', '00:01', 1426, 1441, 15],\n", + " [194, '23:47', '00:33', 1427, 1473, 46],\n", + " [195, '23:52', '00:24', 1432, 1464, 32],\n", + " [196, '23:58', '00:38', 1438, 1478, 40],\n", + " [197, '00:02', '00:12', 1442, 1452, 10],\n", + " [198, '00:07', '00:39', 1447, 1479, 32],\n", + " [199, '00:25', '01:12', 1465, 1512, 47]\n", + "] # yapf:disable\n", + "\n", + "SAMPLE_SHIFTS_LARGE = [\n", + " [0, '04:18', '05:00', 258, 300, 42],\n", + " [1, '04:27', '05:08', 267, 308, 41],\n", + " [2, '04:29', '05:26', 269, 326, 57],\n", + " [3, '04:29', '04:55', 269, 295, 26],\n", + " [4, '04:30', '04:53', 270, 293, 23],\n", + " [5, '04:30', '04:51', 270, 291, 21],\n", + " [6, '04:31', '04:53', 271, 293, 22],\n", + " [7, '04:33', '05:15', 273, 315, 42],\n", + " [8, '04:34', '04:44', 274, 284, 10],\n", + " [9, '04:34', '05:03', 274, 303, 29],\n", + " [10, '04:35', '04:50', 275, 290, 15],\n", + " [11, '04:36', '04:46', 276, 286, 10],\n", + " [12, '04:37', '05:18', 277, 318, 41],\n", + " [13, '04:41', '05:13', 281, 313, 32],\n", + " [14, '04:42', '05:23', 282, 323, 41],\n", + " [15, '04:43', '04:53', 283, 293, 10],\n", + " [16, '04:44', '05:45', 284, 345, 61],\n", + " [17, '04:45', '05:11', 285, 311, 26],\n", + " [18, '04:46', '05:01', 286, 301, 15],\n", + " [19, '04:46', '04:56', 286, 296, 10],\n", + " [20, '04:47', '05:14', 287, 314, 27],\n", + " [21, '04:48', '05:30', 288, 330, 42],\n", + " [22, '04:49', '05:41', 289, 341, 52],\n", + " [23, '04:49', '05:18', 289, 318, 29],\n", + " [24, '04:50', '05:33', 290, 333, 43],\n", + " [25, '04:52', '05:56', 292, 356, 64],\n", + " [26, '04:52', '05:07', 292, 307, 15],\n", + " [27, '04:53', '05:19', 293, 319, 26],\n", + " [28, '04:53', '05:23', 293, 323, 30],\n", + " [29, '04:55', '05:27', 295, 327, 32],\n", + " [30, '04:57', '05:38', 297, 338, 41],\n", + " [31, '05:00', '06:00', 300, 360, 60],\n", + " [32, '05:00', '05:54', 300, 354, 54],\n", + " [33, '05:01', '05:33', 301, 333, 32],\n", + " [34, '05:01', '05:26', 301, 326, 25],\n", + " [35, '05:02', '05:29', 302, 329, 27],\n", + " [36, '05:02', '05:12', 302, 312, 10],\n", + " [37, '05:03', '05:45', 303, 345, 42],\n", + " [38, '05:03', '05:18', 303, 318, 15],\n", + " [39, '05:03', '06:28', 303, 388, 85],\n", + " [40, '05:03', '05:13', 303, 313, 10],\n", + " [41, '05:04', '06:24', 304, 384, 80],\n", + " [42, '05:07', '05:44', 307, 344, 37],\n", + " [43, '05:08', '05:48', 308, 348, 40],\n", + " [44, '05:10', '06:06', 310, 366, 56],\n", + " [45, '05:11', '05:37', 311, 337, 26],\n", + " [46, '05:11', '05:53', 311, 353, 42],\n", + " [47, '05:13', '06:15', 313, 375, 62],\n", + " [48, '05:13', '05:38', 313, 338, 25],\n", + " [49, '05:16', '05:44', 316, 344, 28],\n", + " [50, '05:17', '05:27', 317, 327, 10],\n", + " [51, '05:18', '06:40', 318, 400, 82],\n", + " [52, '05:18', '06:03', 318, 363, 45],\n", + " [53, '05:18', '06:11', 318, 371, 53],\n", + " [54, '05:18', '06:00', 318, 360, 42],\n", + " [55, '05:19', '06:34', 319, 394, 75],\n", + " [56, '05:20', '06:17', 320, 377, 57],\n", + " [57, '05:22', '05:59', 322, 359, 37],\n", + " [58, '05:24', '05:48', 324, 348, 24],\n", + " [59, '05:25', '05:40', 325, 340, 15],\n", + " [60, '05:26', '06:08', 326, 368, 42],\n", + " [61, '05:27', '06:30', 327, 390, 63],\n", + " [62, '05:27', '05:54', 327, 354, 27],\n", + " [63, '05:28', '05:53', 328, 353, 25],\n", + " [64, '05:29', '05:44', 329, 344, 15],\n", + " [65, '05:30', '05:40', 330, 340, 10],\n", + " [66, '05:30', '05:40', 330, 340, 10],\n", + " [67, '05:30', '05:40', 330, 340, 10],\n", + " [68, '05:32', '06:53', 332, 413, 81],\n", + " [69, '05:33', '07:00', 333, 420, 87],\n", + " [70, '05:33', '06:15', 333, 375, 42],\n", + " [71, '05:33', '05:47', 333, 347, 14],\n", + " [72, '05:37', '06:13', 337, 373, 36],\n", + " [73, '05:37', '06:05', 337, 365, 28],\n", + " [74, '05:38', '06:33', 338, 393, 55],\n", + " [75, '05:38', '06:04', 338, 364, 26],\n", + " [76, '05:38', '06:18', 338, 378, 40],\n", + " [77, '05:39', '05:54', 339, 354, 15],\n", + " [78, '05:40', '05:56', 340, 356, 16],\n", + " [79, '05:40', '06:41', 340, 401, 61],\n", + " [80, '05:40', '05:50', 340, 350, 10],\n", + " [81, '05:41', '06:23', 341, 383, 42],\n", + " [82, '05:41', '06:01', 341, 361, 20],\n", + " [83, '05:43', '06:08', 343, 368, 25],\n", + " [84, '05:44', '07:10', 344, 430, 86],\n", + " [85, '05:44', '05:55', 344, 355, 11],\n", + " [86, '05:45', '06:44', 345, 404, 59],\n", + " [87, '05:47', '06:17', 347, 377, 30],\n", + " [88, '05:48', '07:08', 348, 428, 80],\n", + " [89, '05:48', '06:30', 348, 390, 42],\n", + " [90, '05:50', '06:50', 350, 410, 60],\n", + " [91, '05:50', '06:00', 350, 360, 10],\n", + " [92, '05:50', '06:00', 350, 360, 10],\n", + " [93, '05:50', '06:51', 350, 411, 61],\n", + " [94, '05:52', '06:33', 352, 393, 41],\n", + " [95, '05:52', '06:36', 352, 396, 44],\n", + " [96, '05:52', '06:23', 352, 383, 31],\n", + " [97, '05:54', '06:14', 354, 374, 20],\n", + " [98, '05:54', '07:20', 354, 440, 86],\n", + " [99, '05:55', '06:40', 355, 400, 45],\n", + " [100, '05:55', '06:27', 355, 387, 32],\n", + " [101, '05:56', '06:35', 356, 395, 39],\n", + " [102, '05:56', '06:06', 356, 366, 10],\n", + " [103, '05:57', '06:21', 357, 381, 24],\n", + " [104, '05:58', '07:23', 358, 443, 85],\n", + " [105, '05:58', '06:23', 358, 383, 25],\n", + " [106, '05:58', '06:08', 358, 368, 10],\n", + " [107, '05:58', '06:43', 358, 403, 45],\n", + " [108, '06:00', '06:10', 360, 370, 10],\n", + " [109, '06:00', '06:16', 360, 376, 16],\n", + " [110, '06:00', '07:01', 360, 421, 61],\n", + " [111, '06:01', '07:00', 361, 420, 59],\n", + " [112, '06:01', '06:13', 361, 373, 12],\n", + " [113, '06:01', '06:45', 361, 405, 44],\n", + " [114, '06:03', '06:50', 363, 410, 47],\n", + " [115, '06:04', '06:37', 364, 397, 33],\n", + " [116, '06:04', '07:30', 364, 450, 86],\n", + " [117, '06:05', '06:24', 365, 384, 19],\n", + " [118, '06:06', '06:51', 366, 411, 45],\n", + " [119, '06:07', '06:43', 367, 403, 36],\n", + " [120, '06:08', '07:30', 368, 450, 82],\n", + " [121, '06:10', '06:20', 370, 380, 10],\n", + " [122, '06:10', '07:17', 370, 437, 67],\n", + " [123, '06:11', '06:54', 371, 414, 43],\n", + " [124, '06:11', '06:21', 371, 381, 10],\n", + " [125, '06:13', '06:38', 373, 398, 25],\n", + " [126, '06:13', '06:58', 373, 418, 45],\n", + " [127, '06:13', '06:53', 373, 413, 40],\n", + " [128, '06:14', '07:03', 374, 423, 49],\n", + " [129, '06:14', '06:47', 374, 407, 33],\n", + " [130, '06:14', '07:40', 374, 460, 86],\n", + " [131, '06:15', '07:15', 375, 435, 60],\n", + " [132, '06:16', '06:28', 376, 388, 12],\n", + " [133, '06:16', '06:26', 376, 386, 10],\n", + " [134, '06:17', '06:34', 377, 394, 17],\n", + " [135, '06:18', '07:06', 378, 426, 48],\n", + " [136, '06:18', '07:38', 378, 458, 80],\n", + " [137, '06:18', '07:02', 378, 422, 44],\n", + " [138, '06:19', '06:53', 379, 413, 34],\n", + " [139, '06:20', '07:25', 380, 445, 65],\n", + " [140, '06:20', '06:36', 380, 396, 16],\n", + " [141, '06:20', '06:30', 380, 390, 10],\n", + " [142, '06:20', '06:30', 380, 390, 10],\n", + " [143, '06:21', '06:49', 381, 409, 28],\n", + " [144, '06:22', '07:06', 382, 426, 44],\n", + " [145, '06:24', '07:50', 384, 470, 86],\n", + " [146, '06:24', '06:57', 384, 417, 33],\n", + " [147, '06:26', '07:45', 386, 465, 79],\n", + " [148, '06:26', '07:10', 386, 430, 44],\n", + " [149, '06:27', '06:44', 387, 404, 17],\n", + " [150, '06:28', '06:53', 388, 413, 25],\n", + " [151, '06:28', '07:14', 388, 434, 46],\n", + " [152, '06:29', '07:03', 389, 423, 34],\n", + " [153, '06:30', '06:40', 390, 400, 10],\n", + " [154, '06:30', '07:37', 390, 457, 67],\n", + " [155, '06:31', '06:43', 391, 403, 12],\n", + " [156, '06:33', '07:14', 393, 434, 41],\n", + " [157, '06:33', '07:53', 393, 473, 80],\n", + " [158, '06:34', '08:16', 394, 496, 102],\n", + " [159, '06:34', '07:09', 394, 429, 35],\n", + " [160, '06:34', '07:07', 394, 427, 33],\n", + " [161, '06:36', '07:21', 396, 441, 45],\n", + " [162, '06:37', '07:22', 397, 442, 45],\n", + " [163, '06:37', '06:54', 397, 414, 17],\n", + " [164, '06:38', '07:30', 398, 450, 52],\n", + " [165, '06:38', '07:18', 398, 438, 40],\n", + " [166, '06:39', '07:33', 399, 453, 54],\n", + " [167, '06:40', '07:52', 400, 472, 72],\n", + " [168, '06:40', '06:50', 400, 410, 10],\n", + " [169, '06:40', '07:22', 400, 442, 42],\n", + " [170, '06:40', '06:56', 400, 416, 16],\n", + " [171, '06:41', '08:00', 401, 480, 79],\n", + " [172, '06:42', '07:26', 402, 446, 44],\n", + " [173, '06:42', '07:13', 402, 433, 31],\n", + " [174, '06:43', '07:08', 403, 428, 25],\n", + " [175, '06:43', '07:30', 403, 450, 47],\n", + " [176, '06:43', '07:23', 403, 443, 40],\n", + " [177, '06:44', '07:17', 404, 437, 33],\n", + " [178, '06:44', '08:13', 404, 493, 89],\n", + " [179, '06:46', '07:01', 406, 421, 15],\n", + " [180, '06:46', '06:58', 406, 418, 12],\n", + " [181, '06:47', '07:04', 407, 424, 17],\n", + " [182, '06:48', '08:15', 408, 495, 87],\n", + " [183, '06:48', '07:34', 408, 454, 46],\n", + " [184, '06:48', '07:37', 408, 457, 49],\n", + " [185, '06:49', '07:43', 409, 463, 54],\n", + " [186, '06:50', '08:00', 410, 480, 70],\n", + " [187, '06:50', '07:00', 410, 420, 10],\n", + " [188, '06:50', '07:05', 410, 425, 15],\n", + " [189, '06:51', '07:18', 411, 438, 27],\n", + " [190, '06:52', '07:36', 412, 456, 44],\n", + " [191, '06:53', '07:37', 413, 457, 44],\n", + " [192, '06:54', '08:20', 414, 500, 86],\n", + " [193, '06:54', '07:27', 414, 447, 33],\n", + " [194, '06:54', '07:20', 414, 440, 26],\n", + " [195, '06:56', '08:23', 416, 503, 87],\n", + " [196, '06:57', '07:12', 417, 432, 15],\n", + " [197, '06:57', '07:58', 417, 478, 61],\n", + " [198, '06:57', '07:45', 417, 465, 48],\n", + " [199, '06:57', '07:40', 417, 460, 43],\n", + " [200, '06:58', '07:23', 418, 443, 25],\n", + " [201, '06:59', '07:53', 419, 473, 54],\n", + " [202, '06:59', '08:07', 419, 487, 68],\n", + " [203, '07:00', '07:10', 420, 430, 10],\n", + " [204, '07:00', '07:16', 420, 436, 16],\n", + " [205, '07:01', '08:30', 421, 510, 89],\n", + " [206, '07:01', '07:13', 421, 433, 12],\n", + " [207, '07:01', '07:43', 421, 463, 42],\n", + " [208, '07:03', '08:30', 423, 510, 87],\n", + " [209, '07:04', '07:37', 424, 457, 33],\n", + " [210, '07:04', '07:44', 424, 464, 40],\n", + " [211, '07:05', '07:52', 425, 472, 47],\n", + " [212, '07:05', '08:05', 425, 485, 60],\n", + " [213, '07:05', '07:46', 425, 466, 41],\n", + " [214, '07:06', '07:51', 426, 471, 45],\n", + " [215, '07:07', '08:08', 427, 488, 61],\n", + " [216, '07:07', '07:52', 427, 472, 45],\n", + " [217, '07:07', '08:16', 427, 496, 69],\n", + " [218, '07:07', '07:27', 427, 447, 20],\n", + " [219, '07:09', '07:50', 429, 470, 41],\n", + " [220, '07:09', '08:40', 429, 520, 91],\n", + " [221, '07:09', '08:03', 429, 483, 54],\n", + " [222, '07:10', '07:20', 430, 440, 10],\n", + " [223, '07:11', '08:36', 431, 516, 85],\n", + " [224, '07:12', '08:00', 432, 480, 48],\n", + " [225, '07:12', '07:47', 432, 467, 35],\n", + " [226, '07:13', '07:54', 433, 474, 41],\n", + " [227, '07:13', '07:38', 433, 458, 25],\n", + " [228, '07:14', '07:59', 434, 479, 45],\n", + " [229, '07:16', '08:50', 436, 530, 94],\n", + " [230, '07:16', '07:28', 436, 448, 12],\n", + " [231, '07:17', '07:35', 437, 455, 18],\n", + " [232, '07:17', '07:58', 437, 478, 41],\n", + " [233, '07:18', '08:06', 438, 486, 48],\n", + " [234, '07:18', '08:44', 438, 524, 86],\n", + " [235, '07:19', '08:13', 439, 493, 54],\n", + " [236, '07:20', '08:02', 440, 482, 42],\n", + " [237, '07:20', '08:07', 440, 487, 47],\n", + " [238, '07:20', '07:30', 440, 450, 10],\n", + " [239, '07:20', '07:57', 440, 477, 37],\n", + " [240, '07:20', '07:36', 440, 456, 16],\n", + " [241, '07:21', '07:48', 441, 468, 27],\n", + " [242, '07:22', '08:06', 442, 486, 44],\n", + " [243, '07:22', '08:25', 442, 505, 63],\n", + " [244, '07:24', '08:27', 444, 507, 63],\n", + " [245, '07:24', '08:05', 444, 485, 41],\n", + " [246, '07:26', '08:23', 446, 503, 57],\n", + " [247, '07:26', '08:52', 446, 532, 86],\n", + " [248, '07:27', '08:07', 447, 487, 40],\n", + " [249, '07:27', '07:42', 447, 462, 15],\n", + " [250, '07:27', '08:15', 447, 495, 48],\n", + " [251, '07:28', '07:53', 448, 473, 25],\n", + " [252, '07:28', '08:09', 448, 489, 41],\n", + " [253, '07:28', '07:38', 448, 458, 10],\n", + " [254, '07:30', '08:35', 450, 515, 65],\n", + " [255, '07:31', '07:43', 451, 463, 12],\n", + " [256, '07:32', '08:13', 452, 493, 41],\n", + " [257, '07:34', '09:00', 454, 540, 86],\n", + " [258, '07:34', '08:33', 454, 513, 59],\n", + " [259, '07:34', '09:04', 454, 544, 90],\n", + " [260, '07:35', '08:22', 455, 502, 47],\n", + " [261, '07:35', '07:45', 455, 465, 10],\n", + " [262, '07:35', '08:16', 455, 496, 41],\n", + " [263, '07:36', '08:17', 456, 497, 41],\n", + " [264, '07:36', '08:36', 456, 516, 60],\n", + " [265, '07:37', '07:50', 457, 470, 13],\n", + " [266, '07:40', '07:56', 460, 476, 16],\n", + " [267, '07:40', '08:20', 460, 500, 40],\n", + " [268, '07:40', '08:45', 460, 525, 65],\n", + " [269, '07:41', '08:39', 461, 519, 58],\n", + " [270, '07:41', '07:51', 461, 471, 10],\n", + " [271, '07:42', '08:30', 462, 510, 48],\n", + " [272, '07:42', '08:21', 462, 501, 39],\n", + " [273, '07:43', '08:08', 463, 488, 25],\n", + " [274, '07:43', '08:24', 463, 504, 41],\n", + " [275, '07:44', '09:10', 464, 550, 86],\n", + " [276, '07:44', '08:43', 464, 523, 59],\n", + " [277, '07:46', '08:28', 466, 508, 42],\n", + " [278, '07:46', '07:58', 466, 478, 12],\n", + " [279, '07:47', '08:00', 467, 480, 13],\n", + " [280, '07:48', '09:14', 468, 554, 86],\n", + " [281, '07:49', '08:32', 469, 512, 43],\n", + " [282, '07:50', '08:55', 470, 535, 65],\n", + " [283, '07:50', '08:00', 470, 480, 10],\n", + " [284, '07:50', '08:37', 470, 517, 47],\n", + " [285, '07:50', '08:26', 470, 506, 36],\n", + " [286, '07:51', '08:18', 471, 498, 27],\n", + " [287, '07:52', '08:21', 472, 501, 29],\n", + " [288, '07:53', '08:35', 473, 515, 42],\n", + " [289, '07:54', '09:19', 474, 559, 85],\n", + " [290, '07:55', '08:53', 475, 533, 58],\n", + " [291, '07:56', '08:54', 476, 534, 58],\n", + " [292, '07:57', '08:39', 477, 519, 42],\n", + " [293, '07:57', '08:10', 477, 490, 13],\n", + " [294, '07:58', '08:45', 478, 525, 47],\n", + " [295, '07:58', '08:23', 478, 503, 25],\n", + " [296, '08:00', '08:10', 480, 490, 10],\n", + " [297, '08:00', '09:05', 480, 545, 65],\n", + " [298, '08:00', '08:16', 480, 496, 16],\n", + " [299, '08:00', '08:35', 480, 515, 35],\n", + " [300, '08:01', '08:13', 481, 493, 12],\n", + " [301, '08:01', '08:43', 481, 523, 42],\n", + " [302, '08:03', '09:26', 483, 566, 83],\n", + " [303, '08:04', '09:29', 484, 569, 85],\n", + " [304, '08:05', '08:21', 485, 501, 16],\n", + " [305, '08:05', '08:47', 485, 527, 42],\n", + " [306, '08:06', '08:51', 486, 531, 45],\n", + " [307, '08:06', '09:03', 486, 543, 57],\n", + " [308, '08:07', '08:20', 487, 500, 13],\n", + " [309, '08:08', '08:55', 488, 535, 47],\n", + " [310, '08:08', '08:50', 488, 530, 42],\n", + " [311, '08:10', '08:45', 490, 525, 35],\n", + " [312, '08:10', '09:15', 490, 555, 65],\n", + " [313, '08:10', '08:20', 490, 500, 10],\n", + " [314, '08:11', '09:41', 491, 581, 90],\n", + " [315, '08:12', '08:55', 492, 535, 43],\n", + " [316, '08:13', '08:38', 493, 518, 25],\n", + " [317, '08:14', '09:38', 494, 578, 84],\n", + " [318, '08:15', '08:30', 495, 510, 15],\n", + " [319, '08:16', '08:30', 496, 510, 14],\n", + " [320, '08:16', '08:28', 496, 508, 12],\n", + " [321, '08:16', '09:00', 496, 540, 44],\n", + " [322, '08:17', '09:13', 497, 553, 56],\n", + " [323, '08:18', '09:16', 498, 556, 58],\n", + " [324, '08:18', '09:05', 498, 545, 47],\n", + " [325, '08:20', '08:36', 500, 516, 16],\n", + " [326, '08:20', '08:55', 500, 535, 35],\n", + " [327, '08:20', '09:05', 500, 545, 45],\n", + " [328, '08:20', '08:30', 500, 510, 10],\n", + " [329, '08:20', '09:25', 500, 565, 65],\n", + " [330, '08:21', '08:38', 501, 518, 17],\n", + " [331, '08:21', '08:47', 501, 527, 26],\n", + " [332, '08:22', '08:45', 502, 525, 23],\n", + " [333, '08:23', '09:10', 503, 550, 47],\n", + " [334, '08:24', '09:48', 504, 588, 84],\n", + " [335, '08:26', '08:46', 506, 526, 20],\n", + " [336, '08:27', '09:07', 507, 547, 40],\n", + " [337, '08:28', '08:50', 508, 530, 22],\n", + " [338, '08:28', '09:56', 508, 596, 88],\n", + " [339, '08:28', '09:23', 508, 563, 55],\n", + " [340, '08:29', '09:20', 509, 560, 51],\n", + " [341, '08:30', '09:05', 510, 545, 35],\n", + " [342, '08:30', '08:45', 510, 525, 15],\n", + " [343, '08:30', '08:40', 510, 520, 10],\n", + " [344, '08:30', '09:35', 510, 575, 65],\n", + " [345, '08:31', '08:43', 511, 523, 12],\n", + " [346, '08:31', '09:13', 511, 553, 42],\n", + " [347, '08:34', '09:58', 514, 598, 84],\n", + " [348, '08:35', '08:55', 515, 535, 20],\n", + " [349, '08:35', '09:15', 515, 555, 40],\n", + " [350, '08:35', '08:45', 515, 525, 10],\n", + " [351, '08:36', '08:46', 516, 526, 10],\n", + " [352, '08:36', '09:00', 516, 540, 24],\n", + " [353, '08:38', '09:20', 518, 560, 42],\n", + " [354, '08:38', '09:35', 518, 575, 57],\n", + " [355, '08:38', '09:14', 518, 554, 36],\n", + " [356, '08:39', '09:33', 519, 573, 54],\n", + " [357, '08:40', '09:45', 520, 585, 65],\n", + " [358, '08:40', '08:50', 520, 530, 10],\n", + " [359, '08:40', '08:56', 520, 536, 16],\n", + " [360, '08:42', '09:25', 522, 565, 43],\n", + " [361, '08:43', '09:08', 523, 548, 25],\n", + " [362, '08:44', '09:35', 524, 575, 51],\n", + " [363, '08:45', '09:00', 525, 540, 15],\n", + " [364, '08:45', '09:05', 525, 545, 20],\n", + " [365, '08:46', '09:24', 526, 564, 38],\n", + " [366, '08:46', '08:58', 526, 538, 12],\n", + " [367, '08:46', '09:30', 526, 570, 44],\n", + " [368, '08:48', '10:11', 528, 611, 83],\n", + " [369, '08:48', '10:13', 528, 613, 85],\n", + " [370, '08:49', '09:43', 529, 583, 54],\n", + " [371, '08:50', '09:30', 530, 570, 40],\n", + " [372, '08:50', '10:00', 530, 600, 70],\n", + " [373, '08:50', '09:00', 530, 540, 10],\n", + " [374, '08:51', '09:17', 531, 557, 26],\n", + " [375, '08:53', '09:20', 533, 560, 27],\n", + " [376, '08:53', '09:35', 533, 575, 42],\n", + " [377, '08:55', '09:34', 535, 574, 39],\n", + " [378, '08:55', '09:15', 535, 555, 20],\n", + " [379, '08:58', '09:38', 538, 578, 40],\n", + " [380, '08:58', '10:26', 538, 626, 88],\n", + " [381, '08:59', '09:53', 539, 593, 54],\n", + " [382, '08:59', '09:50', 539, 590, 51],\n", + " [383, '09:00', '09:35', 540, 575, 35],\n", + " [384, '09:00', '09:16', 540, 556, 16],\n", + " [385, '09:00', '09:10', 540, 550, 10],\n", + " [386, '09:00', '09:16', 540, 556, 16],\n", + " [387, '09:01', '09:13', 541, 553, 12],\n", + " [388, '09:03', '09:45', 543, 585, 42],\n", + " [389, '09:03', '10:28', 543, 628, 85],\n", + " [390, '09:05', '09:44', 545, 584, 39],\n", + " [391, '09:05', '09:25', 545, 565, 20],\n", + " [392, '09:08', '09:53', 548, 593, 45],\n", + " [393, '09:08', '10:04', 548, 604, 56],\n", + " [394, '09:09', '10:03', 549, 603, 54],\n", + " [395, '09:10', '10:15', 550, 615, 65],\n", + " [396, '09:10', '09:20', 550, 560, 10],\n", + " [397, '09:11', '09:38', 551, 578, 27],\n", + " [398, '09:13', '10:00', 553, 600, 47],\n", + " [399, '09:14', '09:39', 554, 579, 25],\n", + " [400, '09:14', '10:05', 554, 605, 51],\n", + " [401, '09:15', '09:54', 555, 594, 39],\n", + " [402, '09:16', '09:28', 556, 568, 12],\n", + " [403, '09:18', '10:43', 558, 643, 85],\n", + " [404, '09:18', '10:41', 558, 641, 83],\n", + " [405, '09:18', '09:58', 558, 598, 40],\n", + " [406, '09:19', '10:13', 559, 613, 54],\n", + " [407, '09:20', '09:30', 560, 570, 10],\n", + " [408, '09:20', '09:36', 560, 576, 16],\n", + " [409, '09:21', '09:47', 561, 587, 26],\n", + " [410, '09:23', '10:30', 563, 630, 67],\n", + " [411, '09:23', '10:05', 563, 605, 42],\n", + " [412, '09:23', '09:49', 563, 589, 26],\n", + " [413, '09:24', '09:35', 564, 575, 11],\n", + " [414, '09:25', '09:35', 565, 575, 10],\n", + " [415, '09:25', '10:04', 565, 604, 39],\n", + " [416, '09:28', '10:08', 568, 608, 40],\n", + " [417, '09:29', '09:45', 569, 585, 16],\n", + " [418, '09:29', '10:20', 569, 620, 51],\n", + " [419, '09:29', '10:56', 569, 656, 87],\n", + " [420, '09:29', '10:23', 569, 623, 54],\n", + " [421, '09:30', '09:40', 570, 580, 10],\n", + " [422, '09:31', '09:43', 571, 583, 12],\n", + " [423, '09:33', '10:58', 573, 658, 85],\n", + " [424, '09:33', '10:15', 573, 615, 42],\n", + " [425, '09:34', '09:45', 574, 585, 11],\n", + " [426, '09:35', '10:14', 575, 614, 39],\n", + " [427, '09:38', '10:45', 578, 645, 67],\n", + " [428, '09:39', '10:33', 579, 633, 54],\n", + " [429, '09:40', '09:56', 580, 596, 16],\n", + " [430, '09:40', '09:50', 580, 590, 10],\n", + " [431, '09:41', '10:08', 581, 608, 27],\n", + " [432, '09:41', '10:23', 581, 623, 42],\n", + " [433, '09:44', '10:35', 584, 635, 51],\n", + " [434, '09:44', '11:11', 584, 671, 87],\n", + " [435, '09:44', '09:55', 584, 595, 11],\n", + " [436, '09:45', '10:24', 585, 624, 39],\n", + " [437, '09:46', '09:58', 586, 598, 12],\n", + " [438, '09:48', '10:30', 588, 630, 42],\n", + " [439, '09:48', '11:13', 588, 673, 85],\n", + " [440, '09:48', '10:04', 588, 604, 16],\n", + " [441, '09:49', '10:43', 589, 643, 54],\n", + " [442, '09:50', '10:00', 590, 600, 10],\n", + " [443, '09:51', '10:17', 591, 617, 26],\n", + " [444, '09:53', '10:49', 593, 649, 56],\n", + " [445, '09:53', '11:00', 593, 660, 67],\n", + " [446, '09:54', '10:05', 594, 605, 11],\n", + " [447, '09:55', '10:34', 595, 634, 39],\n", + " [448, '09:56', '10:38', 596, 638, 42],\n", + " [449, '09:57', '10:20', 597, 620, 23],\n", + " [450, '09:59', '11:26', 599, 686, 87],\n", + " [451, '09:59', '10:50', 599, 650, 51],\n", + " [452, '09:59', '10:53', 599, 653, 54],\n", + " [453, '10:00', '10:16', 600, 616, 16],\n", + " [454, '10:00', '10:10', 600, 610, 10],\n", + " [455, '10:01', '10:13', 601, 613, 12],\n", + " [456, '10:03', '11:28', 603, 688, 85],\n", + " [457, '10:03', '10:45', 603, 645, 42],\n", + " [458, '10:04', '10:15', 604, 615, 11],\n", + " [459, '10:05', '10:44', 605, 644, 39],\n", + " [460, '10:08', '11:15', 608, 675, 67],\n", + " [461, '10:09', '11:03', 609, 663, 54],\n", + " [462, '10:10', '10:20', 610, 620, 10],\n", + " [463, '10:11', '10:38', 611, 638, 27],\n", + " [464, '10:11', '10:53', 611, 653, 42],\n", + " [465, '10:14', '11:05', 614, 665, 51],\n", + " [466, '10:14', '11:41', 614, 701, 87],\n", + " [467, '10:14', '10:25', 614, 625, 11],\n", + " [468, '10:15', '10:54', 615, 654, 39],\n", + " [469, '10:16', '10:28', 616, 628, 12],\n", + " [470, '10:18', '11:43', 618, 703, 85],\n", + " [471, '10:18', '11:00', 618, 660, 42],\n", + " [472, '10:19', '11:13', 619, 673, 54],\n", + " [473, '10:20', '10:30', 620, 630, 10],\n", + " [474, '10:20', '10:36', 620, 636, 16],\n", + " [475, '10:21', '10:47', 621, 647, 26],\n", + " [476, '10:23', '11:30', 623, 690, 67],\n", + " [477, '10:23', '10:45', 623, 645, 22],\n", + " [478, '10:24', '10:35', 624, 635, 11],\n", + " [479, '10:25', '11:04', 625, 664, 39],\n", + " [480, '10:26', '11:08', 626, 668, 42],\n", + " [481, '10:29', '11:20', 629, 680, 51],\n", + " [482, '10:29', '11:23', 629, 683, 54],\n", + " [483, '10:29', '11:56', 629, 716, 87],\n", + " [484, '10:30', '10:40', 630, 640, 10],\n", + " [485, '10:31', '10:43', 631, 643, 12],\n", + " [486, '10:33', '11:15', 633, 675, 42],\n", + " [487, '10:33', '11:58', 633, 718, 85],\n", + " [488, '10:34', '10:45', 634, 645, 11],\n", + " [489, '10:35', '11:14', 635, 674, 39],\n", + " [490, '10:38', '11:45', 638, 705, 67],\n", + " [491, '10:39', '11:33', 639, 693, 54],\n", + " [492, '10:40', '10:50', 640, 650, 10],\n", + " [493, '10:40', '10:56', 640, 656, 16],\n", + " [494, '10:41', '11:23', 641, 683, 42],\n", + " [495, '10:41', '11:08', 641, 668, 27],\n", + " [496, '10:44', '12:11', 644, 731, 87],\n", + " [497, '10:44', '11:35', 644, 695, 51],\n", + " [498, '10:44', '10:55', 644, 655, 11],\n", + " [499, '10:45', '11:24', 645, 684, 39],\n", + " [500, '10:46', '10:58', 646, 658, 12],\n", + " [501, '10:48', '12:13', 648, 733, 85],\n", + " [502, '10:48', '11:30', 648, 690, 42],\n", + " [503, '10:49', '11:43', 649, 703, 54],\n", + " [504, '10:50', '11:00', 650, 660, 10],\n", + " [505, '10:51', '11:17', 651, 677, 26],\n", + " [506, '10:53', '12:00', 653, 720, 67],\n", + " [507, '10:53', '11:20', 653, 680, 27],\n", + " [508, '10:54', '11:05', 654, 665, 11],\n", + " [509, '10:55', '11:34', 655, 694, 39],\n", + " [510, '10:56', '11:38', 656, 698, 42],\n", + " [511, '10:59', '11:14', 659, 674, 15],\n", + " [512, '10:59', '12:26', 659, 746, 87],\n", + " [513, '10:59', '11:53', 659, 713, 54],\n", + " [514, '10:59', '11:50', 659, 710, 51],\n", + " [515, '11:00', '11:16', 660, 676, 16],\n", + " [516, '11:00', '11:10', 660, 670, 10],\n", + " [517, '11:01', '11:13', 661, 673, 12],\n", + " [518, '11:03', '11:45', 663, 705, 42],\n", + " [519, '11:03', '12:28', 663, 748, 85],\n", + " [520, '11:04', '11:15', 664, 675, 11],\n", + " [521, '11:05', '11:44', 665, 704, 39],\n", + " [522, '11:08', '12:15', 668, 735, 67],\n", + " [523, '11:09', '12:03', 669, 723, 54],\n", + " [524, '11:10', '11:20', 670, 680, 10],\n", + " [525, '11:11', '11:38', 671, 698, 27],\n", + " [526, '11:11', '11:53', 671, 713, 42],\n", + " [527, '11:14', '11:25', 674, 685, 11],\n", + " [528, '11:14', '12:05', 674, 725, 51],\n", + " [529, '11:14', '12:38', 674, 758, 84],\n", + " [530, '11:14', '12:41', 674, 761, 87],\n", + " [531, '11:15', '11:54', 675, 714, 39],\n", + " [532, '11:16', '11:28', 676, 688, 12],\n", + " [533, '11:18', '12:00', 678, 720, 42],\n", + " [534, '11:19', '12:13', 679, 733, 54],\n", + " [535, '11:20', '11:30', 680, 690, 10],\n", + " [536, '11:20', '11:36', 680, 696, 16],\n", + " [537, '11:21', '11:47', 681, 707, 26],\n", + " [538, '11:23', '12:30', 683, 750, 67],\n", + " [539, '11:23', '11:49', 683, 709, 26],\n", + " [540, '11:24', '12:48', 684, 768, 84],\n", + " [541, '11:24', '11:35', 684, 695, 11],\n", + " [542, '11:25', '12:04', 685, 724, 39],\n", + " [543, '11:26', '12:08', 686, 728, 42],\n", + " [544, '11:29', '11:44', 689, 704, 15],\n", + " [545, '11:29', '12:23', 689, 743, 54],\n", + " [546, '11:29', '12:20', 689, 740, 51],\n", + " [547, '11:29', '12:54', 689, 774, 85],\n", + " [548, '11:30', '11:40', 690, 700, 10],\n", + " [549, '11:31', '11:43', 691, 703, 12],\n", + " [550, '11:33', '12:15', 693, 735, 42],\n", + " [551, '11:34', '12:58', 694, 778, 84],\n", + " [552, '11:34', '11:45', 694, 705, 11],\n", + " [553, '11:35', '12:14', 695, 734, 39],\n", + " [554, '11:38', '12:45', 698, 765, 67],\n", + " [555, '11:39', '12:33', 699, 753, 54],\n", + " [556, '11:40', '11:56', 700, 716, 16],\n", + " [557, '11:40', '11:50', 700, 710, 10],\n", + " [558, '11:41', '12:08', 701, 728, 27],\n", + " [559, '11:41', '12:23', 701, 743, 42],\n", + " [560, '11:44', '11:55', 704, 715, 11],\n", + " [561, '11:44', '13:14', 704, 794, 90],\n", + " [562, '11:44', '13:08', 704, 788, 84],\n", + " [563, '11:44', '12:35', 704, 755, 51],\n", + " [564, '11:45', '12:24', 705, 744, 39],\n", + " [565, '11:46', '11:58', 706, 718, 12],\n", + " [566, '11:48', '12:30', 708, 750, 42],\n", + " [567, '11:49', '12:43', 709, 763, 54],\n", + " [568, '11:50', '12:00', 710, 720, 10],\n", + " [569, '11:51', '12:17', 711, 737, 26],\n", + " [570, '11:53', '12:49', 713, 769, 56],\n", + " [571, '11:53', '13:00', 713, 780, 67],\n", + " [572, '11:54', '13:18', 714, 798, 84],\n", + " [573, '11:54', '12:05', 714, 725, 11],\n", + " [574, '11:55', '12:40', 715, 760, 45],\n", + " [575, '11:55', '12:34', 715, 754, 39],\n", + " [576, '11:56', '12:35', 716, 755, 39],\n", + " [577, '11:57', '12:20', 717, 740, 23],\n", + " [578, '11:58', '12:29', 718, 749, 31],\n", + " [579, '11:59', '12:50', 719, 770, 51],\n", + " [580, '11:59', '12:53', 719, 773, 54],\n", + " [581, '11:59', '13:24', 719, 804, 85],\n", + " [582, '11:59', '12:14', 719, 734, 15],\n", + " [583, '12:00', '12:16', 720, 736, 16],\n", + " [584, '12:00', '12:10', 720, 730, 10],\n", + " [585, '12:01', '12:45', 721, 765, 44],\n", + " [586, '12:01', '12:13', 721, 733, 12],\n", + " [587, '12:03', '12:50', 723, 770, 47],\n", + " [588, '12:04', '12:15', 724, 735, 11],\n", + " [589, '12:04', '13:04', 724, 784, 60],\n", + " [590, '12:04', '13:28', 724, 808, 84],\n", + " [591, '12:05', '12:44', 725, 764, 39],\n", + " [592, '12:08', '13:11', 728, 791, 63],\n", + " [593, '12:08', '12:39', 728, 759, 31],\n", + " [594, '12:09', '13:03', 729, 783, 54],\n", + " [595, '12:10', '12:20', 730, 740, 10],\n", + " [596, '12:11', '12:55', 731, 775, 44],\n", + " [597, '12:11', '12:38', 731, 758, 27],\n", + " [598, '12:14', '13:05', 734, 785, 51],\n", + " [599, '12:14', '12:25', 734, 745, 11],\n", + " [600, '12:14', '13:44', 734, 824, 90],\n", + " [601, '12:14', '13:38', 734, 818, 84],\n", + " [602, '12:15', '12:54', 735, 774, 39],\n", + " [603, '12:16', '12:28', 736, 748, 12],\n", + " [604, '12:18', '13:00', 738, 780, 42],\n", + " [605, '12:19', '13:13', 739, 793, 54],\n", + " [606, '12:20', '12:30', 740, 750, 10],\n", + " [607, '12:20', '13:31', 740, 811, 71],\n", + " [608, '12:20', '12:30', 740, 750, 10],\n", + " [609, '12:20', '12:36', 740, 756, 16],\n", + " [610, '12:21', '12:47', 741, 767, 26],\n", + " [611, '12:23', '12:45', 743, 765, 22],\n", + " [612, '12:24', '12:35', 744, 755, 11],\n", + " [613, '12:24', '13:48', 744, 828, 84],\n", + " [614, '12:25', '13:10', 745, 790, 45],\n", + " [615, '12:25', '13:04', 745, 784, 39],\n", + " [616, '12:26', '13:05', 746, 785, 39],\n", + " [617, '12:28', '13:54', 748, 834, 86],\n", + " [618, '12:28', '12:38', 748, 758, 10],\n", + " [619, '12:28', '13:15', 748, 795, 47],\n", + " [620, '12:29', '13:23', 749, 803, 54],\n", + " [621, '12:30', '13:41', 750, 821, 71],\n", + " [622, '12:30', '12:40', 750, 760, 10],\n", + " [623, '12:31', '13:15', 751, 795, 44],\n", + " [624, '12:31', '12:43', 751, 763, 12],\n", + " [625, '12:33', '12:48', 753, 768, 15],\n", + " [626, '12:33', '13:20', 753, 800, 47],\n", + " [627, '12:34', '13:58', 754, 838, 84],\n", + " [628, '12:34', '13:34', 754, 814, 60],\n", + " [629, '12:34', '12:45', 754, 765, 11],\n", + " [630, '12:35', '13:14', 755, 794, 39],\n", + " [631, '12:38', '13:25', 758, 805, 47],\n", + " [632, '12:38', '13:25', 758, 805, 47],\n", + " [633, '12:38', '14:04', 758, 844, 86],\n", + " [634, '12:39', '13:33', 759, 813, 54],\n", + " [635, '12:40', '13:51', 760, 831, 71],\n", + " [636, '12:40', '12:50', 760, 770, 10],\n", + " [637, '12:40', '12:56', 760, 776, 16],\n", + " [638, '12:41', '13:08', 761, 788, 27],\n", + " [639, '12:43', '13:30', 763, 810, 47],\n", + " [640, '12:44', '12:55', 764, 775, 11],\n", + " [641, '12:44', '14:08', 764, 848, 84],\n", + " [642, '12:45', '13:24', 765, 804, 39],\n", + " [643, '12:46', '12:58', 766, 778, 12],\n", + " [644, '12:46', '13:21', 766, 801, 35],\n", + " [645, '12:48', '14:14', 768, 854, 86],\n", + " [646, '12:48', '13:35', 768, 815, 47],\n", + " [647, '12:48', '12:58', 768, 778, 10],\n", + " [648, '12:48', '13:35', 768, 815, 47],\n", + " [649, '12:49', '13:43', 769, 823, 54],\n", + " [650, '12:50', '14:01', 770, 841, 71],\n", + " [651, '12:50', '13:00', 770, 780, 10],\n", + " [652, '12:50', '13:00', 770, 780, 10],\n", + " [653, '12:51', '13:17', 771, 797, 26],\n", + " [654, '12:53', '13:20', 773, 800, 27],\n", + " [655, '12:53', '13:24', 773, 804, 31],\n", + " [656, '12:53', '13:40', 773, 820, 47],\n", + " [657, '12:54', '14:18', 774, 858, 84],\n", + " [658, '12:54', '13:05', 774, 785, 11],\n", + " [659, '12:55', '13:34', 775, 814, 39],\n", + " [660, '12:58', '14:24', 778, 864, 86],\n", + " [661, '12:58', '13:25', 778, 805, 27],\n", + " [662, '12:58', '13:45', 778, 825, 47],\n", + " [663, '12:58', '13:45', 778, 825, 47],\n", + " [664, '12:59', '13:53', 779, 833, 54],\n", + " [665, '13:00', '13:10', 780, 790, 10],\n", + " [666, '13:00', '13:16', 780, 796, 16],\n", + " [667, '13:00', '14:11', 780, 851, 71],\n", + " [668, '13:01', '13:13', 781, 793, 12],\n", + " [669, '13:03', '13:34', 783, 814, 31],\n", + " [670, '13:03', '13:50', 783, 830, 47],\n", + " [671, '13:04', '13:15', 784, 795, 11],\n", + " [672, '13:04', '14:28', 784, 868, 84],\n", + " [673, '13:05', '13:44', 785, 824, 39],\n", + " [674, '13:08', '13:55', 788, 835, 47],\n", + " [675, '13:08', '14:34', 788, 874, 86],\n", + " [676, '13:08', '13:55', 788, 835, 47],\n", + " [677, '13:09', '14:03', 789, 843, 54],\n", + " [678, '13:10', '13:20', 790, 800, 10],\n", + " [679, '13:10', '14:21', 790, 861, 71],\n", + " [680, '13:13', '14:00', 793, 840, 47],\n", + " [681, '13:13', '13:40', 793, 820, 27],\n", + " [682, '13:14', '14:38', 794, 878, 84],\n", + " [683, '13:14', '13:25', 794, 805, 11],\n", + " [684, '13:15', '13:54', 795, 834, 39],\n", + " [685, '13:16', '13:28', 796, 808, 12],\n", + " [686, '13:18', '14:05', 798, 845, 47],\n", + " [687, '13:18', '14:44', 798, 884, 86],\n", + " [688, '13:18', '14:05', 798, 845, 47],\n", + " [689, '13:19', '14:13', 799, 853, 54],\n", + " [690, '13:20', '13:36', 800, 816, 16],\n", + " [691, '13:20', '14:31', 800, 871, 71],\n", + " [692, '13:20', '13:30', 800, 810, 10],\n", + " [693, '13:21', '13:47', 801, 827, 26],\n", + " [694, '13:23', '14:10', 803, 850, 47],\n", + " [695, '13:23', '13:49', 803, 829, 26],\n", + " [696, '13:24', '14:48', 804, 888, 84],\n", + " [697, '13:24', '13:35', 804, 815, 11],\n", + " [698, '13:25', '14:04', 805, 844, 39],\n", + " [699, '13:28', '14:15', 808, 855, 47],\n", + " [700, '13:28', '14:54', 808, 894, 86],\n", + " [701, '13:28', '13:55', 808, 835, 27],\n", + " [702, '13:28', '14:15', 808, 855, 47],\n", + " [703, '13:29', '14:23', 809, 863, 54],\n", + " [704, '13:30', '13:40', 810, 820, 10],\n", + " [705, '13:30', '14:41', 810, 881, 71],\n", + " [706, '13:31', '13:43', 811, 823, 12],\n", + " [707, '13:33', '14:20', 813, 860, 47],\n", + " [708, '13:34', '14:58', 814, 898, 84],\n", + " [709, '13:34', '13:45', 814, 825, 11],\n", + " [710, '13:35', '14:14', 815, 854, 39],\n", + " [711, '13:38', '14:25', 818, 865, 47],\n", + " [712, '13:38', '14:25', 818, 865, 47],\n", + " [713, '13:38', '15:04', 818, 904, 86],\n", + " [714, '13:39', '14:33', 819, 873, 54],\n", + " [715, '13:40', '13:50', 820, 830, 10],\n", + " [716, '13:40', '13:56', 820, 836, 16],\n", + " [717, '13:40', '14:51', 820, 891, 71],\n", + " [718, '13:43', '14:30', 823, 870, 47],\n", + " [719, '13:43', '14:10', 823, 850, 27],\n", + " [720, '13:44', '15:09', 824, 909, 85],\n", + " [721, '13:44', '13:55', 824, 835, 11],\n", + " [722, '13:45', '14:24', 825, 864, 39],\n", + " [723, '13:46', '13:58', 826, 838, 12],\n", + " [724, '13:48', '14:35', 828, 875, 47],\n", + " [725, '13:48', '15:14', 828, 914, 86],\n", + " [726, '13:48', '14:35', 828, 875, 47],\n", + " [727, '13:49', '14:43', 829, 883, 54],\n", + " [728, '13:50', '14:00', 830, 840, 10],\n", + " [729, '13:50', '15:01', 830, 901, 71],\n", + " [730, '13:51', '14:17', 831, 857, 26],\n", + " [731, '13:53', '14:40', 833, 880, 47],\n", + " [732, '13:53', '14:49', 833, 889, 56],\n", + " [733, '13:54', '14:05', 834, 845, 11],\n", + " [734, '13:54', '15:19', 834, 919, 85],\n", + " [735, '13:55', '14:34', 835, 874, 39],\n", + " [736, '13:57', '14:20', 837, 860, 23],\n", + " [737, '13:58', '15:24', 838, 924, 86],\n", + " [738, '13:58', '14:45', 838, 885, 47],\n", + " [739, '13:58', '14:45', 838, 885, 47],\n", + " [740, '13:58', '14:25', 838, 865, 27],\n", + " [741, '13:59', '14:53', 839, 893, 54],\n", + " [742, '14:00', '14:16', 840, 856, 16],\n", + " [743, '14:00', '14:10', 840, 850, 10],\n", + " [744, '14:00', '15:11', 840, 911, 71],\n", + " [745, '14:01', '14:13', 841, 853, 12],\n", + " [746, '14:03', '14:50', 843, 890, 47],\n", + " [747, '14:04', '14:15', 844, 855, 11],\n", + " [748, '14:04', '15:29', 844, 929, 85],\n", + " [749, '14:05', '14:44', 845, 884, 39],\n", + " [750, '14:08', '14:55', 848, 895, 47],\n", + " [751, '14:08', '14:55', 848, 895, 47],\n", + " [752, '14:08', '15:34', 848, 934, 86],\n", + " [753, '14:09', '15:03', 849, 903, 54],\n", + " [754, '14:10', '15:21', 850, 921, 71],\n", + " [755, '14:10', '14:20', 850, 860, 10],\n", + " [756, '14:13', '15:00', 853, 900, 47],\n", + " [757, '14:13', '14:40', 853, 880, 27],\n", + " [758, '14:14', '15:40', 854, 940, 86],\n", + " [759, '14:14', '14:25', 854, 865, 11],\n", + " [760, '14:15', '14:54', 855, 894, 39],\n", + " [761, '14:16', '14:28', 856, 868, 12],\n", + " [762, '14:18', '15:05', 858, 905, 47],\n", + " [763, '14:18', '15:44', 858, 944, 86],\n", + " [764, '14:18', '15:05', 858, 905, 47],\n", + " [765, '14:19', '15:13', 859, 913, 54],\n", + " [766, '14:20', '15:31', 860, 931, 71],\n", + " [767, '14:20', '14:30', 860, 870, 10],\n", + " [768, '14:20', '14:36', 860, 876, 16],\n", + " [769, '14:21', '14:47', 861, 887, 26],\n", + " [770, '14:23', '15:10', 863, 910, 47],\n", + " [771, '14:23', '14:45', 863, 885, 22],\n", + " [772, '14:24', '15:50', 864, 950, 86],\n", + " [773, '14:24', '14:35', 864, 875, 11],\n", + " [774, '14:25', '15:02', 865, 902, 37],\n", + " [775, '14:26', '14:52', 866, 892, 26],\n", + " [776, '14:28', '15:15', 868, 915, 47],\n", + " [777, '14:28', '14:55', 868, 895, 27],\n", + " [778, '14:28', '15:54', 868, 954, 86],\n", + " [779, '14:28', '15:15', 868, 915, 47],\n", + " [780, '14:29', '15:23', 869, 923, 54],\n", + " [781, '14:30', '15:41', 870, 941, 71],\n", + " [782, '14:30', '14:40', 870, 880, 10],\n", + " [783, '14:31', '14:43', 871, 883, 12],\n", + " [784, '14:33', '15:20', 873, 920, 47],\n", + " [785, '14:34', '16:00', 874, 960, 86],\n", + " [786, '14:34', '14:45', 874, 885, 11],\n", + " [787, '14:35', '15:11', 875, 911, 36],\n", + " [788, '14:38', '15:25', 878, 925, 47],\n", + " [789, '14:38', '15:25', 878, 925, 47],\n", + " [790, '14:38', '16:04', 878, 964, 86],\n", + " [791, '14:39', '15:33', 879, 933, 54],\n", + " [792, '14:40', '14:50', 880, 890, 10],\n", + " [793, '14:40', '15:51', 880, 951, 71],\n", + " [794, '14:40', '14:56', 880, 896, 16],\n", + " [795, '14:43', '15:30', 883, 930, 47],\n", + " [796, '14:43', '15:10', 883, 910, 27],\n", + " [797, '14:44', '15:00', 884, 900, 16],\n", + " [798, '14:44', '16:10', 884, 970, 86],\n", + " [799, '14:45', '15:19', 885, 919, 34],\n", + " [800, '14:46', '14:58', 886, 898, 12],\n", + " [801, '14:48', '15:35', 888, 935, 47],\n", + " [802, '14:48', '15:35', 888, 935, 47],\n", + " [803, '14:48', '17:04', 888, 1024, 136],\n", + " [804, '14:49', '15:43', 889, 943, 54],\n", + " [805, '14:50', '16:01', 890, 961, 71],\n", + " [806, '14:50', '15:00', 890, 900, 10],\n", + " [807, '14:51', '15:17', 891, 917, 26],\n", + " [808, '14:52', '15:27', 892, 927, 35],\n", + " [809, '14:52', '15:21', 892, 921, 29],\n", + " [810, '14:53', '15:40', 893, 940, 47],\n", + " [811, '14:54', '15:08', 894, 908, 14],\n", + " [812, '14:54', '16:20', 894, 980, 86],\n", + " [813, '14:58', '16:24', 898, 984, 86],\n", + " [814, '14:58', '15:45', 898, 945, 47],\n", + " [815, '14:58', '15:25', 898, 925, 27],\n", + " [816, '14:58', '15:45', 898, 945, 47],\n", + " [817, '14:59', '15:53', 899, 953, 54],\n", + " [818, '15:00', '15:10', 900, 910, 10],\n", + " [819, '15:00', '15:35', 900, 935, 35],\n", + " [820, '15:00', '16:11', 900, 971, 71],\n", + " [821, '15:00', '15:16', 900, 916, 16],\n", + " [822, '15:01', '15:13', 901, 913, 12],\n", + " [823, '15:02', '15:16', 902, 916, 14],\n", + " [824, '15:03', '15:50', 903, 950, 47],\n", + " [825, '15:04', '16:30', 904, 990, 86],\n", + " [826, '15:08', '16:34', 908, 994, 86],\n", + " [827, '15:08', '15:55', 908, 955, 47],\n", + " [828, '15:08', '15:55', 908, 955, 47],\n", + " [829, '15:08', '15:45', 908, 945, 37],\n", + " [830, '15:09', '16:14', 909, 974, 65],\n", + " [831, '15:09', '16:03', 909, 963, 54],\n", + " [832, '15:10', '16:21', 910, 981, 71],\n", + " [833, '15:10', '15:20', 910, 920, 10],\n", + " [834, '15:11', '15:24', 911, 924, 13],\n", + " [835, '15:12', '15:36', 912, 936, 24],\n", + " [836, '15:13', '16:00', 913, 960, 47],\n", + " [837, '15:13', '15:40', 913, 940, 27],\n", + " [838, '15:14', '16:40', 914, 1000, 86],\n", + " [839, '15:16', '15:28', 916, 928, 12],\n", + " [840, '15:16', '15:55', 916, 955, 39],\n", + " [841, '15:18', '16:05', 918, 965, 47],\n", + " [842, '15:18', '16:44', 918, 1004, 86],\n", + " [843, '15:18', '16:05', 918, 965, 47],\n", + " [844, '15:19', '16:13', 919, 973, 54],\n", + " [845, '15:19', '15:34', 919, 934, 15],\n", + " [846, '15:20', '15:30', 920, 930, 10],\n", + " [847, '15:20', '16:31', 920, 991, 71],\n", + " [848, '15:20', '15:36', 920, 936, 16],\n", + " [849, '15:21', '15:47', 921, 947, 26],\n", + " [850, '15:21', '16:06', 921, 966, 45],\n", + " [851, '15:23', '16:10', 923, 970, 47],\n", + " [852, '15:24', '16:50', 924, 1010, 86],\n", + " [853, '15:24', '16:05', 924, 965, 41],\n", + " [854, '15:27', '15:51', 927, 951, 24],\n", + " [855, '15:27', '15:44', 927, 944, 17],\n", + " [856, '15:28', '16:15', 928, 975, 47],\n", + " [857, '15:28', '16:54', 928, 1014, 86],\n", + " [858, '15:28', '16:15', 928, 975, 47],\n", + " [859, '15:28', '15:55', 928, 955, 27],\n", + " [860, '15:29', '16:23', 929, 983, 54],\n", + " [861, '15:30', '16:41', 930, 1001, 71],\n", + " [862, '15:30', '15:40', 930, 940, 10],\n", + " [863, '15:31', '15:43', 931, 943, 12],\n", + " [864, '15:33', '16:20', 933, 980, 47],\n", + " [865, '15:34', '17:00', 934, 1020, 86],\n", + " [866, '15:34', '16:15', 934, 975, 41],\n", + " [867, '15:35', '15:54', 935, 954, 19],\n", + " [868, '15:36', '16:21', 936, 981, 45],\n", + " [869, '15:38', '16:25', 938, 985, 47],\n", + " [870, '15:38', '16:25', 938, 985, 47],\n", + " [871, '15:38', '16:39', 938, 999, 61],\n", + " [872, '15:39', '16:33', 939, 993, 54],\n", + " [873, '15:40', '15:50', 940, 950, 10],\n", + " [874, '15:40', '16:51', 940, 1011, 71],\n", + " [875, '15:40', '15:56', 940, 956, 16],\n", + " [876, '15:43', '16:10', 943, 970, 27],\n", + " [877, '15:43', '16:30', 943, 990, 47],\n", + " [878, '15:44', '17:10', 944, 1030, 86],\n", + " [879, '15:44', '16:25', 944, 985, 41],\n", + " [880, '15:45', '16:04', 945, 964, 19],\n", + " [881, '15:46', '15:58', 946, 958, 12],\n", + " [882, '15:48', '16:35', 948, 995, 47],\n", + " [883, '15:48', '16:35', 948, 995, 47],\n", + " [884, '15:48', '17:14', 948, 1034, 86],\n", + " [885, '15:49', '16:43', 949, 1003, 54],\n", + " [886, '15:50', '16:00', 950, 960, 10],\n", + " [887, '15:50', '17:01', 950, 1021, 71],\n", + " [888, '15:51', '16:18', 951, 978, 27],\n", + " [889, '15:52', '16:36', 952, 996, 44],\n", + " [890, '15:53', '16:40', 953, 1000, 47],\n", + " [891, '15:54', '17:20', 954, 1040, 86],\n", + " [892, '15:54', '16:35', 954, 995, 41],\n", + " [893, '15:55', '16:14', 955, 974, 19],\n", + " [894, '15:58', '16:25', 958, 985, 27],\n", + " [895, '15:58', '16:45', 958, 1005, 47],\n", + " [896, '15:58', '16:45', 958, 1005, 47],\n", + " [897, '15:58', '17:24', 958, 1044, 86],\n", + " [898, '15:59', '17:11', 959, 1031, 72],\n", + " [899, '15:59', '16:53', 959, 1013, 54],\n", + " [900, '16:00', '16:10', 960, 970, 10],\n", + " [901, '16:00', '16:16', 960, 976, 16],\n", + " [902, '16:01', '16:13', 961, 973, 12],\n", + " [903, '16:03', '16:50', 963, 1010, 47],\n", + " [904, '16:04', '17:30', 964, 1050, 86],\n", + " [905, '16:04', '16:45', 964, 1005, 41],\n", + " [906, '16:05', '16:24', 965, 984, 19],\n", + " [907, '16:06', '16:51', 966, 1011, 45],\n", + " [908, '16:08', '16:55', 968, 1015, 47],\n", + " [909, '16:08', '17:34', 968, 1054, 86],\n", + " [910, '16:08', '16:55', 968, 1015, 47],\n", + " [911, '16:09', '17:03', 969, 1023, 54],\n", + " [912, '16:09', '17:21', 969, 1041, 72],\n", + " [913, '16:10', '16:20', 970, 980, 10],\n", + " [914, '16:13', '16:40', 973, 1000, 27],\n", + " [915, '16:13', '17:00', 973, 1020, 47],\n", + " [916, '16:14', '16:55', 974, 1015, 41],\n", + " [917, '16:14', '17:40', 974, 1060, 86],\n", + " [918, '16:15', '16:34', 975, 994, 19],\n", + " [919, '16:16', '16:28', 976, 988, 12],\n", + " [920, '16:18', '17:05', 978, 1025, 47],\n", + " [921, '16:18', '17:05', 978, 1025, 47],\n", + " [922, '16:18', '17:44', 978, 1064, 86],\n", + " [923, '16:19', '17:31', 979, 1051, 72],\n", + " [924, '16:19', '17:13', 979, 1033, 54],\n", + " [925, '16:20', '16:30', 980, 990, 10],\n", + " [926, '16:20', '16:36', 980, 996, 16],\n", + " [927, '16:21', '16:48', 981, 1008, 27],\n", + " [928, '16:22', '17:06', 982, 1026, 44],\n", + " [929, '16:23', '17:10', 983, 1030, 47],\n", + " [930, '16:24', '17:05', 984, 1025, 41],\n", + " [931, '16:24', '17:50', 984, 1070, 86],\n", + " [932, '16:25', '16:44', 985, 1004, 19],\n", + " [933, '16:28', '17:15', 988, 1035, 47],\n", + " [934, '16:28', '17:15', 988, 1035, 47],\n", + " [935, '16:28', '16:55', 988, 1015, 27],\n", + " [936, '16:28', '17:54', 988, 1074, 86],\n", + " [937, '16:29', '17:23', 989, 1043, 54],\n", + " [938, '16:29', '17:41', 989, 1061, 72],\n", + " [939, '16:30', '16:40', 990, 1000, 10],\n", + " [940, '16:31', '16:43', 991, 1003, 12],\n", + " [941, '16:33', '17:20', 993, 1040, 47],\n", + " [942, '16:34', '17:15', 994, 1035, 41],\n", + " [943, '16:34', '18:00', 994, 1080, 86],\n", + " [944, '16:35', '16:54', 995, 1014, 19],\n", + " [945, '16:36', '17:21', 996, 1041, 45],\n", + " [946, '16:38', '17:25', 998, 1045, 47],\n", + " [947, '16:38', '17:25', 998, 1045, 47],\n", + " [948, '16:38', '18:04', 998, 1084, 86],\n", + " [949, '16:39', '17:33', 999, 1053, 54],\n", + " [950, '16:39', '17:51', 999, 1071, 72],\n", + " [951, '16:40', '16:56', 1000, 1016, 16],\n", + " [952, '16:40', '16:50', 1000, 1010, 10],\n", + " [953, '16:43', '17:10', 1003, 1030, 27],\n", + " [954, '16:43', '17:30', 1003, 1050, 47],\n", + " [955, '16:44', '17:25', 1004, 1045, 41],\n", + " [956, '16:44', '18:10', 1004, 1090, 86],\n", + " [957, '16:45', '17:04', 1005, 1024, 19],\n", + " [958, '16:46', '16:58', 1006, 1018, 12],\n", + " [959, '16:48', '18:14', 1008, 1094, 86],\n", + " [960, '16:48', '17:35', 1008, 1055, 47],\n", + " [961, '16:48', '17:35', 1008, 1055, 47],\n", + " [962, '16:49', '18:01', 1009, 1081, 72],\n", + " [963, '16:49', '17:43', 1009, 1063, 54],\n", + " [964, '16:50', '17:00', 1010, 1020, 10],\n", + " [965, '16:51', '17:18', 1011, 1038, 27],\n", + " [966, '16:52', '17:36', 1012, 1056, 44],\n", + " [967, '16:53', '17:40', 1013, 1060, 47],\n", + " [968, '16:54', '18:20', 1014, 1100, 86],\n", + " [969, '16:54', '17:35', 1014, 1055, 41],\n", + " [970, '16:55', '17:14', 1015, 1034, 19],\n", + " [971, '16:58', '17:25', 1018, 1045, 27],\n", + " [972, '16:58', '17:45', 1018, 1065, 47],\n", + " [973, '16:58', '17:45', 1018, 1065, 47],\n", + " [974, '16:58', '18:24', 1018, 1104, 86],\n", + " [975, '16:59', '18:11', 1019, 1091, 72],\n", + " [976, '16:59', '17:53', 1019, 1073, 54],\n", + " [977, '17:00', '17:16', 1020, 1036, 16],\n", + " [978, '17:00', '17:10', 1020, 1030, 10],\n", + " [979, '17:01', '17:13', 1021, 1033, 12],\n", + " [980, '17:03', '17:50', 1023, 1070, 47],\n", + " [981, '17:04', '18:30', 1024, 1110, 86],\n", + " [982, '17:04', '17:45', 1024, 1065, 41],\n", + " [983, '17:05', '17:24', 1025, 1044, 19],\n", + " [984, '17:06', '17:51', 1026, 1071, 45],\n", + " [985, '17:08', '17:55', 1028, 1075, 47],\n", + " [986, '17:08', '17:55', 1028, 1075, 47],\n", + " [987, '17:08', '18:34', 1028, 1114, 86],\n", + " [988, '17:09', '18:03', 1029, 1083, 54],\n", + " [989, '17:09', '18:21', 1029, 1101, 72],\n", + " [990, '17:10', '17:20', 1030, 1040, 10],\n", + " [991, '17:13', '17:40', 1033, 1060, 27],\n", + " [992, '17:13', '18:00', 1033, 1080, 47],\n", + " [993, '17:14', '17:55', 1034, 1075, 41],\n", + " [994, '17:14', '18:40', 1034, 1120, 86],\n", + " [995, '17:15', '17:34', 1035, 1054, 19],\n", + " [996, '17:16', '17:28', 1036, 1048, 12],\n", + " [997, '17:18', '18:05', 1038, 1085, 47],\n", + " [998, '17:18', '18:05', 1038, 1085, 47],\n", + " [999, '17:18', '18:44', 1038, 1124, 86],\n", + " [1000, '17:19', '18:31', 1039, 1111, 72],\n", + " [1001, '17:19', '18:13', 1039, 1093, 54],\n", + " [1002, '17:20', '17:36', 1040, 1056, 16],\n", + " [1003, '17:20', '17:30', 1040, 1050, 10],\n", + " [1004, '17:21', '17:47', 1041, 1067, 26],\n", + " [1005, '17:22', '18:06', 1042, 1086, 44],\n", + " [1006, '17:23', '18:10', 1043, 1090, 47],\n", + " [1007, '17:24', '18:50', 1044, 1130, 86],\n", + " [1008, '17:24', '18:05', 1044, 1085, 41],\n", + " [1009, '17:25', '17:44', 1045, 1064, 19],\n", + " [1010, '17:28', '17:55', 1048, 1075, 27],\n", + " [1011, '17:28', '18:15', 1048, 1095, 47],\n", + " [1012, '17:28', '18:15', 1048, 1095, 47],\n", + " [1013, '17:28', '18:54', 1048, 1134, 86],\n", + " [1014, '17:29', '18:41', 1049, 1121, 72],\n", + " [1015, '17:29', '18:23', 1049, 1103, 54],\n", + " [1016, '17:30', '17:40', 1050, 1060, 10],\n", + " [1017, '17:31', '17:43', 1051, 1063, 12],\n", + " [1018, '17:33', '18:20', 1053, 1100, 47],\n", + " [1019, '17:34', '18:15', 1054, 1095, 41],\n", + " [1020, '17:34', '19:00', 1054, 1140, 86],\n", + " [1021, '17:35', '17:54', 1055, 1074, 19],\n", + " [1022, '17:36', '18:21', 1056, 1101, 45],\n", + " [1023, '17:38', '18:25', 1058, 1105, 47],\n", + " [1024, '17:38', '19:04', 1058, 1144, 86],\n", + " [1025, '17:38', '18:25', 1058, 1105, 47],\n", + " [1026, '17:39', '18:51', 1059, 1131, 72],\n", + " [1027, '17:39', '18:33', 1059, 1113, 54],\n", + " [1028, '17:40', '17:56', 1060, 1076, 16],\n", + " [1029, '17:40', '17:50', 1060, 1070, 10],\n", + " [1030, '17:43', '18:10', 1063, 1090, 27],\n", + " [1031, '17:43', '18:30', 1063, 1110, 47],\n", + " [1032, '17:44', '18:25', 1064, 1105, 41],\n", + " [1033, '17:44', '19:14', 1064, 1154, 90],\n", + " [1034, '17:45', '18:04', 1065, 1084, 19],\n", + " [1035, '17:46', '17:58', 1066, 1078, 12],\n", + " [1036, '17:48', '18:35', 1068, 1115, 47],\n", + " [1037, '17:48', '18:35', 1068, 1115, 47],\n", + " [1038, '17:48', '19:14', 1068, 1154, 86],\n", + " [1039, '17:49', '19:01', 1069, 1141, 72],\n", + " [1040, '17:49', '18:43', 1069, 1123, 54],\n", + " [1041, '17:50', '18:00', 1070, 1080, 10],\n", + " [1042, '17:51', '18:17', 1071, 1097, 26],\n", + " [1043, '17:52', '18:36', 1072, 1116, 44],\n", + " [1044, '17:53', '18:40', 1073, 1120, 47],\n", + " [1045, '17:54', '18:35', 1074, 1115, 41],\n", + " [1046, '17:54', '18:57', 1074, 1137, 63],\n", + " [1047, '17:55', '18:14', 1075, 1094, 19],\n", + " [1048, '17:58', '18:45', 1078, 1125, 47],\n", + " [1049, '17:58', '18:45', 1078, 1125, 47],\n", + " [1050, '17:58', '18:25', 1078, 1105, 27],\n", + " [1051, '17:58', '19:26', 1078, 1166, 88],\n", + " [1052, '17:59', '18:53', 1079, 1133, 54],\n", + " [1053, '18:00', '19:11', 1080, 1151, 71],\n", + " [1054, '18:00', '18:10', 1080, 1090, 10],\n", + " [1055, '18:00', '18:16', 1080, 1096, 16],\n", + " [1056, '18:01', '18:13', 1081, 1093, 12],\n", + " [1057, '18:03', '18:50', 1083, 1130, 47],\n", + " [1058, '18:04', '18:45', 1084, 1125, 41],\n", + " [1059, '18:04', '19:29', 1084, 1169, 85],\n", + " [1060, '18:05', '18:24', 1085, 1104, 19],\n", + " [1061, '18:06', '18:51', 1086, 1131, 45],\n", + " [1062, '18:08', '18:55', 1088, 1135, 47],\n", + " [1063, '18:08', '19:06', 1088, 1146, 58],\n", + " [1064, '18:08', '18:55', 1088, 1135, 47],\n", + " [1065, '18:09', '19:03', 1089, 1143, 54],\n", + " [1066, '18:10', '18:20', 1090, 1100, 10],\n", + " [1067, '18:10', '19:21', 1090, 1161, 71],\n", + " [1068, '18:13', '19:00', 1093, 1140, 47],\n", + " [1069, '18:13', '18:40', 1093, 1120, 27],\n", + " [1070, '18:14', '19:43', 1094, 1183, 89],\n", + " [1071, '18:14', '18:55', 1094, 1135, 41],\n", + " [1072, '18:15', '18:34', 1095, 1114, 19],\n", + " [1073, '18:16', '18:28', 1096, 1108, 12],\n", + " [1074, '18:17', '18:27', 1097, 1107, 10],\n", + " [1075, '18:18', '19:41', 1098, 1181, 83],\n", + " [1076, '18:18', '18:58', 1098, 1138, 40],\n", + " [1077, '18:18', '19:05', 1098, 1145, 47],\n", + " [1078, '18:19', '19:13', 1099, 1153, 54],\n", + " [1079, '18:20', '19:31', 1100, 1171, 71],\n", + " [1080, '18:20', '18:36', 1100, 1116, 16],\n", + " [1081, '18:20', '18:30', 1100, 1110, 10],\n", + " [1082, '18:22', '19:05', 1102, 1145, 43],\n", + " [1083, '18:23', '19:05', 1103, 1145, 42],\n", + " [1084, '18:24', '19:27', 1104, 1167, 63],\n", + " [1085, '18:24', '19:05', 1104, 1145, 41],\n", + " [1086, '18:25', '18:44', 1105, 1124, 19],\n", + " [1087, '18:28', '19:25', 1108, 1165, 57],\n", + " [1088, '18:28', '18:55', 1108, 1135, 27],\n", + " [1089, '18:28', '19:08', 1108, 1148, 40],\n", + " [1090, '18:28', '19:15', 1108, 1155, 47],\n", + " [1091, '18:29', '19:23', 1109, 1163, 54],\n", + " [1092, '18:30', '19:05', 1110, 1145, 35],\n", + " [1093, '18:30', '18:40', 1110, 1120, 10],\n", + " [1094, '18:31', '18:43', 1111, 1123, 12],\n", + " [1095, '18:33', '19:15', 1113, 1155, 42],\n", + " [1096, '18:34', '19:58', 1114, 1198, 84],\n", + " [1097, '18:34', '19:14', 1114, 1154, 40],\n", + " [1098, '18:35', '18:55', 1115, 1135, 20],\n", + " [1099, '18:36', '19:20', 1116, 1160, 44],\n", + " [1100, '18:38', '19:25', 1118, 1165, 47],\n", + " [1101, '18:38', '19:23', 1118, 1163, 45],\n", + " [1102, '18:38', '19:56', 1118, 1196, 78],\n", + " [1103, '18:39', '19:33', 1119, 1173, 54],\n", + " [1104, '18:40', '18:50', 1120, 1130, 10],\n", + " [1105, '18:40', '19:45', 1120, 1185, 65],\n", + " [1106, '18:40', '18:56', 1120, 1136, 16],\n", + " [1107, '18:43', '19:10', 1123, 1150, 27],\n", + " [1108, '18:43', '19:30', 1123, 1170, 47],\n", + " [1109, '18:44', '19:24', 1124, 1164, 40],\n", + " [1110, '18:45', '19:05', 1125, 1145, 20],\n", + " [1111, '18:46', '18:58', 1126, 1138, 12],\n", + " [1112, '18:48', '19:35', 1128, 1175, 47],\n", + " [1113, '18:48', '20:12', 1128, 1212, 84],\n", + " [1114, '18:48', '20:11', 1128, 1211, 83],\n", + " [1115, '18:48', '19:28', 1128, 1168, 40],\n", + " [1116, '18:49', '19:43', 1129, 1183, 54],\n", + " [1117, '18:50', '19:00', 1130, 1140, 10],\n", + " [1118, '18:51', '19:01', 1131, 1141, 10],\n", + " [1119, '18:53', '19:35', 1133, 1175, 42],\n", + " [1120, '18:53', '19:15', 1133, 1155, 22],\n", + " [1121, '18:53', '20:00', 1133, 1200, 67],\n", + " [1122, '18:55', '19:15', 1135, 1155, 20],\n", + " [1123, '18:55', '19:34', 1135, 1174, 39],\n", + " [1124, '18:58', '19:38', 1138, 1178, 40],\n", + " [1125, '18:59', '19:53', 1139, 1193, 54],\n", + " [1126, '18:59', '19:50', 1139, 1190, 51],\n", + " [1127, '18:59', '19:53', 1139, 1193, 54],\n", + " [1128, '19:00', '19:16', 1140, 1156, 16],\n", + " [1129, '19:00', '19:10', 1140, 1150, 10],\n", + " [1130, '19:00', '19:16', 1140, 1156, 16],\n", + " [1131, '19:01', '19:13', 1141, 1153, 12],\n", + " [1132, '19:03', '20:26', 1143, 1226, 83],\n", + " [1133, '19:03', '19:45', 1143, 1185, 42],\n", + " [1134, '19:05', '19:44', 1145, 1184, 39],\n", + " [1135, '19:05', '19:25', 1145, 1165, 20],\n", + " [1136, '19:08', '20:15', 1148, 1215, 67],\n", + " [1137, '19:08', '19:35', 1148, 1175, 27],\n", + " [1138, '19:09', '19:49', 1149, 1189, 40],\n", + " [1139, '19:09', '20:03', 1149, 1203, 54],\n", + " [1140, '19:10', '19:20', 1150, 1160, 10],\n", + " [1141, '19:10', '19:20', 1150, 1160, 10],\n", + " [1142, '19:11', '19:53', 1151, 1193, 42],\n", + " [1143, '19:14', '20:26', 1154, 1226, 72],\n", + " [1144, '19:14', '19:35', 1154, 1175, 21],\n", + " [1145, '19:14', '19:24', 1154, 1164, 10],\n", + " [1146, '19:14', '20:05', 1154, 1205, 51],\n", + " [1147, '19:15', '19:30', 1155, 1170, 15],\n", + " [1148, '19:15', '19:54', 1155, 1194, 39],\n", + " [1149, '19:18', '20:39', 1158, 1239, 81],\n", + " [1150, '19:18', '20:00', 1158, 1200, 42],\n", + " [1151, '19:19', '20:14', 1159, 1214, 55],\n", + " [1152, '19:20', '19:30', 1160, 1170, 10],\n", + " [1153, '19:20', '19:36', 1160, 1176, 16],\n", + " [1154, '19:21', '19:31', 1161, 1171, 10],\n", + " [1155, '19:23', '20:30', 1163, 1230, 67],\n", + " [1156, '19:23', '19:35', 1163, 1175, 12],\n", + " [1157, '19:24', '19:45', 1164, 1185, 21],\n", + " [1158, '19:24', '19:45', 1164, 1185, 21],\n", + " [1159, '19:25', '20:04', 1165, 1204, 39],\n", + " [1160, '19:26', '20:08', 1166, 1208, 42],\n", + " [1161, '19:29', '20:02', 1169, 1202, 33],\n", + " [1162, '19:29', '20:18', 1169, 1218, 49],\n", + " [1163, '19:29', '20:41', 1169, 1241, 72],\n", + " [1164, '19:30', '19:40', 1170, 1180, 10],\n", + " [1165, '19:33', '20:54', 1173, 1254, 81],\n", + " [1166, '19:33', '20:17', 1173, 1217, 44],\n", + " [1167, '19:34', '19:55', 1174, 1195, 21],\n", + " [1168, '19:35', '20:14', 1175, 1214, 39],\n", + " [1169, '19:38', '20:05', 1178, 1205, 27],\n", + " [1170, '19:38', '20:45', 1178, 1245, 67],\n", + " [1171, '19:39', '20:12', 1179, 1212, 33],\n", + " [1172, '19:40', '19:50', 1180, 1190, 10],\n", + " [1173, '19:40', '19:56', 1180, 1196, 16],\n", + " [1174, '19:41', '20:27', 1181, 1227, 46],\n", + " [1175, '19:43', '19:55', 1183, 1195, 12],\n", + " [1176, '19:44', '20:05', 1184, 1205, 21],\n", + " [1177, '19:44', '20:33', 1184, 1233, 49],\n", + " [1178, '19:44', '21:00', 1184, 1260, 76],\n", + " [1179, '19:45', '20:24', 1185, 1224, 39],\n", + " [1180, '19:48', '20:37', 1188, 1237, 49],\n", + " [1181, '19:48', '21:09', 1188, 1269, 81],\n", + " [1182, '19:50', '20:00', 1190, 1200, 10],\n", + " [1183, '19:52', '20:29', 1192, 1229, 37],\n", + " [1184, '19:53', '20:08', 1193, 1208, 15],\n", + " [1185, '19:53', '21:02', 1193, 1262, 69],\n", + " [1186, '19:53', '20:20', 1193, 1220, 27],\n", + " [1187, '19:54', '20:19', 1194, 1219, 25],\n", + " [1188, '19:55', '20:34', 1195, 1234, 39],\n", + " [1189, '19:56', '20:34', 1196, 1234, 38],\n", + " [1190, '19:59', '20:48', 1199, 1248, 49],\n", + " [1191, '19:59', '21:20', 1199, 1280, 81],\n", + " [1192, '20:00', '20:16', 1200, 1216, 16],\n", + " [1193, '20:00', '20:10', 1200, 1210, 10],\n", + " [1194, '20:03', '20:42', 1203, 1242, 39],\n", + " [1195, '20:03', '21:24', 1203, 1284, 81],\n", + " [1196, '20:04', '20:29', 1204, 1229, 25],\n", + " [1197, '20:05', '20:48', 1205, 1248, 43],\n", + " [1198, '20:07', '20:44', 1207, 1244, 37],\n", + " [1199, '20:08', '20:40', 1208, 1240, 32],\n", + " [1200, '20:08', '20:35', 1208, 1235, 27],\n", + " [1201, '20:10', '20:20', 1210, 1220, 10],\n", + " [1202, '20:10', '20:22', 1210, 1222, 12],\n", + " [1203, '20:11', '20:47', 1211, 1247, 36],\n", + " [1204, '20:14', '21:04', 1214, 1264, 50],\n", + " [1205, '20:14', '21:03', 1214, 1263, 49],\n", + " [1206, '20:17', '21:03', 1217, 1263, 46],\n", + " [1207, '20:18', '21:39', 1218, 1299, 81],\n", + " [1208, '20:20', '20:30', 1220, 1230, 10],\n", + " [1209, '20:20', '20:57', 1220, 1257, 37],\n", + " [1210, '20:20', '20:36', 1220, 1236, 16],\n", + " [1211, '20:22', '20:59', 1222, 1259, 37],\n", + " [1212, '20:22', '20:42', 1222, 1242, 20],\n", + " [1213, '20:24', '20:49', 1224, 1249, 25],\n", + " [1214, '20:27', '21:22', 1227, 1282, 55],\n", + " [1215, '20:29', '21:18', 1229, 1278, 49],\n", + " [1216, '20:30', '21:07', 1230, 1267, 37],\n", + " [1217, '20:30', '20:40', 1230, 1240, 10],\n", + " [1218, '20:30', '20:40', 1230, 1240, 10],\n", + " [1219, '20:30', '21:40', 1230, 1300, 70],\n", + " [1220, '20:32', '21:18', 1232, 1278, 46],\n", + " [1221, '20:35', '21:54', 1235, 1314, 79],\n", + " [1222, '20:37', '21:14', 1237, 1274, 37],\n", + " [1223, '20:38', '21:08', 1238, 1268, 30],\n", + " [1224, '20:40', '20:50', 1240, 1250, 10],\n", + " [1225, '20:40', '21:17', 1240, 1277, 37],\n", + " [1226, '20:40', '20:56', 1240, 1256, 16],\n", + " [1227, '20:44', '21:33', 1244, 1293, 49],\n", + " [1228, '20:47', '21:33', 1247, 1293, 46],\n", + " [1229, '20:47', '21:42', 1247, 1302, 55],\n", + " [1230, '20:50', '21:00', 1250, 1260, 10],\n", + " [1231, '20:50', '22:00', 1250, 1320, 70],\n", + " [1232, '20:50', '22:09', 1250, 1329, 79],\n", + " [1233, '20:50', '21:27', 1250, 1287, 37],\n", + " [1234, '20:52', '21:29', 1252, 1289, 37],\n", + " [1235, '20:53', '21:20', 1253, 1280, 27],\n", + " [1236, '20:56', '21:11', 1256, 1271, 15],\n", + " [1237, '20:59', '21:48', 1259, 1308, 49],\n", + " [1238, '21:00', '21:10', 1260, 1270, 10],\n", + " [1239, '21:00', '21:37', 1260, 1297, 37],\n", + " [1240, '21:02', '21:48', 1262, 1308, 46],\n", + " [1241, '21:05', '22:24', 1265, 1344, 79],\n", + " [1242, '21:07', '21:44', 1267, 1304, 37],\n", + " [1243, '21:07', '22:02', 1267, 1322, 55],\n", + " [1244, '21:08', '21:38', 1268, 1298, 30],\n", + " [1245, '21:10', '22:25', 1270, 1345, 75],\n", + " [1246, '21:10', '21:20', 1270, 1280, 10],\n", + " [1247, '21:10', '21:47', 1270, 1307, 37],\n", + " [1248, '21:14', '22:03', 1274, 1323, 49],\n", + " [1249, '21:17', '22:03', 1277, 1323, 46],\n", + " [1250, '21:20', '22:18', 1280, 1338, 58],\n", + " [1251, '21:20', '21:57', 1280, 1317, 37],\n", + " [1252, '21:20', '21:30', 1280, 1290, 10],\n", + " [1253, '21:22', '21:59', 1282, 1319, 37],\n", + " [1254, '21:24', '21:49', 1284, 1309, 25],\n", + " [1255, '21:27', '22:21', 1287, 1341, 54],\n", + " [1256, '21:30', '22:07', 1290, 1327, 37],\n", + " [1257, '21:30', '22:20', 1290, 1340, 50],\n", + " [1258, '21:30', '21:40', 1290, 1300, 10],\n", + " [1259, '21:32', '22:18', 1292, 1338, 46],\n", + " [1260, '21:32', '22:01', 1292, 1321, 29],\n", + " [1261, '21:35', '22:54', 1295, 1374, 79],\n", + " [1262, '21:37', '22:14', 1297, 1334, 37],\n", + " [1263, '21:39', '21:55', 1299, 1315, 16],\n", + " [1264, '21:40', '22:17', 1300, 1337, 37],\n", + " [1265, '21:40', '21:50', 1300, 1310, 10],\n", + " [1266, '21:41', '22:08', 1301, 1328, 27],\n", + " [1267, '21:47', '22:16', 1307, 1336, 29],\n", + " [1268, '21:47', '22:51', 1307, 1371, 64],\n", + " [1269, '21:47', '22:33', 1307, 1353, 46],\n", + " [1270, '21:48', '22:03', 1308, 1323, 15],\n", + " [1271, '21:50', '22:55', 1310, 1375, 65],\n", + " [1272, '21:50', '22:27', 1310, 1347, 37],\n", + " [1273, '21:50', '22:00', 1310, 1320, 10],\n", + " [1274, '21:52', '22:29', 1312, 1349, 37],\n", + " [1275, '21:53', '22:19', 1313, 1339, 26],\n", + " [1276, '22:00', '22:38', 1320, 1358, 38],\n", + " [1277, '22:00', '22:10', 1320, 1330, 10],\n", + " [1278, '22:02', '22:12', 1322, 1332, 10],\n", + " [1279, '22:02', '22:48', 1322, 1368, 46],\n", + " [1280, '22:04', '22:31', 1324, 1351, 27],\n", + " [1281, '22:05', '23:24', 1325, 1404, 79],\n", + " [1282, '22:07', '22:44', 1327, 1364, 37],\n", + " [1283, '22:07', '22:39', 1327, 1359, 32],\n", + " [1284, '22:09', '22:25', 1329, 1345, 16],\n", + " [1285, '22:10', '23:25', 1330, 1405, 75],\n", + " [1286, '22:13', '22:38', 1333, 1358, 25],\n", + " [1287, '22:13', '22:53', 1333, 1373, 40],\n", + " [1288, '22:17', '22:27', 1337, 1347, 10],\n", + " [1289, '22:17', '23:03', 1337, 1383, 46],\n", + " [1290, '22:19', '22:46', 1339, 1366, 27],\n", + " [1291, '22:22', '22:59', 1342, 1379, 37],\n", + " [1292, '22:24', '22:48', 1344, 1368, 24],\n", + " [1293, '22:27', '22:52', 1347, 1372, 25],\n", + " [1294, '22:27', '23:21', 1347, 1401, 54],\n", + " [1295, '22:28', '23:08', 1348, 1388, 40],\n", + " [1296, '22:30', '23:17', 1350, 1397, 47],\n", + " [1297, '22:32', '22:42', 1352, 1362, 10],\n", + " [1298, '22:32', '23:11', 1352, 1391, 39],\n", + " [1299, '22:34', '23:01', 1354, 1381, 27],\n", + " [1300, '22:35', '23:54', 1355, 1434, 79],\n", + " [1301, '22:37', '23:14', 1357, 1394, 37],\n", + " [1302, '22:43', '23:23', 1363, 1403, 40],\n", + " [1303, '22:43', '23:08', 1363, 1388, 25],\n", + " [1304, '22:47', '23:33', 1367, 1413, 46],\n", + " [1305, '22:47', '22:57', 1367, 1377, 10],\n", + " [1306, '22:49', '23:16', 1369, 1396, 27],\n", + " [1307, '22:52', '23:29', 1372, 1409, 37],\n", + " [1308, '22:53', '23:15', 1373, 1395, 22],\n", + " [1309, '22:55', '23:55', 1375, 1435, 60],\n", + " [1310, '22:57', '23:51', 1377, 1431, 54],\n", + " [1311, '22:58', '23:38', 1378, 1418, 40],\n", + " [1312, '23:02', '23:41', 1382, 1421, 39],\n", + " [1313, '23:02', '23:12', 1382, 1392, 10],\n", + " [1314, '23:04', '23:31', 1384, 1411, 27],\n", + " [1315, '23:05', '00:24', 1385, 1464, 79],\n", + " [1316, '23:07', '23:44', 1387, 1424, 37],\n", + " [1317, '23:13', '23:53', 1393, 1433, 40],\n", + " [1318, '23:13', '23:38', 1393, 1418, 25],\n", + " [1319, '23:17', '00:03', 1397, 1443, 46],\n", + " [1320, '23:17', '23:27', 1397, 1407, 10],\n", + " [1321, '23:19', '23:46', 1399, 1426, 27],\n", + " [1322, '23:22', '23:59', 1402, 1439, 37],\n", + " [1323, '23:25', '00:25', 1405, 1465, 60],\n", + " [1324, '23:27', '00:21', 1407, 1461, 54],\n", + " [1325, '23:28', '00:08', 1408, 1448, 40],\n", + " [1326, '23:32', '23:42', 1412, 1422, 10],\n", + " [1327, '23:34', '00:01', 1414, 1441, 27],\n", + " [1328, '23:35', '01:05', 1415, 1505, 90],\n", + " [1329, '23:37', '00:09', 1417, 1449, 32],\n", + " [1330, '23:43', '00:23', 1423, 1463, 40],\n", + " [1331, '23:43', '00:08', 1423, 1448, 25],\n", + " [1332, '23:46', '00:01', 1426, 1441, 15],\n", + " [1333, '23:47', '23:57', 1427, 1437, 10],\n", + " [1334, '23:47', '00:33', 1427, 1473, 46],\n", + " [1335, '23:52', '00:24', 1432, 1464, 32],\n", + " [1336, '23:55', '00:49', 1435, 1489, 54],\n", + " [1337, '23:57', '00:57', 1437, 1497, 60],\n", + " [1338, '23:58', '00:38', 1438, 1478, 40],\n", + " [1339, '00:02', '00:12', 1442, 1452, 10],\n", + " [1340, '00:07', '00:39', 1447, 1479, 32],\n", + " [1341, '00:13', '00:38', 1453, 1478, 25],\n", + " [1342, '00:13', '00:51', 1453, 1491, 38],\n", + " [1343, '00:15', '01:14', 1455, 1514, 59],\n", + " [1344, '00:17', '01:23', 1457, 1523, 66],\n", + " [1345, '00:23', '00:33', 1463, 1473, 10],\n", + " [1346, '00:24', '00:40', 1464, 1480, 16],\n", + " [1347, '00:25', '01:12', 1465, 1512, 47],\n", + " [1348, '00:28', '01:07', 1468, 1507, 39],\n", + " [1349, '00:33', '01:05', 1473, 1505, 32],\n", + " [1350, '00:43', '01:21', 1483, 1521, 38],\n", + " [1351, '00:44', '00:54', 1484, 1494, 10],\n", + " [1352, '00:47', '01:09', 1487, 1509, 22],\n", + " [1353, '00:47', '01:26', 1487, 1526, 39],\n", + " [1354, '00:54', '01:04', 1494, 1504, 10],\n", + " [1355, '00:57', '01:07', 1497, 1507, 10]\n", + "] # yapf:disable\n", + "\n", + "\n", + "SAMPLE_SHIFTS = SAMPLE_SHIFTS_MEDIUM\n", + "\n", + "\n", + "def bus_driver_scheduling(minimize_drivers, max_num_drivers):\n", + " \"\"\"Optimize the bus driver scheduling problem.\n", + "\n", + " This model has two modes.\n", + "\n", + " If minimize_drivers == True, the objective will be to find the minimal\n", + " number of drivers, independently of the working times of each drivers.\n", + "\n", + " Otherwise, will will create max_num_drivers non optional drivers, and\n", + " minimize the sum of working times of these drivers.\n", + " \"\"\"\n", + " num_shifts = len(SAMPLE_SHIFTS)\n", + "\n", + " # All durations are in minutes.\n", + " max_driving_time = 540 # 8 hours.\n", + " max_driving_time_without_pauses = 240 # 4 hours\n", + " min_pause_after_4h = 30\n", + " min_delay_between_shifts = 2\n", + " max_working_time = 720\n", + " min_working_time = 390 # 6.5 hours\n", + " setup_time = 10\n", + " cleanup_time = 15\n", + "\n", + " # Computed data.\n", + " total_driving_time = sum(shift[5] for shift in SAMPLE_SHIFTS)\n", + " min_num_drivers = int(\n", + " math.ceil(total_driving_time * 1.0 / max_driving_time))\n", + " num_drivers = 2 * min_num_drivers if minimize_drivers else max_num_drivers\n", + " min_start_time = min(shift[3] for shift in SAMPLE_SHIFTS)\n", + " max_end_time = max(shift[4] for shift in SAMPLE_SHIFTS)\n", + "\n", + " print('Bus driver scheduling')\n", + " print(' num shifts =', num_shifts)\n", + " print(' total driving time =', total_driving_time, 'minutes')\n", + " print(' min num drivers =', min_num_drivers)\n", + " print(' num drivers =', num_drivers)\n", + " print(' min start time =', min_start_time)\n", + " print(' max end time =', max_end_time)\n", + "\n", + " model = cp_model.CpModel()\n", + "\n", + " # For each driver and each shift, we store:\n", + " # - the total driving time including this shift\n", + " # - the acrued driving time since the last 30 minute break\n", + " # Special arcs have the following effect:\n", + " # - 'from source to shift' sets the starting time and accumulate the first\n", + " # shift\n", + " # - 'from shift to end' sets the ending time, and fill the driving_times\n", + " # variable\n", + " # Arcs between two shifts have the following impact\n", + " # - add the duration of the shift to the total driving time\n", + " # - reset the accumulated driving time if the distance between the two\n", + " # shifts is more than 30 minutes, add the duration of the shift to the\n", + " # accumulated driving time since the last break otherwise\n", + "\n", + " # Per (driver, node) info (driving time, performed,\n", + " # driving time since break)\n", + " total_driving = {}\n", + " no_break_driving = {}\n", + " performed = {}\n", + " starting_shifts = {}\n", + "\n", + " # Per driver info (start, end, driving times, is working)\n", + " start_times = []\n", + " end_times = []\n", + " driving_times = []\n", + " working_drivers = []\n", + " working_times = []\n", + "\n", + " # Weighted objective\n", + " delay_literals = []\n", + " delay_weights = []\n", + "\n", + " # Used to propagate more between drivers\n", + " shared_incoming_literals = collections.defaultdict(list)\n", + " shared_outgoing_literals = collections.defaultdict(list)\n", + "\n", + " for d in range(num_drivers):\n", + " start_times.append(\n", + " model.NewIntVar(min_start_time - setup_time, max_end_time,\n", + " 'start_%i' % d))\n", + " end_times.append(\n", + " model.NewIntVar(min_start_time, max_end_time + cleanup_time,\n", + " 'end_%i' % d))\n", + " driving_times.append(\n", + " model.NewIntVar(0, max_driving_time, 'driving_%i' % d))\n", + " working_times.append(\n", + " model.NewIntVar(0, max_working_time, 'working_times_%i' % d))\n", + "\n", + " incoming_literals = collections.defaultdict(list)\n", + " outgoing_literals = collections.defaultdict(list)\n", + " outgoing_source_literals = []\n", + " incoming_sink_literals = []\n", + "\n", + " # Create all the shift variables before iterating on the transitions\n", + " # between these shifts.\n", + " for s in range(num_shifts):\n", + " total_driving[d, s] = model.NewIntVar(0, max_driving_time,\n", + " 'dr_%i_%i' % (d, s))\n", + " no_break_driving[d, s] = model.NewIntVar(\n", + " 0, max_driving_time_without_pauses, 'mdr_%i_%i' % (d, s))\n", + " performed[d, s] = model.NewBoolVar('performed_%i_%i' % (d, s))\n", + "\n", + " for s in range(num_shifts):\n", + " shift = SAMPLE_SHIFTS[s]\n", + " duration = shift[5]\n", + "\n", + " # Arc from source to shift.\n", + " # - set the start time of the driver\n", + " # - increase driving time and driving time since break\n", + " source_lit = model.NewBoolVar('%i from source to %i' % (d, s))\n", + " outgoing_source_literals.append(source_lit)\n", + " incoming_literals[s].append(source_lit)\n", + " shared_incoming_literals[s].append(source_lit)\n", + " model.Add(start_times[d] == shift[3] -\n", + " setup_time).OnlyEnforceIf(source_lit)\n", + " model.Add(\n", + " total_driving[d, s] == duration).OnlyEnforceIf(source_lit)\n", + " model.Add(\n", + " no_break_driving[d, s] == duration).OnlyEnforceIf(source_lit)\n", + " starting_shifts[d, s] = source_lit\n", + "\n", + " # Arc from shift to sink\n", + " # - set the end time of the driver\n", + " # - set the driving times of the driver\n", + " sink_lit = model.NewBoolVar('%i from %i to sink' % (d, s))\n", + " outgoing_literals[s].append(sink_lit)\n", + " shared_outgoing_literals[s].append(sink_lit)\n", + " incoming_sink_literals.append(sink_lit)\n", + " model.Add(end_times[d] == shift[4] +\n", + " cleanup_time).OnlyEnforceIf(sink_lit)\n", + " model.Add(driving_times[d] == total_driving[d, s]).OnlyEnforceIf(\n", + " sink_lit)\n", + "\n", + " # Node not performed\n", + " # - set both driving times to 0\n", + " # - add a looping arc on the node\n", + " model.Add(total_driving[d, s] == 0).OnlyEnforceIf(\n", + " performed[d, s].Not())\n", + " model.Add(no_break_driving[d, s] == 0).OnlyEnforceIf(\n", + " performed[d, s].Not())\n", + " incoming_literals[s].append(performed[d, s].Not())\n", + " outgoing_literals[s].append(performed[d, s].Not())\n", + " # Not adding to the shared lists, because, globally, each node will have\n", + " # one incoming literal, and one outgoing literal.\n", + "\n", + " # Node performed:\n", + " # - add upper bound on start_time\n", + " # - add lower bound on end_times\n", + " model.Add(start_times[d] <= shift[3] - setup_time).OnlyEnforceIf(\n", + " performed[d, s])\n", + " model.Add(end_times[d] >= shift[4] + cleanup_time).OnlyEnforceIf(\n", + " performed[d, s])\n", + "\n", + " for o in range(num_shifts):\n", + " other = SAMPLE_SHIFTS[o]\n", + " delay = other[3] - shift[4]\n", + " if delay < min_delay_between_shifts:\n", + " continue\n", + " lit = model.NewBoolVar('%i from %i to %i' % (d, s, o))\n", + "\n", + " # Increase driving time\n", + " model.Add(total_driving[d, o] == total_driving[d, s] +\n", + " other[5]).OnlyEnforceIf(lit)\n", + "\n", + " # Increase no_break_driving or reset it to 0 depending on the delay\n", + " if delay >= min_pause_after_4h:\n", + " model.Add(\n", + " no_break_driving[d, o] == other[5]).OnlyEnforceIf(lit)\n", + " else:\n", + " model.Add(\n", + " no_break_driving[d, o] == no_break_driving[d, s] +\n", + " other[5]).OnlyEnforceIf(lit)\n", + "\n", + " # Add arc\n", + " outgoing_literals[s].append(lit)\n", + " shared_outgoing_literals[s].append(lit)\n", + " incoming_literals[o].append(lit)\n", + " shared_incoming_literals[o].append(lit)\n", + "\n", + " # Cost part\n", + " delay_literals.append(lit)\n", + " delay_weights.append(delay)\n", + "\n", + " model.Add(working_times[d] == end_times[d] - start_times[d])\n", + "\n", + " if minimize_drivers:\n", + " # Driver is not working.\n", + " working = model.NewBoolVar('working_%i' % d)\n", + " model.Add(start_times[d] == min_start_time).OnlyEnforceIf(\n", + " working.Not())\n", + " model.Add(end_times[d] == min_start_time).OnlyEnforceIf(\n", + " working.Not())\n", + " model.Add(driving_times[d] == 0).OnlyEnforceIf(working.Not())\n", + " working_drivers.append(working)\n", + " outgoing_source_literals.append(working.Not())\n", + " incoming_sink_literals.append(working.Not())\n", + " # Conditional working time constraints\n", + " model.Add(\n", + " working_times[d] >= min_working_time).OnlyEnforceIf(working)\n", + " model.Add(working_times[d] == 0).OnlyEnforceIf(working.Not())\n", + " else:\n", + " # Working time constraints\n", + " model.Add(working_times[d] >= min_working_time)\n", + "\n", + " # Create circuit constraint.\n", + " model.Add(sum(outgoing_source_literals) == 1)\n", + " for s in range(num_shifts):\n", + " model.Add(sum(outgoing_literals[s]) == 1)\n", + " model.Add(sum(incoming_literals[s]) == 1)\n", + " model.Add(sum(incoming_sink_literals) == 1)\n", + "\n", + " # Each shift is covered.\n", + " for s in range(num_shifts):\n", + " model.Add(sum(performed[d, s] for d in range(num_drivers)) == 1)\n", + " # Globally, each node has one incoming and one outgoing literal\n", + " model.Add(sum(shared_incoming_literals[s]) == 1)\n", + " model.Add(sum(shared_outgoing_literals[s]) == 1)\n", + "\n", + " # Symmetry breaking\n", + "\n", + " # The first 3 shifts must be performed by 3 different drivers.\n", + " # Let's assign them to the first 3 drivers in sequence\n", + " model.Add(starting_shifts[0, 0] == 1)\n", + " model.Add(starting_shifts[1, 1] == 1)\n", + " model.Add(starting_shifts[2, 2] == 1)\n", + "\n", + " if minimize_drivers:\n", + " # Push non working drivers to the end\n", + " for d in range(num_drivers - 1):\n", + " model.AddImplication(working_drivers[d].Not(),\n", + " working_drivers[d + 1].Not())\n", + "\n", + " # Redundant constraints: sum of driving times = sum of shift driving times\n", + " model.Add(sum(driving_times) == total_driving_time)\n", + " if not minimize_drivers:\n", + " model.Add(\n", + " sum(working_times) == total_driving_time +\n", + " num_drivers * (setup_time + cleanup_time) +\n", + " cp_model.LinearExpr.ScalProd(delay_literals, delay_weights))\n", + "\n", + " if minimize_drivers:\n", + " # Minimize the number of working drivers\n", + " model.Minimize(sum(working_drivers))\n", + " else:\n", + " # Minimize the sum of delays between tasks, which in turns minimize the\n", + " # sum of working times as the total driving time is fixed\n", + " model.Minimize(\n", + " cp_model.LinearExpr.ScalProd(delay_literals, delay_weights))\n", + "\n", + " # Solve model.\n", + " solver = cp_model.CpSolver()\n", + " solver.parameters.log_search_progress = True # not minimize_drivers\n", + " solver.parameters.num_search_workers = 8\n", + " status = solver.Solve(model)\n", + "\n", + " if status != cp_model.OPTIMAL and status != cp_model.FEASIBLE:\n", + " return -1\n", + "\n", + " # Display solution\n", + " if minimize_drivers:\n", + " max_num_drivers = int(solver.ObjectiveValue())\n", + " print('minimal number of drivers =', max_num_drivers)\n", + " return max_num_drivers\n", + "\n", + " for d in range(num_drivers):\n", + " print('Driver %i: ' % (d + 1))\n", + " print(' total driving time =', solver.Value(driving_times[d]))\n", + " print(' working time =',\n", + " solver.Value(working_times[d]) + setup_time + cleanup_time)\n", + "\n", + " first = True\n", + " for s in range(num_shifts):\n", + " shift = SAMPLE_SHIFTS[s]\n", + "\n", + " if not solver.BooleanValue(performed[d, s]):\n", + " continue\n", + "\n", + " # Hack to detect if the waiting time between the last shift and\n", + " # this one exceeds 30 minutes. For this, we look at the\n", + " # no_break_driving which was reinitialized in that case.\n", + " if solver.Value(no_break_driving[d, s]) == shift[5] and not first:\n", + " print(' **break**')\n", + " print(' shift ', shift[0], ':', shift[1], \"-\", shift[2])\n", + " first = False\n", + "\n", + " return int(solver.ObjectiveValue())\n", + "\n", + "\n", + "def optimize_bus_driver_allocation():\n", + " \"\"\"Optimize the bus driver allocation in two passes.\"\"\"\n", + " print('----------- first pass: minimize the number of drivers')\n", + " num_drivers = bus_driver_scheduling(True, -1)\n", + " print('----------- second pass: minimize the sum of working times')\n", + " bus_driver_scheduling(False, num_drivers)\n", + "\n", + "\n", + "optimize_bus_driver_allocation()\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/examples/chemical_balance_lp.ipynb b/examples/notebook/examples/chemical_balance_lp.ipynb new file mode 100644 index 0000000000..575f1cdfae --- /dev/null +++ b/examples/notebook/examples/chemical_balance_lp.ipynb @@ -0,0 +1,108 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010-2018 Google LLC\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\n", + "# We are trying to group items in equal sized groups.\n", + "# Each item has a color and a value. We want the sum of values of each group to\n", + "# be as close to the average as possible.\n", + "# Furthermore, if one color is an a group, at least k items with this color must\n", + "# be in that group.\n", + "\n", + "from __future__ import print_function\n", + "from __future__ import division\n", + "\n", + "from ortools.linear_solver import pywraplp\n", + "\n", + "import math\n", + "\n", + "# Data\n", + "\n", + "max_quantities = [[\"N_Total\", 1944], [\"P2O5\", 1166.4], [\"K2O\", 1822.5],\n", + " [\"CaO\", 1458], [\"MgO\", 486], [\"Fe\", 9.7], [\"B\", 2.4]]\n", + "\n", + "chemical_set = [[\"A\", 0, 0, 510, 540, 0, 0, 0], [\"B\", 110, 0, 0, 0, 160, 0, 0],\n", + " [\"C\", 61, 149, 384, 0, 30, 1,\n", + " 0.2], [\"D\", 148, 70, 245, 0, 15, 1,\n", + " 0.2], [\"E\", 160, 158, 161, 0, 10, 1, 0.2]]\n", + "\n", + "num_products = len(max_quantities)\n", + "all_products = range(num_products)\n", + "\n", + "num_sets = len(chemical_set)\n", + "all_sets = range(num_sets)\n", + "\n", + "# Model\n", + "\n", + "max_set = [\n", + " min(max_quantities[q][1] / chemical_set[s][q + 1] for q in all_products\n", + " if chemical_set[s][q + 1] != 0.0) for s in all_sets\n", + "]\n", + "\n", + "solver = pywraplp.Solver(\"chemical_set_lp\",\n", + " pywraplp.Solver.GLOP_LINEAR_PROGRAMMING)\n", + "\n", + "set_vars = [solver.NumVar(0, max_set[s], \"set_%i\" % s) for s in all_sets]\n", + "\n", + "epsilon = solver.NumVar(0, 1000, \"epsilon\")\n", + "\n", + "for p in all_products:\n", + " solver.Add(\n", + " sum(chemical_set[s][p + 1] * set_vars[s]\n", + " for s in all_sets) <= max_quantities[p][1])\n", + " solver.Add(\n", + " sum(chemical_set[s][p + 1] * set_vars[s]\n", + " for s in all_sets) >= max_quantities[p][1] - epsilon)\n", + "\n", + "solver.Minimize(epsilon)\n", + "\n", + "print((\"Number of variables = %d\" % solver.NumVariables()))\n", + "print((\"Number of constraints = %d\" % solver.NumConstraints()))\n", + "\n", + "result_status = solver.Solve()\n", + "\n", + "# The problem has an optimal solution.\n", + "assert result_status == pywraplp.Solver.OPTIMAL\n", + "\n", + "assert solver.VerifySolution(1e-7, True)\n", + "\n", + "print((\"Problem solved in %f milliseconds\" % solver.wall_time()))\n", + "\n", + "# The objective value of the solution.\n", + "print((\"Optimal objective value = %f\" % solver.Objective().Value()))\n", + "\n", + "for s in all_sets:\n", + " print(\n", + " \" %s = %f\" % (chemical_set[s][0], set_vars[s].solution_value()),\n", + " end=\" \")\n", + " print()\n", + "for p in all_products:\n", + " name = max_quantities[p][0]\n", + " max_quantity = max_quantities[p][1]\n", + " quantity = sum(\n", + " set_vars[s].solution_value() * chemical_set[s][p + 1] for s in all_sets)\n", + " print(\"%s: %f out of %f\" % (name, quantity, max_quantity))\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/chemical_balance_sat.ipynb b/examples/notebook/examples/chemical_balance_sat.ipynb similarity index 50% rename from examples/notebook/chemical_balance_sat.ipynb rename to examples/notebook/examples/chemical_balance_sat.ipynb index d44458a725..7dfa5caac5 100644 --- a/examples/notebook/chemical_balance_sat.ipynb +++ b/examples/notebook/examples/chemical_balance_sat.ipynb @@ -6,7 +6,7 @@ "metadata": {}, "outputs": [], "source": [ - "# Copyright 2010-2017 Google\n", + "# Copyright 2010-2018 Google LLC\n", "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", "# you may not use this file except in compliance with the License.\n", "# You may obtain a copy of the License at\n", @@ -28,26 +28,18 @@ "from __future__ import print_function\n", "from __future__ import division\n", "\n", - "\n", "from ortools.sat.python import cp_model\n", "import math\n", "\n", - "\n", "# Data\n", "\n", - "max_quantities = [ [\"N_Total\", 1944],\n", - " [\"P2O5\", 1166.4],\n", - " [\"K2O\", 1822.5],\n", - " [\"CaO\", 1458],\n", - " [\"MgO\", 486],\n", - " [\"Fe\", 9.7],\n", - " [\"B\", 2.4] ]\n", + "max_quantities = [[\"N_Total\", 1944], [\"P2O5\", 1166.4], [\"K2O\", 1822.5],\n", + " [\"CaO\", 1458], [\"MgO\", 486], [\"Fe\", 9.7], [\"B\", 2.4]]\n", "\n", - "chemical_set = [ [\"A\", 0, 0, 510, 540, 0, 0, 0],\n", - " [\"B\", 110, 0, 0, 0, 160, 0, 0],\n", - " [\"C\", 61, 149, 384, 0, 30, 1, 0.2],\n", - " [\"D\", 148, 70, 245, 0, 15, 1, 0.2],\n", - " [\"E\", 160, 158, 161, 0, 10, 1, 0.2] ]\n", + "chemical_set = [[\"A\", 0, 0, 510, 540, 0, 0, 0], [\"B\", 110, 0, 0, 0, 160, 0, 0],\n", + " [\"C\", 61, 149, 384, 0, 30, 1,\n", + " 0.2], [\"D\", 148, 70, 245, 0, 15, 1,\n", + " 0.2], [\"E\", 160, 158, 161, 0, 10, 1, 0.2]]\n", "\n", "num_products = len(max_quantities)\n", "all_products = range(num_products)\n", @@ -60,46 +52,52 @@ "model = cp_model.CpModel()\n", "\n", "# Scale quantities by 100.\n", - "max_set = [int(math.ceil(min(max_quantities[q][1] * 1000 / chemical_set[s][q + 1]\n", - " for q in all_products\n", - " if chemical_set[s][q + 1] != 0)))\n", - " for s in all_sets]\n", + "max_set = [\n", + " int(\n", + " math.ceil(\n", + " min(max_quantities[q][1] * 1000 / chemical_set[s][q + 1]\n", + " for q in all_products if chemical_set[s][q + 1] != 0)))\n", + " for s in all_sets\n", + "]\n", "\n", - "set_vars = [model.NewIntVar(0, max_set[s], 'set_%i' % s) for s in all_sets]\n", + "set_vars = [model.NewIntVar(0, max_set[s], \"set_%i\" % s) for s in all_sets]\n", "\n", - "epsilon = model.NewIntVar(0, 10000000, 'epsilon')\n", + "epsilon = model.NewIntVar(0, 10000000, \"epsilon\")\n", "\n", "for p in all_products:\n", - " model.Add(sum(int(chemical_set[s][p + 1] * 10) * set_vars[s]\n", - " for s in all_sets) <= int(max_quantities[p][1] * 10000))\n", - " model.Add(sum(int(chemical_set[s][p + 1] * 10) * set_vars[s]\n", - " for s in all_sets) >= int(max_quantities[p][1] * 10000) -\n", - " epsilon)\n", + " model.Add(\n", + " sum(int(chemical_set[s][p + 1] * 10) * set_vars[s]\n", + " for s in all_sets) <= int(max_quantities[p][1] * 10000))\n", + " model.Add(\n", + " sum(int(chemical_set[s][p + 1] * 10) * set_vars[s]\n", + " for s in all_sets) >= int(max_quantities[p][1] * 10000) - epsilon)\n", "\n", "model.Minimize(epsilon)\n", "\n", "# Creates a solver and solves.\n", "solver = cp_model.CpSolver()\n", "status = solver.Solve(model)\n", - "print('Status = %s' % solver.StatusName(status))\n", + "print(\"Status = %s\" % solver.StatusName(status))\n", "# The objective value of the solution.\n", - "print('Optimal objective value = %f' % (solver.ObjectiveValue() / 10000.0))\n", + "print(\"Optimal objective value = %f\" % (solver.ObjectiveValue() / 10000.0))\n", "\n", "for s in all_sets:\n", - " print(' %s = %f' % (chemical_set[s][0], solver.Value(set_vars[s]) / 1000.0),\n", - " end=' ')\n", - " print()\n", + " print(\n", + " \" %s = %f\" % (chemical_set[s][0], solver.Value(set_vars[s]) / 1000.0),\n", + " end=\" \")\n", + " print()\n", "for p in all_products:\n", - " name = max_quantities[p][0]\n", - " max_quantity = max_quantities[p][1]\n", - " quantity = sum(solver.Value(set_vars[s]) / 1000.0 * chemical_set[s][p + 1]\n", - " for s in all_sets)\n", - " print('%s: %f out of %f' % (name, quantity, max_quantity))\n", + " name = max_quantities[p][0]\n", + " max_quantity = max_quantities[p][1]\n", + " quantity = sum(\n", + " solver.Value(set_vars[s]) / 1000.0 * chemical_set[s][p + 1]\n", + " for s in all_sets)\n", + " print(\"%s: %f out of %f\" % (name, quantity, max_quantity))\n", "\n" ] } ], "metadata": {}, "nbformat": 4, - "nbformat_minor": 2 + "nbformat_minor": 4 } diff --git a/examples/notebook/examples/clustering_sat.ipynb b/examples/notebook/examples/clustering_sat.ipynb new file mode 100644 index 0000000000..ccb144b509 --- /dev/null +++ b/examples/notebook/examples/clustering_sat.ipynb @@ -0,0 +1,142 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010-2018 Google LLC\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"Cluster 40 cities in 4 equal groups to minimize sum of crossed distances.\"\"\"\n", + "\n", + "from __future__ import print_function\n", + "from __future__ import division\n", + "\n", + "from ortools.sat.python import cp_model\n", + "\n", + "\n", + "distance_matrix = [\n", + " [0, 10938, 4542, 2835, 29441, 2171, 1611, 9208, 9528, 11111, 16120, 22606, 22127, 20627, 21246, 23387, 16697, 33609, 26184, 24772, 22644, 20655, 30492, 23296, 32979, 18141, 19248, 17129, 17192, 15645, 12658, 11210, 12094, 13175, 18162, 4968, 12308, 10084, 13026, 15056],\n", + " [10938, 0, 6422, 9742, 18988, 12974, 11216, 19715, 19004, 18271, 25070, 31971, 31632, 30571, 31578, 33841, 27315, 43964, 36944, 35689, 33569, 31481, 41360, 33760, 43631, 28730, 29976, 27803, 28076, 26408, 23504, 22025, 22000, 13197, 14936, 15146, 23246, 20956, 23963, 25994],\n", + " [4542, 6422, 0, 3644, 25173, 6552, 5092, 13584, 13372, 13766, 19805, 26537, 26117, 24804, 25590, 27784, 21148, 37981, 30693, 29315, 27148, 25071, 34943, 27472, 37281, 22389, 23592, 21433, 21655, 20011, 17087, 15612, 15872, 11653, 15666, 8842, 16843, 14618, 17563, 19589],\n", + " [2835, 9742, 3644, 0, 28681, 3851, 4341, 11660, 12294, 13912, 18893, 25283, 24777, 23173, 23636, 25696, 18950, 35927, 28233, 26543, 24127, 21864, 31765, 24018, 33904, 19005, 20295, 18105, 18551, 16763, 13958, 12459, 12296, 10370, 15331, 5430, 14044, 12135, 14771, 16743],\n", + " [29441, 18988, 25173, 28681, 0, 31590, 29265, 37173, 35501, 32929, 40239, 47006, 46892, 46542, 48112, 50506, 44539, 60103, 54208, 53557, 51878, 50074, 59849, 52645, 62415, 47544, 48689, 46560, 46567, 45086, 42083, 40648, 40971, 29929, 28493, 34015, 41473, 38935, 42160, 44198],\n", + " [2171, 12974, 6552, 3851, 31590, 0, 3046, 7856, 8864, 11330, 15411, 21597, 21065, 19382, 19791, 21845, 15099, 32076, 24425, 22848, 20600, 18537, 28396, 21125, 30825, 15975, 17101, 14971, 15104, 13503, 10544, 9080, 9983, 13435, 18755, 2947, 10344, 8306, 11069, 13078],\n", + " [1611, 11216, 5092, 4341, 29265, 3046, 0, 8526, 8368, 9573, 14904, 21529, 21085, 19719, 20504, 22713, 16118, 32898, 25728, 24541, 22631, 20839, 30584, 23755, 33278, 18557, 19545, 17490, 17309, 15936, 12881, 11498, 12944, 14711, 19589, 5993, 12227, 9793, 12925, 14967],\n", + " [9208, 19715, 13584, 11660, 37173, 7856, 8526, 0, 3248, 7855, 8245, 13843, 13272, 11526, 12038, 14201, 7599, 24411, 17259, 16387, 15050, 13999, 23134, 17899, 26460, 12894, 13251, 11680, 10455, 9997, 7194, 6574, 10678, 20959, 26458, 8180, 5255, 2615, 5730, 7552],\n", + " [9528, 19004, 13372, 12294, 35501, 8864, 8368, 3248, 0, 4626, 6598, 13168, 12746, 11567, 12731, 15083, 9120, 25037, 18718, 18433, 17590, 16888, 25630, 20976, 29208, 16055, 16300, 14838, 13422, 13165, 10430, 9813, 13777, 22300, 27564, 10126, 8388, 5850, 8778, 10422],\n", + " [11111, 18271, 13766, 13912, 32929, 11330, 9573, 7855, 4626, 0, 7318, 14185, 14005, 13655, 15438, 17849, 12839, 27179, 21947, 22230, 21814, 21366, 29754, 25555, 33535, 20674, 20872, 19457, 17961, 17787, 15048, 14372, 18115, 24280, 29101, 13400, 13008, 10467, 13375, 14935],\n", + " [16120, 25070, 19805, 18893, 40239, 15411, 14904, 8245, 6598, 7318, 0, 6939, 6702, 6498, 8610, 10961, 7744, 19889, 15350, 16403, 16975, 17517, 24357, 22176, 28627, 18093, 17672, 16955, 14735, 15510, 13694, 13768, 18317, 28831, 34148, 16326, 11276, 9918, 11235, 11891],\n", + " [22606, 31971, 26537, 25283, 47006, 21597, 21529, 13843, 13168, 14185, 6939, 0, 793, 3401, 5562, 6839, 8923, 13433, 11264, 13775, 15853, 17629, 21684, 22315, 26411, 19539, 18517, 18636, 16024, 17632, 16948, 17587, 22131, 34799, 40296, 21953, 14739, 14568, 14366, 14002],\n", + " [22127, 31632, 26117, 24777, 46892, 21065, 21085, 13272, 12746, 14005, 6702, 793, 0, 2608, 4809, 6215, 8151, 13376, 10702, 13094, 15099, 16845, 21039, 21535, 25744, 18746, 17725, 17845, 15232, 16848, 16197, 16859, 21391, 34211, 39731, 21345, 14006, 13907, 13621, 13225],\n", + " [20627, 30571, 24804, 23173, 46542, 19382, 19719, 11526, 11567, 13655, 6498, 3401, 2608, 0, 2556, 4611, 5630, 13586, 9157, 11005, 12681, 14285, 19044, 18996, 23644, 16138, 15126, 15240, 12625, 14264, 13736, 14482, 18958, 32292, 37879, 19391, 11621, 11803, 11188, 10671],\n", + " [21246, 31578, 25590, 23636, 48112, 19791, 20504, 12038, 12731, 15438, 8610, 5562, 4809, 2556, 0, 2411, 4917, 12395, 6757, 8451, 10292, 12158, 16488, 16799, 21097, 14374, 13194, 13590, 10943, 12824, 12815, 13779, 18042, 32259, 37918, 19416, 10975, 11750, 10424, 9475],\n", + " [23387, 33841, 27784, 25696, 50506, 21845, 22713, 14201, 15083, 17849, 10961, 6839, 6215, 4611, 2411, 0, 6760, 10232, 4567, 7010, 9607, 12003, 14846, 16408, 19592, 14727, 13336, 14109, 11507, 13611, 14104, 15222, 19237, 34013, 39703, 21271, 12528, 13657, 11907, 10633],\n", + " [16697, 27315, 21148, 18950, 44539, 15099, 16118, 7599, 9120, 12839, 7744, 8923, 8151, 5630, 4917, 6760, 0, 16982, 9699, 9400, 9302, 9823, 16998, 14534, 21042, 10911, 10190, 9900, 7397, 8758, 8119, 8948, 13353, 27354, 33023, 14542, 6106, 6901, 5609, 5084],\n", + " [33609, 43964, 37981, 35927, 60103, 32076, 32898, 24411, 25037, 27179, 19889, 13433, 13376, 13586, 12395, 10232, 16982, 0, 8843, 12398, 16193, 19383, 16423, 22583, 20997, 22888, 21194, 22640, 20334, 22636, 23801, 25065, 28675, 44048, 49756, 31426, 22528, 23862, 21861, 20315],\n", + " [26184, 36944, 30693, 28233, 54208, 24425, 25728, 17259, 18718, 21947, 15350, 11264, 10702, 9157, 6757, 4567, 9699, 8843, 0, 3842, 7518, 10616, 10666, 14237, 15515, 14053, 12378, 13798, 11537, 13852, 15276, 16632, 19957, 35660, 41373, 23361, 14333, 16125, 13624, 11866],\n", + " [24772, 35689, 29315, 26543, 53557, 22848, 24541, 16387, 18433, 22230, 16403, 13775, 13094, 11005, 8451, 7010, 9400, 12398, 3842, 0, 3795, 7014, 8053, 10398, 12657, 10633, 8889, 10569, 8646, 10938, 12906, 14366, 17106, 33171, 38858, 21390, 12507, 14748, 11781, 9802],\n", + " [22644, 33569, 27148, 24127, 51878, 20600, 22631, 15050, 17590, 21814, 16975, 15853, 15099, 12681, 10292, 9607, 9302, 16193, 7518, 3795, 0, 3250, 8084, 6873, 11763, 6949, 5177, 7050, 5619, 7730, 10187, 11689, 13792, 30012, 35654, 18799, 10406, 12981, 9718, 7682],\n", + " [20655, 31481, 25071, 21864, 50074, 18537, 20839, 13999, 16888, 21366, 17517, 17629, 16845, 14285, 12158, 12003, 9823, 19383, 10616, 7014, 3250, 0, 9901, 4746, 12531, 3737, 1961, 4036, 3588, 5109, 7996, 9459, 10846, 27094, 32690, 16451, 8887, 11624, 8304, 6471],\n", + " [30492, 41360, 34943, 31765, 59849, 28396, 30584, 23134, 25630, 29754, 24357, 21684, 21039, 19044, 16488, 14846, 16998, 16423, 10666, 8053, 8084, 9901, 0, 9363, 4870, 13117, 11575, 13793, 13300, 15009, 17856, 19337, 20454, 36551, 42017, 26352, 18403, 21033, 17737, 15720],\n", + " [23296, 33760, 27472, 24018, 52645, 21125, 23755, 17899, 20976, 25555, 22176, 22315, 21535, 18996, 16799, 16408, 14534, 22583, 14237, 10398, 6873, 4746, 9363, 0, 10020, 5211, 4685, 6348, 7636, 8010, 11074, 12315, 11926, 27537, 32880, 18634, 12644, 15358, 12200, 10674],\n", + " [32979, 43631, 37281, 33904, 62415, 30825, 33278, 26460, 29208, 33535, 28627, 26411, 25744, 23644, 21097, 19592, 21042, 20997, 15515, 12657, 11763, 12531, 4870, 10020, 0, 14901, 13738, 15855, 16118, 17348, 20397, 21793, 21936, 37429, 42654, 28485, 21414, 24144, 20816, 18908],\n", + " [18141, 28730, 22389, 19005, 47544, 15975, 18557, 12894, 16055, 20674, 18093, 19539, 18746, 16138, 14374, 14727, 10911, 22888, 14053, 10633, 6949, 3737, 13117, 5211, 14901, 0, 1777, 1217, 3528, 2896, 5892, 7104, 7338, 23517, 29068, 13583, 7667, 10304, 7330, 6204],\n", + " [19248, 29976, 23592, 20295, 48689, 17101, 19545, 13251, 16300, 20872, 17672, 18517, 17725, 15126, 13194, 13336, 10190, 21194, 12378, 8889, 5177, 1961, 11575, 4685, 13738, 1777, 0, 2217, 2976, 3610, 6675, 8055, 8965, 25197, 30774, 14865, 8007, 10742, 7532, 6000],\n", + " [17129, 27803, 21433, 18105, 46560, 14971, 17490, 11680, 14838, 19457, 16955, 18636, 17845, 15240, 13590, 14109, 9900, 22640, 13798, 10569, 7050, 4036, 13793, 6348, 15855, 1217, 2217, 0, 2647, 1686, 4726, 6000, 6810, 23060, 28665, 12674, 6450, 9094, 6117, 5066],\n", + " [17192, 28076, 21655, 18551, 46567, 15104, 17309, 10455, 13422, 17961, 14735, 16024, 15232, 12625, 10943, 11507, 7397, 20334, 11537, 8646, 5619, 3588, 13300, 7636, 16118, 3528, 2976, 2647, 0, 2320, 4593, 6093, 8479, 24542, 30219, 13194, 5301, 8042, 4735, 3039],\n", + " [15645, 26408, 20011, 16763, 45086, 13503, 15936, 9997, 13165, 17787, 15510, 17632, 16848, 14264, 12824, 13611, 8758, 22636, 13852, 10938, 7730, 5109, 15009, 8010, 17348, 2896, 3610, 1686, 2320, 0, 3086, 4444, 6169, 22301, 27963, 11344, 4780, 7408, 4488, 3721],\n", + " [12658, 23504, 17087, 13958, 42083, 10544, 12881, 7194, 10430, 15048, 13694, 16948, 16197, 13736, 12815, 14104, 8119, 23801, 15276, 12906, 10187, 7996, 17856, 11074, 20397, 5892, 6675, 4726, 4593, 3086, 0, 1501, 5239, 20390, 26101, 8611, 2418, 4580, 2599, 3496],\n", + " [11210, 22025, 15612, 12459, 40648, 9080, 11498, 6574, 9813, 14372, 13768, 17587, 16859, 14482, 13779, 15222, 8948, 25065, 16632, 14366, 11689, 9459, 19337, 12315, 21793, 7104, 8055, 6000, 6093, 4444, 1501, 0, 4608, 19032, 24747, 7110, 2860, 4072, 3355, 4772],\n", + " [12094, 22000, 15872, 12296, 40971, 9983, 12944, 10678, 13777, 18115, 18317, 22131, 21391, 18958, 18042, 19237, 13353, 28675, 19957, 17106, 13792, 10846, 20454, 11926, 21936, 7338, 8965, 6810, 8479, 6169, 5239, 4608, 0, 16249, 21866, 7146, 7403, 8446, 7773, 8614],\n", + " [13175, 13197, 11653, 10370, 29929, 13435, 14711, 20959, 22300, 24280, 28831, 34799, 34211, 32292, 32259, 34013, 27354, 44048, 35660, 33171, 30012, 27094, 36551, 27537, 37429, 23517, 25197, 23060, 24542, 22301, 20390, 19032, 16249, 0, 5714, 12901, 21524, 20543, 22186, 23805],\n", + " [18162, 14936, 15666, 15331, 28493, 18755, 19589, 26458, 27564, 29101, 34148, 40296, 39731, 37879, 37918, 39703, 33023, 49756, 41373, 38858, 35654, 32690, 42017, 32880, 42654, 29068, 30774, 28665, 30219, 27963, 26101, 24747, 21866, 5714, 0, 18516, 27229, 26181, 27895, 29519],\n", + " [4968, 15146, 8842, 5430, 34015, 2947, 5993, 8180, 10126, 13400, 16326, 21953, 21345, 19391, 19416, 21271, 14542, 31426, 23361, 21390, 18799, 16451, 26352, 18634, 28485, 13583, 14865, 12674, 13194, 11344, 8611, 7110, 7146, 12901, 18516, 0, 9029, 7668, 9742, 11614],\n", + " [12308, 23246, 16843, 14044, 41473, 10344, 12227, 5255, 8388, 13008, 11276, 14739, 14006, 11621, 10975, 12528, 6106, 22528, 14333, 12507, 10406, 8887, 18403, 12644, 21414, 7667, 8007, 6450, 5301, 4780, 2418, 2860, 7403, 21524, 27229, 9029, 0, 2747, 726, 2749],\n", + " [10084, 20956, 14618, 12135, 38935, 8306, 9793, 2615, 5850, 10467, 9918, 14568, 13907, 11803, 11750, 13657, 6901, 23862, 16125, 14748, 12981, 11624, 21033, 15358, 24144, 10304, 10742, 9094, 8042, 7408, 4580, 4072, 8446, 20543, 26181, 7668, 2747, 0, 3330, 5313],\n", + " [13026, 23963, 17563, 14771, 42160, 11069, 12925, 5730, 8778, 13375, 11235, 14366, 13621, 11188, 10424, 11907, 5609, 21861, 13624, 11781, 9718, 8304, 17737, 12200, 20816, 7330, 7532, 6117, 4735, 4488, 2599, 3355, 7773, 22186, 27895, 9742, 726, 3330, 0, 2042],\n", + " [15056, 25994, 19589, 16743, 44198, 13078, 14967, 7552, 10422, 14935, 11891, 14002, 13225, 10671, 9475, 10633, 5084, 20315, 11866, 9802, 7682, 6471, 15720, 10674, 18908, 6204, 6000, 5066, 3039, 3721, 3496, 4772, 8614, 23805, 29519, 11614, 2749, 5313, 2042, 0],\n", + " ] # yapf: disable\n", + "\n", + "\n", + "\"\"\"Entry point of the program.\"\"\"\n", + "num_nodes = len(distance_matrix)\n", + "print('Num nodes =', num_nodes)\n", + "\n", + "# Number of groups to split the nodes, must divide num_nodes.\n", + "num_groups = 4\n", + "group_size = num_nodes // num_groups\n", + "\n", + "# Model.\n", + "model = cp_model.CpModel()\n", + "\n", + "# Variables.\n", + "neighbors = {}\n", + "obj_vars = []\n", + "obj_coeffs = []\n", + "for n1 in range(num_nodes - 1):\n", + " for n2 in range(n1 + 1, num_nodes):\n", + " same = model.NewBoolVar('neighbors_%i_%i' % (n1, n2))\n", + " neighbors[n1, n2] = same\n", + " obj_vars.append(same)\n", + " obj_coeffs.append(distance_matrix[n1][n2] + distance_matrix[n2][n1])\n", + "\n", + "# Number of neighborss:\n", + "for n in range(num_nodes):\n", + " model.Add(sum(neighbors[m, n] for m in range(n)) + \n", + " sum(neighbors[n, m] for m in range(n + 1, num_nodes)) ==\n", + " group_size - 1)\n", + "\n", + "# Enforce transivity on all triplets.\n", + "for n1 in range(num_nodes - 2):\n", + " for n2 in range(n1 + 1, num_nodes - 1):\n", + " for n3 in range(n2 + 1, num_nodes):\n", + " model.Add(\n", + " neighbors[n1, n3] + neighbors[n2, n3] + neighbors[n1, n2] != 2)\n", + "\n", + "# Redundant constraints on total sum of neighborss.\n", + "model.Add(sum(obj_vars) == num_groups * group_size * (group_size - 1) // 2)\n", + "\n", + "# Minimize weighted sum of arcs.\n", + "model.Minimize(\n", + " sum(obj_vars[i] * obj_coeffs[i] for i in range(len(obj_vars))))\n", + "\n", + "# Solve and print out the solution.\n", + "solver = cp_model.CpSolver()\n", + "solver.parameters.log_search_progress = True\n", + "solver.parameters.num_search_workers = 8\n", + "\n", + "status = solver.Solve(model)\n", + "print(solver.ResponseStats())\n", + "\n", + "visited = set()\n", + "for g in range(num_groups):\n", + " for n in range(num_nodes):\n", + " if not n in visited:\n", + " visited.add(n)\n", + " output = str(n)\n", + " for o in range(n + 1, num_nodes):\n", + " if solver.BooleanValue(neighbors[n, o]):\n", + " visited.add(o)\n", + " output += ' ' + str(o)\n", + " print('Group', g, ':', output)\n", + " break\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/examples/cover_rectangle_sat.ipynb b/examples/notebook/examples/cover_rectangle_sat.ipynb new file mode 100644 index 0000000000..b3ba89f480 --- /dev/null +++ b/examples/notebook/examples/cover_rectangle_sat.ipynb @@ -0,0 +1,126 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010-2018 Google LLC\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"Fill a 72x37 rectangle by a minimum number of non-overlapping squares.\"\"\"\n", + "\n", + "from __future__ import print_function\n", + "from __future__ import division\n", + "\n", + "from ortools.sat.python import cp_model\n", + "\n", + "\n", + "def cover_rectangle(num_squares):\n", + " \"\"\"Try to fill the rectangle with a given number of squares.\"\"\"\n", + " size_x = 72\n", + " size_y = 37\n", + "\n", + " model = cp_model.CpModel()\n", + "\n", + " areas = []\n", + " sizes = []\n", + " x_intervals = []\n", + " y_intervals = []\n", + " x_starts = []\n", + " y_starts = []\n", + "\n", + " # Creates intervals for the NoOverlap2D and size variables.\n", + " for i in range(num_squares):\n", + " size = model.NewIntVar(1, size_y, 'size_%i' % i)\n", + " start_x = model.NewIntVar(0, size_x, 'sx_%i' % i)\n", + " end_x = model.NewIntVar(0, size_x, 'ex_%i' % i)\n", + " start_y = model.NewIntVar(0, size_y, 'sy_%i' % i)\n", + " end_y = model.NewIntVar(0, size_y, 'ey_%i' % i)\n", + "\n", + " interval_x = model.NewIntervalVar(start_x, size, end_x, 'ix_%i' % i)\n", + " interval_y = model.NewIntervalVar(start_y, size, end_y, 'iy_%i' % i)\n", + "\n", + " area = model.NewIntVar(1, size_y * size_y, 'area_%i' % i)\n", + " model.AddProdEquality(area, [size, size])\n", + "\n", + " areas.append(area)\n", + " x_intervals.append(interval_x)\n", + " y_intervals.append(interval_y)\n", + " sizes.append(size)\n", + " x_starts.append(start_x)\n", + " y_starts.append(start_y)\n", + "\n", + " # Main constraint.\n", + " model.AddNoOverlap2D(x_intervals, y_intervals)\n", + "\n", + " # Redundant constraints.\n", + " model.AddCumulative(x_intervals, sizes, size_y)\n", + " model.AddCumulative(y_intervals, sizes, size_x)\n", + "\n", + " # Forces the rectangle to be exactly covered.\n", + " model.Add(sum(areas) == size_x * size_y)\n", + "\n", + " # Symmetry breaking 1: sizes are ordered.\n", + " for i in range(num_squares - 1):\n", + " model.Add(sizes[i] <= sizes[i + 1])\n", + "\n", + " # Define same to be true iff sizes[i] == sizes[i + 1]\n", + " same = model.NewBoolVar('')\n", + " model.Add(sizes[i] == sizes[i + 1]).OnlyEnforceIf(same)\n", + " model.Add(sizes[i] < sizes[i + 1]).OnlyEnforceIf(same.Not())\n", + "\n", + " # Tie break with starts.\n", + " model.Add(x_starts[i] <= x_starts[i + 1]).OnlyEnforceIf(same)\n", + "\n", + " # Symmetry breaking 2: first square in one quadrant.\n", + " model.Add(x_starts[0] < 36)\n", + " model.Add(y_starts[0] < 19)\n", + "\n", + " # Creates a solver and solves.\n", + " solver = cp_model.CpSolver()\n", + " status = solver.Solve(model)\n", + " print('%s found in %0.2fs' % (solver.StatusName(status), solver.WallTime()))\n", + "\n", + " # Prints solution.\n", + " if status == cp_model.FEASIBLE:\n", + " display = [[' ' for _ in range(size_x)] for _ in range(size_y)]\n", + " for i in range(num_squares):\n", + " sol_x = solver.Value(x_starts[i])\n", + " sol_y = solver.Value(y_starts[i])\n", + " sol_s = solver.Value(sizes[i])\n", + " char = format(i, '01x')\n", + " for j in range(sol_s):\n", + " for k in range(sol_s):\n", + " if display[sol_y + j][sol_x + k] != ' ':\n", + " print('ERROR between %s and %s' %\n", + " (display[sol_y + j][sol_x + k], char))\n", + " display[sol_y + j][sol_x + k] = char\n", + "\n", + " for line in range(size_y):\n", + " print(' '.join(display[line]))\n", + " return status == cp_model.FEASIBLE\n", + "\n", + "\n", + "for num in range(1, 15):\n", + " print('Trying with size =', num)\n", + " if cover_rectangle(num):\n", + " break\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/examples/cvrptw_plot.ipynb b/examples/notebook/examples/cvrptw_plot.ipynb new file mode 100644 index 0000000000..c33dbdfdbd --- /dev/null +++ b/examples/notebook/examples/cvrptw_plot.ipynb @@ -0,0 +1,765 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# This Python file uses the following encoding: utf-8\n", + "# Copyright 2015 Tin Arm Engineering AB\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"Capacitated Vehicle Routing Problem with Time Windows (and optional orders).\n", + "\n", + " This is a sample using the routing library python wrapper to solve a\n", + " CVRPTW problem.\n", + " A description of the problem can be found here:\n", + " http://en.wikipedia.org/wiki/Vehicle_routing_problem.\n", + " The variant which is tackled by this model includes a capacity dimension,\n", + " time windows and optional orders, with a penalty cost if orders are not\n", + " performed.\n", + " To help explore the problem, two classes are provided Customers() and\n", + " Vehicles(): used to randomly locate orders and depots, and to randomly\n", + " generate demands, time-window constraints and vehicles.\n", + " Distances are computed using the Great Circle distances. Distances are in km\n", + " and times in seconds.\n", + "\n", + " A function for the displaying of the vehicle plan\n", + " display_vehicle_output\n", + "\n", + " The optimization engine uses local search to improve solutions, first\n", + " solutions being generated using a cheapest addition heuristic.\n", + " Numpy and Matplotlib are required for the problem creation and display.\n", + "\n", + "\"\"\"\n", + "import os\n", + "import numpy as np\n", + "from matplotlib import pyplot as plt\n", + "from collections import namedtuple\n", + "from ortools.constraint_solver import pywrapcp\n", + "from ortools.constraint_solver import routing_enums_pb2\n", + "from datetime import datetime, timedelta\n", + "\n", + "\n", + "class Customers():\n", + " \"\"\"\n", + " A class that generates and holds customers information.\n", + "\n", + " Randomly normally distribute a number of customers and locations within\n", + " a region described by a rectangle. Generate a random demand for each\n", + " customer. Generate a random time window for each customer.\n", + " May either be initiated with the extents, as a dictionary describing\n", + " two corners of a rectangle in latitude and longitude OR as a center\n", + " point (lat, lon), and box_size in km. The default arguments are for a\n", + " 10 x 10 km square centered in Sheffield).\n", + "\n", + " Args: extents (Optional[Dict]): A dictionary describing a rectangle in\n", + " latitude and longitude with the keys 'llcrnrlat', 'llcrnrlon' &\n", + " 'urcrnrlat' & 'urcrnrlat' center (Optional(Tuple): A tuple of\n", + " (latitude, longitude) describing the centre of the rectangle. box_size\n", + " (Optional float: The length in km of the box's sides. num_stops (int):\n", + " The number of customers, including the depots that are placed normally\n", + " distributed in the rectangle. min_demand (int): Lower limit on the\n", + " randomly generated demand at each customer. max_demand (int): Upper\n", + " limit on the randomly generated demand at each customer.\n", + " min_tw: shortest random time window for a customer, in hours.\n", + " max_tw: longest random time window for a customer, in hours.\n", + " Examples: To place 100 customers randomly within 100 km x 100 km\n", + " rectangle, centered in the default location, with a random demand of\n", + " between 5 and 10 units: >>> customers = Customers(num_stops=100,\n", + " box_size=100, ... min_demand=5, max_demand=10)\n", + " alternatively, to place 75 customers in the same area with default\n", + " arguments for demand: >>> extents = {'urcrnrlon': 0.03403, 'llcrnrlon':\n", + " -2.98325, ... 'urcrnrlat': 54.28127, 'llcrnrlat': 52.48150} >>>\n", + " customers = Customers(num_stops=75, extents=extents)\n", + " \"\"\"\n", + "\n", + " def __init__(self,\n", + " extents=None,\n", + " center=(53.381393, -1.474611),\n", + " box_size=10,\n", + " num_stops=100,\n", + " min_demand=0,\n", + " max_demand=25,\n", + " min_tw=1,\n", + " max_tw=5):\n", + " self.number = num_stops #: The number of customers and depots\n", + " #: Location, a named tuple for locations.\n", + " Location = namedtuple('Location', ['lat', 'lon'])\n", + " if extents is not None:\n", + " self.extents = extents #: The lower left and upper right points\n", + " #: Location[lat,lon]: the centre point of the area.\n", + " self.center = Location(\n", + " extents['urcrnrlat'] - 0.5 *\n", + " (extents['urcrnrlat'] - extents['llcrnrlat']),\n", + " extents['urcrnrlon'] - 0.5 *\n", + " (extents['urcrnrlon'] - extents['llcrnrlon']))\n", + " else:\n", + " #: Location[lat,lon]: the centre point of the area.\n", + " (clat, clon) = self.center = Location(center[0], center[1])\n", + " rad_earth = 6367 # km\n", + " circ_earth = np.pi * rad_earth\n", + " #: The lower left and upper right points\n", + " self.extents = {\n", + " 'llcrnrlon': (clon - 180 * box_size /\n", + " (circ_earth * np.cos(np.deg2rad(clat)))),\n", + " 'llcrnrlat':\n", + " clat - 180 * box_size / circ_earth,\n", + " 'urcrnrlon': (clon + 180 * box_size /\n", + " (circ_earth * np.cos(np.deg2rad(clat)))),\n", + " 'urcrnrlat':\n", + " clat + 180 * box_size / circ_earth\n", + " }\n", + " # The 'name' of the stop, indexed from 0 to num_stops-1\n", + " stops = np.array(range(0, num_stops))\n", + " # normaly distributed random distribution of stops within the box\n", + " stdv = 6 # the number of standard deviations 99.9% will be within +-3\n", + " lats = (self.extents['llcrnrlat'] + np.random.randn(num_stops) *\n", + " (self.extents['urcrnrlat'] - self.extents['llcrnrlat']) / stdv)\n", + " lons = (self.extents['llcrnrlon'] + np.random.randn(num_stops) *\n", + " (self.extents['urcrnrlon'] - self.extents['llcrnrlon']) / stdv)\n", + " # uniformly distributed integer demands.\n", + " demands = np.random.randint(min_demand, max_demand, num_stops)\n", + "\n", + " self.time_horizon = 24 * 60**2 # A 24 hour period.\n", + "\n", + " # The customers demand min_tw to max_tw hour time window for each\n", + " # delivery\n", + " time_windows = np.random.randint(min_tw * 3600, max_tw * 3600,\n", + " num_stops)\n", + " # The last time a delivery window can start\n", + " latest_time = self.time_horizon - time_windows\n", + " start_times = [None for o in time_windows]\n", + " stop_times = [None for o in time_windows]\n", + " # Make random timedeltas, nominally from the start of the day.\n", + " for idx in range(self.number):\n", + " stime = int(np.random.randint(0, latest_time[idx]))\n", + " start_times[idx] = timedelta(seconds=stime)\n", + " stop_times[idx] = (\n", + " start_times[idx] + timedelta(seconds=int(time_windows[idx])))\n", + " # A named tuple for the customer\n", + " Customer = namedtuple(\n", + " 'Customer',\n", + " [\n", + " 'index', # the index of the stop\n", + " 'demand', # the demand for the stop\n", + " 'lat', # the latitude of the stop\n", + " 'lon', # the longitude of the stop\n", + " 'tw_open', # timedelta window open\n", + " 'tw_close'\n", + " ]) # timedelta window cls\n", + "\n", + " self.customers = [\n", + " Customer(idx, dem, lat, lon, tw_open, tw_close)\n", + " for idx, dem, lat, lon, tw_open, tw_close in zip(\n", + " stops, demands, lats, lons, start_times, stop_times)\n", + " ]\n", + "\n", + " # The number of seconds needed to 'unload' 1 unit of goods.\n", + " self.service_time_per_dem = 300 # seconds\n", + "\n", + " def set_manager(self, manager):\n", + " self.manager = manager\n", + "\n", + " def central_start_node(self, invert=False):\n", + " \"\"\"\n", + " Return a random starting node, with probability weighted by distance\n", + " from the centre of the extents, so that a central starting node is\n", + " likely.\n", + "\n", + " Args: invert (Optional bool): When True, a peripheral starting node is\n", + " most likely.\n", + "\n", + " Returns:\n", + " int: a node index.\n", + "\n", + " Examples:\n", + " >>> customers.central_start_node(invert=True)\n", + " 42\n", + " \"\"\"\n", + " num_nodes = len(self.customers)\n", + " dist = np.empty((num_nodes, 1))\n", + " for idx_to in range(num_nodes):\n", + " dist[idx_to] = self._haversine(self.center.lon, self.center.lat,\n", + " self.customers[idx_to].lon,\n", + " self.customers[idx_to].lat)\n", + " furthest = np.max(dist)\n", + "\n", + " if invert:\n", + " prob = dist * 1.0 / sum(dist)\n", + " else:\n", + " prob = (furthest - dist * 1.0) / sum(furthest - dist)\n", + " indexes = np.array([range(num_nodes)])\n", + " start_node = np.random.choice(\n", + " indexes.flatten(), size=1, replace=True, p=prob.flatten())\n", + " return start_node[0]\n", + "\n", + " def make_distance_mat(self, method='haversine'):\n", + " \"\"\"\n", + " Return a distance matrix and make it a member of Customer, using the\n", + " method given in the call. Currently only Haversine (GC distance) is\n", + " implemented, but Manhattan, or using a maps API could be added here.\n", + " Raises an AssertionError for all other methods.\n", + "\n", + " Args: method (Optional[str]): method of distance calculation to use. The\n", + " Haversine formula is the only method implemented.\n", + "\n", + " Returns:\n", + " Numpy array of node to node distances.\n", + "\n", + " Examples:\n", + " >>> dist_mat = customers.make_distance_mat(method='haversine')\n", + " >>> dist_mat = customers.make_distance_mat(method='manhattan')\n", + " AssertionError\n", + " \"\"\"\n", + " self.distmat = np.zeros((self.number, self.number))\n", + " methods = {'haversine': self._haversine}\n", + " assert (method in methods)\n", + " for frm_idx in range(self.number):\n", + " for to_idx in range(self.number):\n", + " if frm_idx != to_idx:\n", + " frm_c = self.customers[frm_idx]\n", + " to_c = self.customers[to_idx]\n", + " self.distmat[frm_idx, to_idx] = self._haversine(\n", + " frm_c.lon, frm_c.lat, to_c.lon, to_c.lat)\n", + " return (self.distmat)\n", + "\n", + " def _haversine(self, lon1, lat1, lon2, lat2):\n", + " \"\"\"\n", + " Calculate the great circle distance between two points\n", + " on the earth specified in decimal degrees of latitude and longitude.\n", + " https://en.wikipedia.org/wiki/Haversine_formula\n", + "\n", + " Args:\n", + " lon1: longitude of pt 1,\n", + " lat1: latitude of pt 1,\n", + " lon2: longitude of pt 2,\n", + " lat2: latitude of pt 2\n", + "\n", + " Returns:\n", + " the distace in km between pt1 and pt2\n", + " \"\"\"\n", + " # convert decimal degrees to radians\n", + " lon1, lat1, lon2, lat2 = map(np.radians, [lon1, lat1, lon2, lat2])\n", + "\n", + " # haversine formula\n", + " dlon = lon2 - lon1\n", + " dlat = lat2 - lat1\n", + " a = (np.sin(dlat / 2)**2 +\n", + " np.cos(lat1) * np.cos(lat2) * np.sin(dlon / 2)**2)\n", + " c = 2 * np.arcsin(np.sqrt(a))\n", + "\n", + " # 6367 km is the radius of the Earth\n", + " km = 6367 * c\n", + " return km\n", + "\n", + " def get_total_demand(self):\n", + " \"\"\"\n", + " Return the total demand of all customers.\n", + " \"\"\"\n", + " return (sum([c.demand for c in self.customers]))\n", + "\n", + " def return_dist_callback(self, **kwargs):\n", + " \"\"\"\n", + " Return a callback function for the distance matrix.\n", + "\n", + " Args: **kwargs: Arbitrary keyword arguments passed on to\n", + " make_distance_mat()\n", + "\n", + " Returns:\n", + " function: dist_return(a,b) A function that takes the 'from' node\n", + " index and the 'to' node index and returns the distance in km.\n", + " \"\"\"\n", + " self.make_distance_mat(**kwargs)\n", + "\n", + " def dist_return(from_index, to_index):\n", + " # Convert from routing variable Index to distance matrix NodeIndex.\n", + " from_node = self.manager.IndexToNode(from_index)\n", + " to_node = self.manager.IndexToNode(to_index)\n", + " return (self.distmat[from_node][to_node])\n", + "\n", + " return dist_return\n", + "\n", + " def return_dem_callback(self):\n", + " \"\"\"\n", + " Return a callback function that gives the demands.\n", + "\n", + " Returns:\n", + " function: dem_return(a,b) A function that takes the 'from' node\n", + " index and the 'to' node index and returns the distance in km.\n", + " \"\"\"\n", + "\n", + " def dem_return(from_index, to_index):\n", + " # Convert from routing variable Index to distance matrix NodeIndex.\n", + " from_node = self.manager.IndexToNode(from_index)\n", + " to_node = self.manager.IndexToNode(to_index)\n", + " return (self.customers[from_node].demand)\n", + "\n", + " return dem_return\n", + "\n", + " def zero_depot_demands(self, depot):\n", + " \"\"\"\n", + " Zero out the demands and time windows of depot. The Depots do not have\n", + " demands or time windows so this function clears them.\n", + "\n", + " Args: depot (int): index of the stop to modify into a depot.\n", + " Examples: >>> customers.zero_depot_demands(5) >>>\n", + " customers.customers[5].demand == 0 True\n", + " \"\"\"\n", + " start_depot = self.customers[depot]\n", + " self.customers[depot] = start_depot._replace(\n", + " demand=0, tw_open=None, tw_close=None)\n", + "\n", + " def make_service_time_call_callback(self):\n", + " \"\"\"\n", + " Return a callback function that provides the time spent servicing the\n", + " customer. Here is it proportional to the demand given by\n", + " self.service_time_per_dem, default 300 seconds per unit demand.\n", + "\n", + " Returns:\n", + " function [dem_return(a, b)]: A function that takes the from/a node\n", + " index and the to/b node index and returns the service time at a\n", + "\n", + " \"\"\"\n", + "\n", + " def service_time_return(a, b):\n", + " return (self.customers[a].demand * self.service_time_per_dem)\n", + "\n", + " return service_time_return\n", + "\n", + " def make_transit_time_callback(self, speed_kmph=10):\n", + " \"\"\"\n", + " Creates a callback function for transit time. Assuming an average\n", + " speed of speed_kmph\n", + " Args:\n", + " speed_kmph: the average speed in km/h\n", + "\n", + " Returns:\n", + " function [transit_time_return(a, b)]: A function that takes the\n", + " from/a node index and the to/b node index and returns the\n", + " transit time from a to b.\n", + " \"\"\"\n", + "\n", + " def transit_time_return(a, b):\n", + " return (self.distmat[a][b] / (speed_kmph * 1.0 / 60**2))\n", + "\n", + " return transit_time_return\n", + "\n", + "\n", + "class Vehicles():\n", + " \"\"\"\n", + " A Class to create and hold vehicle information.\n", + "\n", + " The Vehicles in a CVRPTW problem service the customers and belong to a\n", + " depot. The class Vehicles creates a list of named tuples describing the\n", + " Vehicles. The main characteristics are the vehicle capacity, fixed cost,\n", + " and cost per km. The fixed cost of using a certain type of vehicles can be\n", + " higher or lower than others. If a vehicle is used, i.e. this vehicle serves\n", + " at least one node, then this cost is added to the objective function.\n", + "\n", + " Note:\n", + " If numpy arrays are given for capacity and cost, then they must be of\n", + " the same length, and the number of vehicles are inferred from them.\n", + " If scalars are given, the fleet is homogeneous, and the number of\n", + " vehicles is determined by number.\n", + "\n", + " Args: capacity (scalar or numpy array): The integer capacity of demand\n", + " units. cost (scalar or numpy array): The fixed cost of the vehicle. number\n", + " (Optional [int]): The number of vehicles in a homogeneous fleet.\n", + " \"\"\"\n", + "\n", + " def __init__(self, capacity=100, cost=100, number=None):\n", + "\n", + " Vehicle = namedtuple('Vehicle', ['index', 'capacity', 'cost'])\n", + "\n", + " if number is None:\n", + " self.number = np.size(capacity)\n", + " else:\n", + " self.number = number\n", + " idxs = np.array(range(0, self.number))\n", + "\n", + " if np.isscalar(capacity):\n", + " capacities = capacity * np.ones_like(idxs)\n", + " elif np.size(capacity) != np.size(capacity):\n", + " print('capacity is neither scalar, nor the same size as num!')\n", + " else:\n", + " capacities = capacity\n", + "\n", + " if np.isscalar(cost):\n", + " costs = cost * np.ones_like(idxs)\n", + " elif np.size(cost) != self.number:\n", + " print(np.size(cost))\n", + " print('cost is neither scalar, nor the same size as num!')\n", + " else:\n", + " costs = cost\n", + "\n", + " self.vehicles = [\n", + " Vehicle(idx, capacity, cost)\n", + " for idx, capacity, cost in zip(idxs, capacities, costs)\n", + " ]\n", + "\n", + " def get_total_capacity(self):\n", + " return (sum([c.capacity for c in self.vehicles]))\n", + "\n", + " def return_starting_callback(self, customers, sameStartFinish=False):\n", + " # create a different starting and finishing depot for each vehicle\n", + " self.starts = [\n", + " int(customers.central_start_node()) for o in range(self.number)\n", + " ]\n", + " if sameStartFinish:\n", + " self.ends = self.starts\n", + " else:\n", + " self.ends = [\n", + " int(customers.central_start_node(invert=True))\n", + " for o in range(self.number)\n", + " ]\n", + " # the depots will not have demands, so zero them.\n", + " for depot in self.starts:\n", + " customers.zero_depot_demands(depot)\n", + " for depot in self.ends:\n", + " customers.zero_depot_demands(depot)\n", + "\n", + " def start_return(v):\n", + " return (self.starts[v])\n", + "\n", + " return start_return\n", + "\n", + "\n", + "def discrete_cmap(N, base_cmap=None):\n", + " \"\"\"\n", + " Create an N-bin discrete colormap from the specified input map\n", + " \"\"\"\n", + " # Note that if base_cmap is a string or None, you can simply do\n", + " # return plt.cm.get_cmap(base_cmap, N)\n", + " # The following works for string, None, or a colormap instance:\n", + "\n", + " base = plt.cm.get_cmap(base_cmap)\n", + " color_list = base(np.linspace(0, 1, N))\n", + " cmap_name = base.name + str(N)\n", + " return base.from_list(cmap_name, color_list, N)\n", + "\n", + "\n", + "def vehicle_output_string(manager, routing, plan):\n", + " \"\"\"\n", + " Return a string displaying the output of the routing instance and\n", + " assignment (plan).\n", + "\n", + " Args: routing (ortools.constraint_solver.pywrapcp.RoutingModel): routing.\n", + " plan (ortools.constraint_solver.pywrapcp.Assignment): the assignment.\n", + "\n", + " Returns:\n", + " (string) plan_output: describing each vehicle's plan.\n", + "\n", + " (List) dropped: list of dropped orders.\n", + "\n", + " \"\"\"\n", + " dropped = []\n", + " for order in range(routing.Size()):\n", + " if (plan.Value(routing.NextVar(order)) == order):\n", + " dropped.append(str(order))\n", + "\n", + " capacity_dimension = routing.GetDimensionOrDie('Capacity')\n", + " time_dimension = routing.GetDimensionOrDie('Time')\n", + " plan_output = ''\n", + "\n", + " for route_number in range(routing.vehicles()):\n", + " order = routing.Start(route_number)\n", + " plan_output += 'Route {0}:'.format(route_number)\n", + " if routing.IsEnd(plan.Value(routing.NextVar(order))):\n", + " plan_output += ' Empty \\n'\n", + " else:\n", + " while True:\n", + " load_var = capacity_dimension.CumulVar(order)\n", + " time_var = time_dimension.CumulVar(order)\n", + " node = manager.IndexToNode(order)\n", + " plan_output += \\\n", + " ' {node} Load({load}) Time({tmin}, {tmax}) -> '.format(\n", + " node=node,\n", + " load=plan.Value(load_var),\n", + " tmin=str(timedelta(seconds=plan.Min(time_var))),\n", + " tmax=str(timedelta(seconds=plan.Max(time_var))))\n", + "\n", + " if routing.IsEnd(order):\n", + " plan_output += ' EndRoute {0}. \\n'.format(route_number)\n", + " break\n", + " order = plan.Value(routing.NextVar(order))\n", + " plan_output += '\\n'\n", + "\n", + " return (plan_output, dropped)\n", + "\n", + "\n", + "def build_vehicle_route(manager, routing, plan, customers, veh_number):\n", + " \"\"\"\n", + " Build a route for a vehicle by starting at the strat node and\n", + " continuing to the end node.\n", + "\n", + " Args: routing (ortools.constraint_solver.pywrapcp.RoutingModel): routing.\n", + " plan (ortools.constraint_solver.pywrapcp.Assignment): the assignment.\n", + " customers (Customers): the customers instance. veh_number (int): index of\n", + " the vehicle\n", + "\n", + " Returns:\n", + " (List) route: indexes of the customers for vehicle veh_number\n", + " \"\"\"\n", + " veh_used = routing.IsVehicleUsed(plan, veh_number)\n", + " print('Vehicle {0} is used {1}'.format(veh_number, veh_used))\n", + " if veh_used:\n", + " route = []\n", + " node = routing.Start(veh_number) # Get the starting node index\n", + " route.append(customers.customers[manager.IndexToNode(node)])\n", + " while not routing.IsEnd(node):\n", + " route.append(customers.customers[manager.IndexToNode(node)])\n", + " node = plan.Value(routing.NextVar(node))\n", + "\n", + " route.append(customers.customers[manager.IndexToNode(node)])\n", + " return route\n", + " else:\n", + " return None\n", + "\n", + "\n", + "def plot_vehicle_routes(veh_route, ax1, customers, vehicles):\n", + " \"\"\"\n", + " Plot the vehicle routes on matplotlib axis ax1.\n", + "\n", + " Args: veh_route (dict): a dictionary of routes keyed by vehicle idx. ax1\n", + " (matplotlib.axes._subplots.AxesSubplot): Matplotlib axes customers\n", + " (Customers): the customers instance. vehicles (Vehicles): the vehicles\n", + " instance.\n", + " \"\"\"\n", + " veh_used = [v for v in veh_route if veh_route[v] is not None]\n", + "\n", + " cmap = discrete_cmap(vehicles.number + 2, 'nipy_spectral')\n", + "\n", + " for veh_number in veh_used:\n", + "\n", + " lats, lons = zip(*[(c.lat, c.lon) for c in veh_route[veh_number]])\n", + " lats = np.array(lats)\n", + " lons = np.array(lons)\n", + " s_dep = customers.customers[vehicles.starts[veh_number]]\n", + " s_fin = customers.customers[vehicles.ends[veh_number]]\n", + " ax1.annotate(\n", + " 'v({veh}) S @ {node}'.format(\n", + " veh=veh_number, node=vehicles.starts[veh_number]),\n", + " xy=(s_dep.lon, s_dep.lat),\n", + " xytext=(10, 10),\n", + " xycoords='data',\n", + " textcoords='offset points',\n", + " arrowprops=dict(\n", + " arrowstyle='->',\n", + " connectionstyle='angle3,angleA=90,angleB=0',\n", + " shrinkA=0.05),\n", + " )\n", + " ax1.annotate(\n", + " 'v({veh}) F @ {node}'.format(\n", + " veh=veh_number, node=vehicles.ends[veh_number]),\n", + " xy=(s_fin.lon, s_fin.lat),\n", + " xytext=(10, -20),\n", + " xycoords='data',\n", + " textcoords='offset points',\n", + " arrowprops=dict(\n", + " arrowstyle='->',\n", + " connectionstyle='angle3,angleA=-90,angleB=0',\n", + " shrinkA=0.05),\n", + " )\n", + " ax1.plot(lons, lats, 'o', mfc=cmap(veh_number + 1))\n", + " ax1.quiver(\n", + " lons[:-1],\n", + " lats[:-1],\n", + " lons[1:] - lons[:-1],\n", + " lats[1:] - lats[:-1],\n", + " scale_units='xy',\n", + " angles='xy',\n", + " scale=1,\n", + " color=cmap(veh_number + 1))\n", + "\n", + "\n", + "# Create a set of customer, (and depot) stops.\n", + "customers = Customers(\n", + " num_stops=50,\n", + " min_demand=1,\n", + " max_demand=15,\n", + " box_size=40,\n", + " min_tw=3,\n", + " max_tw=6)\n", + "\n", + "# Create a list of inhomgenious vehicle capacities as integer units.\n", + "capacity = [50, 75, 100, 125, 150, 175, 200, 250]\n", + "\n", + "# Create a list of inhomogeneous fixed vehicle costs.\n", + "cost = [int(100 + 2 * np.sqrt(c)) for c in capacity]\n", + "\n", + "# Create a set of vehicles, the number set by the length of capacity.\n", + "vehicles = Vehicles(capacity=capacity, cost=cost)\n", + "\n", + "# check to see that the problem is feasible, if we don't have enough\n", + "# vehicles to cover the demand, there is no point in going further.\n", + "assert (customers.get_total_demand() < vehicles.get_total_capacity())\n", + "\n", + "# Set the starting nodes, and create a callback fn for the starting node.\n", + "start_fn = vehicles.return_starting_callback(\n", + " customers, sameStartFinish=False)\n", + "\n", + "# Create the routing index manager.\n", + "manager = pywrapcp.RoutingIndexManager(\n", + " customers.number, # int number\n", + " vehicles.number, # int number\n", + " vehicles.starts, # List of int start depot\n", + " vehicles.ends) # List of int end depot\n", + "\n", + "customers.set_manager(manager)\n", + "\n", + "# Set model parameters\n", + "model_parameters = pywrapcp.DefaultRoutingModelParameters()\n", + "\n", + "# The solver parameters can be accessed from the model parameters. For example :\n", + "# model_parameters.solver_parameters.CopyFrom(\n", + "# pywrapcp.Solver.DefaultSolverParameters())\n", + "# model_parameters.solver_parameters.trace_propagation = True\n", + "\n", + "# Make the routing model instance.\n", + "routing = pywrapcp.RoutingModel(manager, model_parameters)\n", + "\n", + "parameters = pywrapcp.DefaultRoutingSearchParameters()\n", + "# Setting first solution heuristic (cheapest addition).\n", + "parameters.first_solution_strategy = (\n", + " routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC)\n", + "# Routing: forbids use of TSPOpt neighborhood, (this is the default behaviour)\n", + "parameters.local_search_operators.use_tsp_opt = pywrapcp.BOOL_FALSE\n", + "# Disabling Large Neighborhood Search, (this is the default behaviour)\n", + "parameters.local_search_operators.use_path_lns = pywrapcp.BOOL_FALSE\n", + "parameters.local_search_operators.use_inactive_lns = pywrapcp.BOOL_FALSE\n", + "\n", + "parameters.time_limit.seconds = 10\n", + "parameters.use_full_propagation = True\n", + "#parameters.log_search = True\n", + "\n", + "# Create callback fns for distances, demands, service and transit-times.\n", + "dist_fn = customers.return_dist_callback()\n", + "dist_fn_index = routing.RegisterTransitCallback(dist_fn)\n", + "\n", + "dem_fn = customers.return_dem_callback()\n", + "dem_fn_index = routing.RegisterTransitCallback(dem_fn)\n", + "\n", + "# Create and register a transit callback.\n", + "serv_time_fn = customers.make_service_time_call_callback()\n", + "transit_time_fn = customers.make_transit_time_callback()\n", + "def tot_time_fn(from_index, to_index):\n", + " \"\"\"\n", + " The time function we want is both transit time and service time.\n", + " \"\"\"\n", + " # Convert from routing variable Index to distance matrix NodeIndex.\n", + " from_node = manager.IndexToNode(from_index)\n", + " to_node = manager.IndexToNode(to_index)\n", + " return serv_time_fn(from_node, to_node) + transit_time_fn(from_node, to_node)\n", + "\n", + "tot_time_fn_index = routing.RegisterTransitCallback(tot_time_fn)\n", + "\n", + "# Set the cost function (distance callback) for each arc, homogeneous for\n", + "# all vehicles.\n", + "routing.SetArcCostEvaluatorOfAllVehicles(dist_fn_index)\n", + "\n", + "# Set vehicle costs for each vehicle, not homogeneous.\n", + "for veh in vehicles.vehicles:\n", + " routing.SetFixedCostOfVehicle(veh.cost, int(veh.index))\n", + "\n", + "# Add a dimension for vehicle capacities\n", + "null_capacity_slack = 0\n", + "routing.AddDimensionWithVehicleCapacity(\n", + " dem_fn_index, # demand callback\n", + " null_capacity_slack,\n", + " capacity, # capacity array\n", + " True,\n", + " 'Capacity')\n", + "# Add a dimension for time and a limit on the total time_horizon\n", + "routing.AddDimension(\n", + " tot_time_fn_index, # total time function callback\n", + " customers.time_horizon,\n", + " customers.time_horizon,\n", + " True,\n", + " 'Time')\n", + "\n", + "time_dimension = routing.GetDimensionOrDie('Time')\n", + "for cust in customers.customers:\n", + " if cust.tw_open is not None:\n", + " time_dimension.CumulVar(manager.NodeToIndex(cust.index)).SetRange(\n", + " cust.tw_open.seconds, cust.tw_close.seconds)\n", + "\"\"\"\n", + " To allow the dropping of orders, we add disjunctions to all the customer\n", + "nodes. Each disjunction is a list of 1 index, which allows that customer to\n", + "be active or not, with a penalty if not. The penalty should be larger\n", + "than the cost of servicing that customer, or it will always be dropped!\n", + "\"\"\"\n", + "# To add disjunctions just to the customers, make a list of non-depots.\n", + "non_depot = set(range(customers.number))\n", + "non_depot.difference_update(vehicles.starts)\n", + "non_depot.difference_update(vehicles.ends)\n", + "penalty = 400000 # The cost for dropping a node from the plan.\n", + "nodes = [routing.AddDisjunction([manager.NodeToIndex(c)], penalty) for c in non_depot]\n", + "\n", + "# This is how you would implement partial routes if you already knew part\n", + "# of a feasible solution for example:\n", + "# partial = np.random.choice(list(non_depot), size=(4,5), replace=False)\n", + "\n", + "# routing.CloseModel()\n", + "# partial_list = [partial[0,:].tolist(),\n", + "# partial[1,:].tolist(),\n", + "# partial[2,:].tolist(),\n", + "# partial[3,:].tolist(),\n", + "# [],[],[],[]]\n", + "# print(routing.ApplyLocksToAllVehicles(partial_list, False))\n", + "\n", + "# Solve the problem !\n", + "assignment = routing.SolveWithParameters(parameters)\n", + "\n", + "# The rest is all optional for saving, printing or plotting the solution.\n", + "if assignment:\n", + " ## save the assignment, (Google Protobuf format)\n", + " #save_file_base = os.path.realpath(__file__).split('.')[0]\n", + " #if routing.WriteAssignment(save_file_base + '_assignment.ass'):\n", + " # print('succesfully wrote assignment to file ' + save_file_base +\n", + " # '_assignment.ass')\n", + "\n", + " print('The Objective Value is {0}'.format(assignment.ObjectiveValue()))\n", + "\n", + " plan_output, dropped = vehicle_output_string(manager, routing, assignment)\n", + " print(plan_output)\n", + " print('dropped nodes: ' + ', '.join(dropped))\n", + "\n", + " # you could print debug information like this:\n", + " # print(routing.DebugOutputAssignment(assignment, 'Capacity'))\n", + "\n", + " vehicle_routes = {}\n", + " for veh in range(vehicles.number):\n", + " vehicle_routes[veh] = build_vehicle_route(manager, routing, assignment,\n", + " customers, veh)\n", + "\n", + " # Plotting of the routes in matplotlib.\n", + " fig = plt.figure()\n", + " ax = fig.add_subplot(111)\n", + " # Plot all the nodes as black dots.\n", + " clon, clat = zip(*[(c.lon, c.lat) for c in customers.customers])\n", + " ax.plot(clon, clat, 'k.')\n", + " # plot the routes as arrows\n", + " plot_vehicle_routes(vehicle_routes, ax, customers, vehicles)\n", + " plt.show()\n", + "\n", + "else:\n", + " print('No assignment')\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/examples/flexible_job_shop_sat.ipynb b/examples/notebook/examples/flexible_job_shop_sat.ipynb new file mode 100644 index 0000000000..ad0c96894d --- /dev/null +++ b/examples/notebook/examples/flexible_job_shop_sat.ipynb @@ -0,0 +1,194 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010-2018 Google LLC\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"Solves a flexible jobshop problems with the CP-SAT solver.\"\"\"\n", + "\n", + "from __future__ import print_function\n", + "\n", + "import collections\n", + "from ortools.sat.python import cp_model\n", + "\n", + "\n", + "class SolutionPrinter(cp_model.CpSolverSolutionCallback):\n", + " \"\"\"Print intermediate solutions.\"\"\"\n", + "\n", + " def __init__(self):\n", + " cp_model.CpSolverSolutionCallback.__init__(self)\n", + " self.__solution_count = 0\n", + "\n", + " def on_solution_callback(self):\n", + " \"\"\"Called at each new solution.\"\"\"\n", + " print('Solution %i, time = %f s, objective = %i' %\n", + " (self.__solution_count, self.WallTime(), self.ObjectiveValue()))\n", + " self.__solution_count += 1\n", + "\n", + "\n", + "def flexible_jobshop():\n", + " \"\"\"Solve a small flexible jobshop problem.\"\"\"\n", + " # Data part.\n", + " jobs = [[[(3, 0), (1, 1), (5, 2)], [(2, 0), (4, 1), (6, 2)], [(2, 0), (3, 1), (1, 2)]],\n", + " [[(2, 0), (3, 1), (4, 2)], [(1, 0), (5, 1), (4, 2)], [(2, 0), (1, 1), (4, 2)]],\n", + " [[(2, 0), (1, 1), (4, 2)], [(2, 0), (3, 1), (4, 2)], [(3, 0), (1, 1), (5, 2)]]\n", + " ] # yapf:disable\n", + "\n", + " num_jobs = len(jobs)\n", + " all_jobs = range(num_jobs)\n", + "\n", + " num_machines = 3\n", + " all_machines = range(num_machines)\n", + "\n", + " # Model the flexible jobshop problem.\n", + " model = cp_model.CpModel()\n", + "\n", + " horizon = 0\n", + " for job in jobs:\n", + " for task in job:\n", + " max_task_duration = 0\n", + " for alternative in task:\n", + " max_task_duration = max(max_task_duration, alternative[0])\n", + " horizon += max_task_duration\n", + "\n", + " print('Horizon = %i' % horizon)\n", + "\n", + " # Global storage of variables.\n", + " intervals_per_resources = collections.defaultdict(list)\n", + " starts = {} # indexed by (job_id, task_id).\n", + " presences = {} # indexed by (job_id, task_id, alt_id).\n", + " job_ends = []\n", + "\n", + " # Scan the jobs and create the relevant variables and intervals.\n", + " for job_id in all_jobs:\n", + " job = jobs[job_id]\n", + " num_tasks = len(job)\n", + " previous_end = None\n", + " for task_id in range(num_tasks):\n", + " task = job[task_id]\n", + "\n", + " min_duration = task[0][0]\n", + " max_duration = task[0][0]\n", + "\n", + " num_alternatives = len(task)\n", + " all_alternatives = range(num_alternatives)\n", + "\n", + " for alt_id in range(1, num_alternatives):\n", + " alt_duration = task[alt_id][0]\n", + " min_duration = min(min_duration, alt_duration)\n", + " max_duration = max(max_duration, alt_duration)\n", + "\n", + " # Create main interval for the task.\n", + " suffix_name = '_j%i_t%i' % (job_id, task_id)\n", + " start = model.NewIntVar(0, horizon, 'start' + suffix_name)\n", + " duration = model.NewIntVar(min_duration, max_duration,\n", + " 'duration' + suffix_name)\n", + " end = model.NewIntVar(0, horizon, 'end' + suffix_name)\n", + " interval = model.NewIntervalVar(start, duration, end,\n", + " 'interval' + suffix_name)\n", + "\n", + " # Store the start for the solution.\n", + " starts[(job_id, task_id)] = start\n", + "\n", + " # Add precedence with previous task in the same job.\n", + " if previous_end:\n", + " model.Add(start >= previous_end)\n", + " previous_end = end\n", + "\n", + " # Create alternative intervals.\n", + " if num_alternatives > 1:\n", + " l_presences = []\n", + " for alt_id in all_alternatives:\n", + " alt_suffix = '_j%i_t%i_a%i' % (job_id, task_id, alt_id)\n", + " l_presence = model.NewBoolVar('presence' + alt_suffix)\n", + " l_start = model.NewIntVar(0, horizon, 'start' + alt_suffix)\n", + " l_duration = task[alt_id][0]\n", + " l_end = model.NewIntVar(0, horizon, 'end' + alt_suffix)\n", + " l_interval = model.NewOptionalIntervalVar(\n", + " l_start, l_duration, l_end, l_presence,\n", + " 'interval' + alt_suffix)\n", + " l_presences.append(l_presence)\n", + "\n", + " # Link the master variables with the local ones.\n", + " model.Add(start == l_start).OnlyEnforceIf(l_presence)\n", + " model.Add(duration == l_duration).OnlyEnforceIf(l_presence)\n", + " model.Add(end == l_end).OnlyEnforceIf(l_presence)\n", + "\n", + " # Add the local interval to the right machine.\n", + " intervals_per_resources[task[alt_id][1]].append(l_interval)\n", + "\n", + " # Store the presences for the solution.\n", + " presences[(job_id, task_id, alt_id)] = l_presence\n", + "\n", + " # Select exactly one presence variable.\n", + " model.Add(sum(l_presences) == 1)\n", + " else:\n", + " intervals_per_resources[task[0][1]].append(interval)\n", + " presences[(job_id, task_id, 0)] = model.NewConstant(1)\n", + "\n", + " job_ends.append(previous_end)\n", + "\n", + " # Create machines constraints.\n", + " for machine_id in all_machines:\n", + " intervals = intervals_per_resources[machine_id]\n", + " if len(intervals) > 1:\n", + " model.AddNoOverlap(intervals)\n", + "\n", + " # Makespan objective\n", + " makespan = model.NewIntVar(0, horizon, 'makespan')\n", + " model.AddMaxEquality(makespan, job_ends)\n", + " model.Minimize(makespan)\n", + "\n", + " # Solve model.\n", + " solver = cp_model.CpSolver()\n", + " solution_printer = SolutionPrinter()\n", + " status = solver.SolveWithSolutionCallback(model, solution_printer)\n", + "\n", + " # Print final solution.\n", + " for job_id in all_jobs:\n", + " print('Job %i:' % job_id)\n", + " for task_id in range(len(jobs[job_id])):\n", + " start_value = solver.Value(starts[(job_id, task_id)])\n", + " machine = -1\n", + " duration = -1\n", + " selected = -1\n", + " for alt_id in range(len(jobs[job_id][task_id])):\n", + " if solver.Value(presences[(job_id, task_id, alt_id)]):\n", + " duration = jobs[job_id][task_id][alt_id][0]\n", + " machine = jobs[job_id][task_id][alt_id][1]\n", + " selected = alt_id\n", + " print(\n", + " ' task_%i_%i starts at %i (alt %i, machine %i, duration %i)' %\n", + " (job_id, task_id, start_value, selected, machine, duration))\n", + "\n", + " print('Solve status: %s' % solver.StatusName(status))\n", + " print('Optimal objective value: %i' % solver.ObjectiveValue())\n", + " print('Statistics')\n", + " print(' - conflicts : %i' % solver.NumConflicts())\n", + " print(' - branches : %i' % solver.NumBranches())\n", + " print(' - wall time : %f s' % solver.WallTime())\n", + "\n", + "\n", + "flexible_jobshop()\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/examples/gate_scheduling_sat.ipynb b/examples/notebook/examples/gate_scheduling_sat.ipynb new file mode 100644 index 0000000000..9f99170374 --- /dev/null +++ b/examples/notebook/examples/gate_scheduling_sat.ipynb @@ -0,0 +1,147 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010-2018 Google LLC\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"Gate Scheduling problem.\n", + "\n", + "We have a set of jobs to perform (duration, width).\n", + "We have two parallel machines that can perform this job.\n", + "One machine can only perform one job at a time.\n", + "At any point in time, the sum of the width of the two active jobs does not\n", + "exceed a max_length.\n", + "\n", + "The objective is to minimize the max end time of all jobs.\n", + "\"\"\"\n", + "\n", + "from ortools.sat.python import cp_model\n", + "from ortools.sat.python import visualization\n", + "\n", + "\n", + "\"\"\"Solves the gate scheduling problem.\"\"\"\n", + "model = cp_model.CpModel()\n", + "\n", + "jobs = [[3, 3], [2, 5], [1, 3], [3, 7], [7, 3], [2, 2], [2, 2], [5, 5],\n", + " [10, 2], [4, 3], [2, 6], [1, 2], [6, 8], [4, 5], [3, 7]]\n", + "\n", + "max_length = 10\n", + "\n", + "horizon = sum(t[0] for t in jobs)\n", + "num_jobs = len(jobs)\n", + "all_jobs = range(num_jobs)\n", + "\n", + "intervals = []\n", + "intervals0 = []\n", + "intervals1 = []\n", + "performed = []\n", + "starts = []\n", + "ends = []\n", + "demands = []\n", + "\n", + "for i in all_jobs:\n", + " # Create main interval.\n", + " start = model.NewIntVar(0, horizon, 'start_%i' % i)\n", + " duration = jobs[i][0]\n", + " end = model.NewIntVar(0, horizon, 'end_%i' % i)\n", + " interval = model.NewIntervalVar(start, duration, end, 'interval_%i' % i)\n", + " starts.append(start)\n", + " intervals.append(interval)\n", + " ends.append(end)\n", + " demands.append(jobs[i][1])\n", + "\n", + " performed_on_m0 = model.NewBoolVar('perform_%i_on_m0' % i)\n", + " performed.append(performed_on_m0)\n", + "\n", + " # Create an optional copy of interval to be executed on machine 0.\n", + " start0 = model.NewIntVar(0, horizon, 'start_%i_on_m0' % i)\n", + " end0 = model.NewIntVar(0, horizon, 'end_%i_on_m0' % i)\n", + " interval0 = model.NewOptionalIntervalVar(\n", + " start0, duration, end0, performed_on_m0, 'interval_%i_on_m0' % i)\n", + " intervals0.append(interval0)\n", + "\n", + " # Create an optional copy of interval to be executed on machine 1.\n", + " start1 = model.NewIntVar(0, horizon, 'start_%i_on_m1' % i)\n", + " end1 = model.NewIntVar(0, horizon, 'end_%i_on_m1' % i)\n", + " interval1 = model.NewOptionalIntervalVar(start1, duration, end1,\n", + " performed_on_m0.Not(),\n", + " 'interval_%i_on_m1' % i)\n", + " intervals1.append(interval1)\n", + "\n", + " # We only propagate the constraint if the tasks is performed on the machine.\n", + " model.Add(start0 == start).OnlyEnforceIf(performed_on_m0)\n", + " model.Add(start1 == start).OnlyEnforceIf(performed_on_m0.Not())\n", + "\n", + "# Max Length constraint (modeled as a cumulative)\n", + "model.AddCumulative(intervals, demands, max_length)\n", + "\n", + "# Choose which machine to perform the jobs on.\n", + "model.AddNoOverlap(intervals0)\n", + "model.AddNoOverlap(intervals1)\n", + "\n", + "# Objective variable.\n", + "makespan = model.NewIntVar(0, horizon, 'makespan')\n", + "model.AddMaxEquality(makespan, ends)\n", + "model.Minimize(makespan)\n", + "\n", + "# Symmetry breaking.\n", + "model.Add(performed[0] == 0)\n", + "\n", + "# Solve model.\n", + "solver = cp_model.CpSolver()\n", + "solver.Solve(model)\n", + "\n", + "# Output solution.\n", + "if visualization.RunFromIPython():\n", + " output = visualization.SvgWrapper(solver.ObjectiveValue(), max_length,\n", + " 40.0)\n", + " output.AddTitle('Makespan = %i' % solver.ObjectiveValue())\n", + " color_manager = visualization.ColorManager()\n", + " color_manager.SeedRandomColor(0)\n", + "\n", + " for i in all_jobs:\n", + " performed_machine = 1 - solver.Value(performed[i])\n", + " start = solver.Value(starts[i])\n", + " d_x = jobs[i][0]\n", + " d_y = jobs[i][1]\n", + " s_y = performed_machine * (max_length - d_y)\n", + " output.AddRectangle(start, s_y, d_x, d_y,\n", + " color_manager.RandomColor(), 'black', 'j%i' % i)\n", + "\n", + " output.AddXScale()\n", + " output.AddYScale()\n", + " output.Display()\n", + "else:\n", + " print('Solution')\n", + " print(' - makespan = %i' % solver.ObjectiveValue())\n", + " for i in all_jobs:\n", + " performed_machine = 1 - solver.Value(performed[i])\n", + " start = solver.Value(starts[i])\n", + " print(' - Job %i starts at %i on machine %i' % (i, start,\n", + " performed_machine))\n", + " print('Statistics')\n", + " print(' - conflicts : %i' % solver.NumConflicts())\n", + " print(' - branches : %i' % solver.NumBranches())\n", + " print(' - wall time : %f s' % solver.WallTime())\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/examples/golomb8.ipynb b/examples/notebook/examples/golomb8.ipynb new file mode 100644 index 0000000000..934ef07139 --- /dev/null +++ b/examples/notebook/examples/golomb8.ipynb @@ -0,0 +1,86 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010-2018 Google LLC\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"This is the Golomb ruler problem.\n", + "\n", + "This model aims at maximizing radar interferences in a minimum space.\n", + "It is known as the Golomb Ruler problem.\n", + "\n", + "The idea is to put marks on a rule such that all differences\n", + "between all marks are all different. The objective is to minimize the length\n", + "of the rule.\n", + "\"\"\"\n", + "\n", + "from ortools.constraint_solver import pywrapcp\n", + "\n", + "# We disable the following warning because it is a false positive on constraints\n", + "# like: solver.Add(x == 0)\n", + "# pylint: disable=g-explicit-bool-comparison\n", + "\n", + "\n", + "# Create the solver.\n", + "solver = pywrapcp.Solver('golomb ruler')\n", + "\n", + "size = 8\n", + "var_max = size * size\n", + "all_vars = list(range(0, size))\n", + "\n", + "marks = [solver.IntVar(0, var_max, 'marks_%d' % i) for i in all_vars]\n", + "\n", + "objective = solver.Minimize(marks[size - 1], 1)\n", + "\n", + "solver.Add(marks[0] == 0)\n", + "solver.Add(\n", + " solver.AllDifferent([\n", + " marks[j] - marks[i]\n", + " for i in range(0, size - 1) for j in range(i + 1, size)\n", + " ]))\n", + "\n", + "solver.Add(marks[size - 1] - marks[size - 2] > marks[1] - marks[0])\n", + "for i in range(0, size - 2):\n", + " solver.Add(marks[i + 1] > marks[i])\n", + "\n", + "solution = solver.Assignment()\n", + "solution.Add(marks[size - 1])\n", + "collector = solver.AllSolutionCollector(solution)\n", + "\n", + "solver.Solve(\n", + " solver.Phase(marks, solver.CHOOSE_FIRST_UNBOUND,\n", + " solver.ASSIGN_MIN_VALUE), [objective, collector])\n", + "for i in range(0, collector.SolutionCount()):\n", + " obj_value = collector.Value(i, marks[size - 1])\n", + " time = collector.WallTime(i)\n", + " branches = collector.Branches(i)\n", + " failures = collector.Failures(i)\n", + " print(('Solution #%i: value = %i, failures = %i, branches = %i,'\n", + " 'time = %i ms') % (i, obj_value, failures, branches, time))\n", + "time = solver.WallTime()\n", + "branches = solver.Branches()\n", + "failures = solver.Failures()\n", + "print(('Total run : failures = %i, branches = %i, time = %i ms' %\n", + " (failures, branches, time)))\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/examples/hidato_sat.ipynb b/examples/notebook/examples/hidato_sat.ipynb new file mode 100644 index 0000000000..2f45dbde06 --- /dev/null +++ b/examples/notebook/examples/hidato_sat.ipynb @@ -0,0 +1,207 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010-2018 Google LLC\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"Solves the Hidato problem with the CP-SAT solver.\"\"\"\n", + "\n", + "from __future__ import print_function\n", + "\n", + "from ortools.sat.python import visualization\n", + "from ortools.sat.python import cp_model\n", + "\n", + "\n", + "def build_pairs(rows, cols):\n", + " \"\"\"Build closeness pairs for consecutive numbers.\n", + "\n", + " Build set of allowed pairs such that two consecutive numbers touch\n", + " each other in the grid.\n", + "\n", + " Returns:\n", + " A list of pairs for allowed consecutive position of numbers.\n", + "\n", + " Args:\n", + " rows: the number of rows in the grid\n", + " cols: the number of columns in the grid\n", + " \"\"\"\n", + " return [\n", + " (x * cols + y, (x + dx) * cols + (y + dy)) for x in range(rows)\n", + " for y in range(cols) for dx in (-1, 0, 1)\n", + " for dy in (-1, 0, 1)\n", + " if (x + dx >= 0 and x + dx < rows and y + dy >= 0 and y + dy < cols and\n", + " (dx != 0 or dy != 0))\n", + " ]\n", + "\n", + "\n", + "def print_solution(positions, rows, cols):\n", + " \"\"\"Print a current solution.\"\"\"\n", + " # Create empty board.\n", + " board = []\n", + " for _ in range(rows):\n", + " board.append([0] * cols)\n", + " # Fill board with solution value.\n", + " for k in range(rows * cols):\n", + " position = positions[k]\n", + " board[position // cols][position % cols] = k + 1\n", + " # Print the board.\n", + " print('Solution')\n", + " print_matrix(board)\n", + "\n", + "\n", + "def print_matrix(game):\n", + " \"\"\"Pretty print of a matrix.\"\"\"\n", + " rows = len(game)\n", + " cols = len(game[0])\n", + " for i in range(rows):\n", + " line = ''\n", + " for j in range(cols):\n", + " if game[i][j] == 0:\n", + " line += ' .'\n", + " else:\n", + " line += '% 3s' % game[i][j]\n", + " print(line)\n", + "\n", + "\n", + "def build_puzzle(problem):\n", + " \"\"\"Build the problem from its index.\"\"\"\n", + " #\n", + " # models, a 0 indicates an open cell which number is not yet known.\n", + " #\n", + " #\n", + " puzzle = None\n", + " if problem == 1:\n", + " # Simple problem\n", + " puzzle = [[6, 0, 9], [0, 2, 8], [1, 0, 0]]\n", + "\n", + " elif problem == 2:\n", + " puzzle = [[0, 44, 41, 0, 0, 0, 0], [0, 43, 0, 28, 29, 0, 0],\n", + " [0, 1, 0, 0, 0, 33, 0], [0, 2, 25, 4, 34, 0, 36],\n", + " [49, 16, 0, 23, 0, 0, 0], [0, 19, 0, 0, 12, 7,\n", + " 0], [0, 0, 0, 14, 0, 0, 0]]\n", + "\n", + " elif problem == 3:\n", + " # Problems from the book:\n", + " # Gyora Bededek: \"Hidato: 2000 Pure Logic Puzzles\"\n", + " # Problem 1 (Practice)\n", + " puzzle = [[0, 0, 20, 0, 0], [0, 0, 0, 16, 18], [22, 0, 15, 0, 0],\n", + " [23, 0, 1, 14, 11], [0, 25, 0, 0, 12]]\n", + "\n", + " elif problem == 4:\n", + " # problem 2 (Practice)\n", + " puzzle = [[0, 0, 0, 0, 14], [0, 18, 12, 0, 0], [0, 0, 17, 4, 5],\n", + " [0, 0, 7, 0, 0], [9, 8, 25, 1, 0]]\n", + "\n", + " elif problem == 5:\n", + " # problem 3 (Beginner)\n", + " puzzle = [[0, 26, 0, 0, 0, 18], [0, 0, 27, 0, 0, 19],\n", + " [31, 23, 0, 0, 14, 0], [0, 33, 8, 0, 15, 1],\n", + " [0, 0, 0, 5, 0, 0], [35, 36, 0, 10, 0, 0]]\n", + " elif problem == 6:\n", + " # Problem 15 (Intermediate)\n", + " puzzle = [[64, 0, 0, 0, 0, 0, 0, 0], [1, 63, 0, 59, 15, 57, 53, 0],\n", + " [0, 4, 0, 14, 0, 0, 0, 0], [3, 0, 11, 0, 20, 19, 0,\n", + " 50], [0, 0, 0, 0, 22, 0, 48, 40],\n", + " [9, 0, 0, 32, 23, 0, 0, 41], [27, 0, 0, 0, 36, 0, 46,\n", + " 0], [28, 30, 0, 35, 0, 0, 0, 0]]\n", + " return puzzle\n", + "\n", + "\n", + "def solve_hidato(puzzle, index):\n", + " \"\"\"Solve the given hidato table.\"\"\"\n", + " # Create the model.\n", + " model = cp_model.CpModel()\n", + "\n", + " r = len(puzzle)\n", + " c = len(puzzle[0])\n", + " if not visualization.RunFromIPython():\n", + " print('')\n", + " print('----- Solving problem %i -----' % index)\n", + " print('')\n", + " print(('Initial game (%i x %i)' % (r, c)))\n", + " print_matrix(puzzle)\n", + "\n", + " #\n", + " # declare variables\n", + " #\n", + " positions = [\n", + " model.NewIntVar(0, r * c - 1, 'p[%i]' % i) for i in range(r * c)\n", + " ]\n", + "\n", + " #\n", + " # constraints\n", + " #\n", + " model.AddAllDifferent(positions)\n", + "\n", + " #\n", + " # Fill in the clues\n", + " #\n", + " for i in range(r):\n", + " for j in range(c):\n", + " if puzzle[i][j] > 0:\n", + " model.Add(positions[puzzle[i][j] - 1] == i * c + j)\n", + "\n", + " # Consecutive numbers much touch each other in the grid.\n", + " # We use an allowed assignment constraint to model it.\n", + " close_tuples = build_pairs(r, c)\n", + " for k in range(0, r * c - 1):\n", + " model.AddAllowedAssignments([positions[k], positions[k + 1]],\n", + " close_tuples)\n", + "\n", + " #\n", + " # solution and search\n", + " #\n", + "\n", + " solver = cp_model.CpSolver()\n", + " status = solver.Solve(model)\n", + "\n", + " if status == cp_model.FEASIBLE:\n", + " if visualization.RunFromIPython():\n", + " output = visualization.SvgWrapper(10, r, 40.0)\n", + " for i, var in enumerate(positions):\n", + " val = solver.Value(var)\n", + " x = val % c\n", + " y = val // c\n", + " color = 'white' if puzzle[y][x] == 0 else 'lightgreen'\n", + " output.AddRectangle(x, r - y - 1, 1, 1, color, 'black',\n", + " str(i + 1))\n", + "\n", + " output.AddTitle('Puzzle %i solved in %f s' % (index,\n", + " solver.WallTime()))\n", + " output.Display()\n", + " else:\n", + " print_solution(\n", + " [solver.Value(x) for x in positions],\n", + " r,\n", + " c,\n", + " )\n", + " print('Statistics')\n", + " print(' - conflicts : %i' % solver.NumConflicts())\n", + " print(' - branches : %i' % solver.NumBranches())\n", + " print(' - wall time : %f s' % solver.WallTime())\n", + "\n", + "\n", + "for pb in range(1, 7):\n", + " solve_hidato(build_puzzle(pb), pb)\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/examples/integer_programming.ipynb b/examples/notebook/examples/integer_programming.ipynb new file mode 100644 index 0000000000..5424294a7c --- /dev/null +++ b/examples/notebook/examples/integer_programming.ipynb @@ -0,0 +1,153 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010-2018 Google LLC\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"Integer programming examples that show how to use the APIs.\"\"\"\n", + "\n", + "from __future__ import print_function\n", + "\n", + "from ortools.linear_solver import pywraplp\n", + "\n", + "\n", + "def RunIntegerExampleNaturalLanguageAPI(optimization_problem_type):\n", + " \"\"\"Example of simple integer program with natural language API.\"\"\"\n", + " solver = pywraplp.Solver('RunIntegerExampleNaturalLanguageAPI',\n", + " optimization_problem_type)\n", + " infinity = solver.infinity()\n", + " # x1 and x2 are integer non-negative variables.\n", + " x1 = solver.IntVar(0.0, infinity, 'x1')\n", + " x2 = solver.IntVar(0.0, infinity, 'x2')\n", + "\n", + " solver.Minimize(x1 + 2 * x2)\n", + " solver.Add(3 * x1 + 2 * x2 >= 17)\n", + "\n", + " SolveAndPrint(solver, [x1, x2])\n", + "\n", + "\n", + "def RunIntegerExampleCppStyleAPI(optimization_problem_type):\n", + " \"\"\"Example of simple integer program with the C++ style API.\"\"\"\n", + " solver = pywraplp.Solver('RunIntegerExampleCppStyleAPI',\n", + " optimization_problem_type)\n", + " infinity = solver.infinity()\n", + " # x1 and x2 are integer non-negative variables.\n", + " x1 = solver.IntVar(0.0, infinity, 'x1')\n", + " x2 = solver.IntVar(0.0, infinity, 'x2')\n", + "\n", + " # Minimize x1 + 2 * x2.\n", + " objective = solver.Objective()\n", + " objective.SetCoefficient(x1, 1)\n", + " objective.SetCoefficient(x2, 2)\n", + "\n", + " # 2 * x2 + 3 * x1 >= 17.\n", + " ct = solver.Constraint(17, infinity)\n", + " ct.SetCoefficient(x1, 3)\n", + " ct.SetCoefficient(x2, 2)\n", + "\n", + " SolveAndPrint(solver, [x1, x2])\n", + "\n", + "\n", + "def SolveAndPrint(solver, variable_list):\n", + " \"\"\"Solve the problem and print the solution.\"\"\"\n", + " print('Number of variables = %d' % solver.NumVariables())\n", + " print('Number of constraints = %d' % solver.NumConstraints())\n", + "\n", + " solver.SetNumThreads(8)\n", + " result_status = solver.Solve()\n", + "\n", + " # The problem has an optimal solution.\n", + " assert result_status == pywraplp.Solver.OPTIMAL\n", + "\n", + " # The solution looks legit (when using solvers others than\n", + " # GLOP_LINEAR_PROGRAMMING, verifying the solution is highly recommended!).\n", + " assert solver.VerifySolution(1e-7, True)\n", + "\n", + " print('Problem solved in %f milliseconds' % solver.wall_time())\n", + "\n", + " # The objective value of the solution.\n", + " print('Optimal objective value = %f' % solver.Objective().Value())\n", + "\n", + " # The value of each variable in the solution.\n", + " for variable in variable_list:\n", + " print('%s = %f' % (variable.name(), variable.solution_value()))\n", + "\n", + " print('Advanced usage:')\n", + " print('Problem solved in %d branch-and-bound nodes' % solver.nodes())\n", + "\n", + "\n", + "def Announce(solver, api_type):\n", + " print('---- Integer programming example with ' + solver + ' (' + api_type +\n", + " ') -----')\n", + "\n", + "\n", + "def RunAllIntegerExampleNaturalLanguageAPI():\n", + " if hasattr(pywraplp.Solver, 'GLPK_MIXED_INTEGER_PROGRAMMING'):\n", + " Announce('GLPK', 'natural language API')\n", + " RunIntegerExampleNaturalLanguageAPI(\n", + " pywraplp.Solver.GLPK_MIXED_INTEGER_PROGRAMMING)\n", + " if hasattr(pywraplp.Solver, 'CBC_MIXED_INTEGER_PROGRAMMING'):\n", + " Announce('CBC', 'natural language API')\n", + " RunIntegerExampleNaturalLanguageAPI(\n", + " pywraplp.Solver.CBC_MIXED_INTEGER_PROGRAMMING)\n", + " if hasattr(pywraplp.Solver, 'SCIP_MIXED_INTEGER_PROGRAMMING'):\n", + " Announce('SCIP', 'natural language API')\n", + " RunIntegerExampleNaturalLanguageAPI(\n", + " pywraplp.Solver.SCIP_MIXED_INTEGER_PROGRAMMING)\n", + " if hasattr(pywraplp.Solver, 'GUROBI_MIXED_INTEGER_PROGRAMMING'):\n", + " Announce('GUROBI', 'natural language API')\n", + " RunIntegerExampleNaturalLanguageAPI(\n", + " pywraplp.Solver.GUROBI_MIXED_INTEGER_PROGRAMMING)\n", + " if hasattr(pywraplp.Solver, 'CPLEX_MIXED_INTEGER_PROGRAMMING'):\n", + " Announce('CPLEX', 'natural language API')\n", + " RunIntegerExampleNaturalLanguageAPI(\n", + " pywraplp.Solver.CPLEX_MIXED_INTEGER_PROGRAMMING)\n", + "\n", + "\n", + "def RunAllIntegerExampleCppStyleAPI():\n", + " if hasattr(pywraplp.Solver, 'GLPK_MIXED_INTEGER_PROGRAMMING'):\n", + " Announce('GLPK', 'C++ style API')\n", + " RunIntegerExampleCppStyleAPI(\n", + " pywraplp.Solver.GLPK_MIXED_INTEGER_PROGRAMMING)\n", + " if hasattr(pywraplp.Solver, 'CBC_MIXED_INTEGER_PROGRAMMING'):\n", + " Announce('CBC', 'C++ style API')\n", + " RunIntegerExampleCppStyleAPI(\n", + " pywraplp.Solver.CBC_MIXED_INTEGER_PROGRAMMING)\n", + " if hasattr(pywraplp.Solver, 'SCIP_MIXED_INTEGER_PROGRAMMING'):\n", + " Announce('SCIP', 'C++ style API')\n", + " RunIntegerExampleCppStyleAPI(\n", + " pywraplp.Solver.SCIP_MIXED_INTEGER_PROGRAMMING)\n", + " if hasattr(pywraplp.Solver, 'GUROBI_MIXED_INTEGER_PROGRAMMING'):\n", + " Announce('GUROBI', 'C++ style API')\n", + " RunIntegerExampleCppStyleAPI(\n", + " pywraplp.Solver.GUROBI_MIXED_INTEGER_PROGRAMMING)\n", + " if hasattr(pywraplp.Solver, 'CPLEX_MIXED_INTEGER_PROGRAMMING'):\n", + " Announce('CPLEX', 'C++ style API')\n", + " RunIntegerExampleCppStyleAPI(\n", + " pywraplp.Solver.CPLEX_MIXED_INTEGER_PROGRAMMING)\n", + "\n", + "\n", + "RunAllIntegerExampleNaturalLanguageAPI()\n", + "RunAllIntegerExampleCppStyleAPI()\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/examples/jobshop_ft06_distance_sat.ipynb b/examples/notebook/examples/jobshop_ft06_distance_sat.ipynb new file mode 100644 index 0000000000..48f959b8b9 --- /dev/null +++ b/examples/notebook/examples/jobshop_ft06_distance_sat.ipynb @@ -0,0 +1,146 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010-2018 Google LLC\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"This model implements a variation of the ft06 jobshop.\n", + "\n", + "A jobshop is a standard scheduling problem when you must sequence a\n", + "series of tasks on a set of machines. Each job contains one task per\n", + "machine. The order of execution and the length of each job on each\n", + "machine is task dependent.\n", + "\n", + "The objective is to minimize the maximum completion time of all\n", + "jobs. This is called the makespan.\n", + "\n", + "This variation introduces a minimum distance between all the jobs on each\n", + "machine.\n", + "\"\"\"\n", + "\n", + "from __future__ import print_function\n", + "\n", + "import collections\n", + "\n", + "from ortools.sat.python import cp_model\n", + "\n", + "\n", + "def distance_between_jobs(x, y):\n", + " \"\"\"Returns the distance between tasks of job x and tasks of job y.\"\"\"\n", + " return abs(x - y)\n", + "\n", + "\n", + "def jobshop_ft06_distance():\n", + " \"\"\"Solves the ft06 jobshop with distances between tasks.\"\"\"\n", + " # Creates the model.\n", + " model = cp_model.CpModel()\n", + "\n", + " machines_count = 6\n", + " jobs_count = 6\n", + " all_machines = range(0, machines_count)\n", + " all_jobs = range(0, jobs_count)\n", + "\n", + " durations = [[1, 3, 6, 7, 3, 6], [8, 5, 10, 10, 10, 4], [5, 4, 8, 9, 1, 7],\n", + " [5, 5, 5, 3, 8, 9], [9, 3, 5, 4, 3, 1], [3, 3, 9, 10, 4, 1]]\n", + "\n", + " machines = [[2, 0, 1, 3, 5, 4], [1, 2, 4, 5, 0, 3], [2, 3, 5, 0, 1, 4],\n", + " [1, 0, 2, 3, 4, 5], [2, 1, 4, 5, 0, 3], [1, 3, 5, 0, 4, 2]]\n", + "\n", + " # Computes horizon statically.\n", + " horizon = 150\n", + "\n", + " task_type = collections.namedtuple('task_type', 'start end interval')\n", + "\n", + " # Creates jobs.\n", + " all_tasks = {}\n", + " for i in all_jobs:\n", + " for j in all_machines:\n", + " start_var = model.NewIntVar(0, horizon, 'start_%i_%i' % (i, j))\n", + " duration = durations[i][j]\n", + " end_var = model.NewIntVar(0, horizon, 'end_%i_%i' % (i, j))\n", + " interval_var = model.NewIntervalVar(start_var, duration, end_var,\n", + " 'interval_%i_%i' % (i, j))\n", + " all_tasks[(i, j)] = task_type(\n", + " start=start_var, end=end_var, interval=interval_var)\n", + "\n", + " # Create disjuctive constraints.\n", + " for i in all_machines:\n", + " job_intervals = []\n", + " job_indices = []\n", + " job_starts = []\n", + " job_ends = []\n", + " for j in all_jobs:\n", + " for k in all_machines:\n", + " if machines[j][k] == i:\n", + " job_intervals.append(all_tasks[(j, k)].interval)\n", + " job_indices.append(j)\n", + " job_starts.append(all_tasks[(j, k)].start)\n", + " job_ends.append(all_tasks[(j, k)].end)\n", + " model.AddNoOverlap(job_intervals)\n", + "\n", + " arcs = []\n", + " for j1 in range(len(job_intervals)):\n", + " # Initial arc from the dummy node (0) to a task.\n", + " start_lit = model.NewBoolVar('%i is first job' % j1)\n", + " arcs.append([0, j1 + 1, start_lit])\n", + " # Final arc from an arc to the dummy node.\n", + " arcs.append([j1 + 1, 0, model.NewBoolVar('%i is last job' % j1)])\n", + "\n", + " for j2 in range(len(job_intervals)):\n", + " if j1 == j2:\n", + " continue\n", + "\n", + " lit = model.NewBoolVar('%i follows %i' % (j2, j1))\n", + " arcs.append([j1 + 1, j2 + 1, lit])\n", + "\n", + " # We add the reified precedence to link the literal with the\n", + " # times of the two tasks.\n", + " min_distance = distance_between_jobs(j1, j2)\n", + " model.Add(job_starts[j2] >=\n", + " job_ends[j1] + min_distance).OnlyEnforceIf(lit)\n", + "\n", + " model.AddCircuit(arcs)\n", + "\n", + " # Precedences inside a job.\n", + " for i in all_jobs:\n", + " for j in range(0, machines_count - 1):\n", + " model.Add(all_tasks[(i, j + 1)].start >= all_tasks[(i, j)].end)\n", + "\n", + " # Makespan objective.\n", + " obj_var = model.NewIntVar(0, horizon, 'makespan')\n", + " model.AddMaxEquality(\n", + " obj_var, [all_tasks[(i, machines_count - 1)].end for i in all_jobs])\n", + " model.Minimize(obj_var)\n", + "\n", + " # Solve model.\n", + " solver = cp_model.CpSolver()\n", + " status = solver.Solve(model)\n", + "\n", + " # Output solution.\n", + " if status == cp_model.OPTIMAL:\n", + " print('Optimal makespan: %i' % solver.ObjectiveValue())\n", + "\n", + "\n", + "jobshop_ft06_distance()\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/examples/jobshop_ft06_sat.ipynb b/examples/notebook/examples/jobshop_ft06_sat.ipynb new file mode 100644 index 0000000000..13719778c0 --- /dev/null +++ b/examples/notebook/examples/jobshop_ft06_sat.ipynb @@ -0,0 +1,118 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010-2018 Google LLC\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"This model implements a simple jobshop named ft06.\n", + "\n", + "A jobshop is a standard scheduling problem when you must sequence a\n", + "series of task_types on a set of machines. Each job contains one task_type per\n", + "machine. The order of execution and the length of each job on each\n", + "machine is task_type dependent.\n", + "\n", + "The objective is to minimize the maximum completion time of all\n", + "jobs. This is called the makespan.\n", + "\"\"\"\n", + "from __future__ import print_function\n", + "\n", + "import collections\n", + "\n", + "from ortools.sat.python import visualization\n", + "from ortools.sat.python import cp_model\n", + "\n", + "\n", + "def jobshop_ft06():\n", + " \"\"\"Solves the ft06 jobshop.\"\"\"\n", + " # Creates the solver.\n", + " model = cp_model.CpModel()\n", + "\n", + " machines_count = 6\n", + " jobs_count = 6\n", + " all_machines = range(0, machines_count)\n", + " all_jobs = range(0, jobs_count)\n", + "\n", + " durations = [[1, 3, 6, 7, 3, 6], [8, 5, 10, 10, 10, 4], [5, 4, 8, 9, 1, 7],\n", + " [5, 5, 5, 3, 8, 9], [9, 3, 5, 4, 3, 1], [3, 3, 9, 10, 4, 1]]\n", + "\n", + " machines = [[2, 0, 1, 3, 5, 4], [1, 2, 4, 5, 0, 3], [2, 3, 5, 0, 1, 4],\n", + " [1, 0, 2, 3, 4, 5], [2, 1, 4, 5, 0, 3], [1, 3, 5, 0, 4, 2]]\n", + "\n", + " # Computes horizon dynamically.\n", + " horizon = sum([sum(durations[i]) for i in all_jobs])\n", + "\n", + " task_type = collections.namedtuple('task_type', 'start end interval')\n", + "\n", + " # Creates jobs.\n", + " all_tasks = {}\n", + " for i in all_jobs:\n", + " for j in all_machines:\n", + " start_var = model.NewIntVar(0, horizon, 'start_%i_%i' % (i, j))\n", + " duration = durations[i][j]\n", + " end_var = model.NewIntVar(0, horizon, 'end_%i_%i' % (i, j))\n", + " interval_var = model.NewIntervalVar(start_var, duration, end_var,\n", + " 'interval_%i_%i' % (i, j))\n", + " all_tasks[(i, j)] = task_type(\n", + " start=start_var, end=end_var, interval=interval_var)\n", + "\n", + " # Create disjuctive constraints.\n", + " machine_to_jobs = {}\n", + " for i in all_machines:\n", + " machines_jobs = []\n", + " for j in all_jobs:\n", + " for k in all_machines:\n", + " if machines[j][k] == i:\n", + " machines_jobs.append(all_tasks[(j, k)].interval)\n", + " machine_to_jobs[i] = machines_jobs\n", + " model.AddNoOverlap(machines_jobs)\n", + "\n", + " # Precedences inside a job.\n", + " for i in all_jobs:\n", + " for j in range(0, machines_count - 1):\n", + " model.Add(all_tasks[(i, j + 1)].start >= all_tasks[(i, j)].end)\n", + "\n", + " # Makespan objective.\n", + " obj_var = model.NewIntVar(0, horizon, 'makespan')\n", + " model.AddMaxEquality(\n", + " obj_var, [all_tasks[(i, machines_count - 1)].end for i in all_jobs])\n", + " model.Minimize(obj_var)\n", + "\n", + " # Solve model.\n", + " solver = cp_model.CpSolver()\n", + " solver.parameters.log_search_progress = True\n", + " status = solver.Solve(model)\n", + "\n", + " # Output solution.\n", + " if status == cp_model.OPTIMAL:\n", + " if visualization.RunFromIPython():\n", + " starts = [[\n", + " solver.Value(all_tasks[(i, j)][0]) for j in all_machines\n", + " ] for i in all_jobs]\n", + " visualization.DisplayJobshop(starts, durations, machines, 'FT06')\n", + " else:\n", + " print('Optimal makespan: %i' % solver.ObjectiveValue())\n", + "\n", + "\n", + "jobshop_ft06()\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/examples/jobshop_with_maintenance_sat.ipynb b/examples/notebook/examples/jobshop_with_maintenance_sat.ipynb new file mode 100644 index 0000000000..5b598a4f51 --- /dev/null +++ b/examples/notebook/examples/jobshop_with_maintenance_sat.ipynb @@ -0,0 +1,147 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010-2018 Google\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"Jobshop with maintenance tasks using the CP-SAT solver.\"\"\"\n", + "\n", + "from __future__ import absolute_import\n", + "from __future__ import print_function\n", + "\n", + "import collections\n", + "\n", + "from ortools.sat.python import cp_model\n", + "\n", + "\n", + "def jobshop_with_maintenance():\n", + " \"\"\"Solves a jobshop with maintenance on one machine.\"\"\"\n", + " # Create the model.\n", + " model = cp_model.CpModel()\n", + "\n", + " jobs_data = [ # task = (machine_id, processing_time).\n", + " [(0, 3), (1, 2), (2, 2)], # Job0\n", + " [(0, 2), (2, 1), (1, 4)], # Job1\n", + " [(1, 4), (2, 3)] # Job2\n", + " ]\n", + "\n", + " machines_count = 1 + max(task[0] for job in jobs_data for task in job)\n", + " all_machines = range(machines_count)\n", + "\n", + " # Computes horizon dynamically as the sum of all durations.\n", + " horizon = sum(task[1] for job in jobs_data for task in job)\n", + "\n", + " # Named tuple to store information about created variables.\n", + " task_type = collections.namedtuple('Task', 'start end interval')\n", + " # Named tuple to manipulate solution information.\n", + " assigned_task_type = collections.namedtuple('assigned_task_type',\n", + " 'start job index duration')\n", + "\n", + " # Creates job intervals and add to the corresponding machine lists.\n", + " all_tasks = {}\n", + " machine_to_intervals = collections.defaultdict(list)\n", + "\n", + " for job_id, job in enumerate(jobs_data):\n", + " for task_id, task in enumerate(job):\n", + " machine = task[0]\n", + " duration = task[1]\n", + " suffix = '_%i_%i' % (job_id, task_id)\n", + " start_var = model.NewIntVar(0, horizon, 'start' + suffix)\n", + " end_var = model.NewIntVar(0, horizon, 'end' + suffix)\n", + " interval_var = model.NewIntervalVar(start_var, duration, end_var,\n", + " 'interval' + suffix)\n", + " all_tasks[job_id, task_id] = task_type(\n", + " start=start_var, end=end_var, interval=interval_var)\n", + " machine_to_intervals[machine].append(interval_var)\n", + "\n", + " # Add maintenance interval (machine 0 is not available on time {4, 5, 6, 7}).\n", + " machine_to_intervals[0].append(model.NewIntervalVar(4, 4, 8, 'weekend_0'))\n", + "\n", + " # Create and add disjunctive constraints.\n", + " for machine in all_machines:\n", + " model.AddNoOverlap(machine_to_intervals[machine])\n", + "\n", + " # Precedences inside a job.\n", + " for job_id, job in enumerate(jobs_data):\n", + " for task_id in range(len(job) - 1):\n", + " model.Add(all_tasks[job_id, task_id +\n", + " 1].start >= all_tasks[job_id, task_id].end)\n", + "\n", + " # Makespan objective.\n", + " obj_var = model.NewIntVar(0, horizon, 'makespan')\n", + " model.AddMaxEquality(obj_var, [\n", + " all_tasks[job_id, len(job) - 1].end\n", + " for job_id, job in enumerate(jobs_data)\n", + " ])\n", + " model.Minimize(obj_var)\n", + "\n", + " # Solve model.\n", + " solver = cp_model.CpSolver()\n", + " status = solver.Solve(model)\n", + "\n", + " # Output solution.\n", + " if status == cp_model.OPTIMAL:\n", + " # Create one list of assigned tasks per machine.\n", + " assigned_jobs = collections.defaultdict(list)\n", + " for job_id, job in enumerate(jobs_data):\n", + " for task_id, task in enumerate(job):\n", + " machine = task[0]\n", + " assigned_jobs[machine].append(\n", + " assigned_task_type(\n", + " start=solver.Value(all_tasks[job_id, task_id].start),\n", + " job=job_id,\n", + " index=task_id,\n", + " duration=task[1]))\n", + "\n", + " # Create per machine output lines.\n", + " output = ''\n", + " for machine in all_machines:\n", + " # Sort by starting time.\n", + " assigned_jobs[machine].sort()\n", + " sol_line_tasks = 'Machine ' + str(machine) + ': '\n", + " sol_line = ' '\n", + "\n", + " for assigned_task in assigned_jobs[machine]:\n", + " name = 'job_%i_%i' % (assigned_task.job, assigned_task.index)\n", + " # Add spaces to output to align columns.\n", + " sol_line_tasks += '%-10s' % name\n", + " start = assigned_task.start\n", + " duration = assigned_task.duration\n", + "\n", + " sol_tmp = '[%i,%i]' % (start, start + duration)\n", + " # Add spaces to output to align columns.\n", + " sol_line += '%-10s' % sol_tmp\n", + "\n", + " sol_line += '\\n'\n", + " sol_line_tasks += '\\n'\n", + " output += sol_line_tasks\n", + " output += sol_line\n", + "\n", + " # Finally print the solution found.\n", + " print('Optimal Schedule Length: %i' % solver.ObjectiveValue())\n", + " print(output)\n", + "\n", + "\n", + "jobshop_with_maintenance()\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/examples/linear_assignment_api.ipynb b/examples/notebook/examples/linear_assignment_api.ipynb new file mode 100644 index 0000000000..1a140b5b83 --- /dev/null +++ b/examples/notebook/examples/linear_assignment_api.ipynb @@ -0,0 +1,68 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010-2018 Google LLC\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"Test linear sum assignment on a 4x4 matrix.\n", + "\n", + " Example taken from:\n", + " http://www.ee.oulu.fi/~mpa/matreng/eem1_2-1.htm with kCost[0][1]\n", + " modified so the optimum solution is unique.\n", + "\"\"\"\n", + "from __future__ import print_function\n", + "\n", + "from ortools.graph import pywrapgraph\n", + "\n", + "\n", + "def RunAssignmentOn4x4Matrix():\n", + " \"\"\"Test linear sum assignment on a 4x4 matrix.\n", + " \"\"\"\n", + " num_sources = 4\n", + " num_targets = 4\n", + " cost = [[90, 76, 75, 80], [35, 85, 55, 65], [125, 95, 90, 105],\n", + " [45, 110, 95, 115]]\n", + " expected_cost = cost[0][3] + cost[1][2] + cost[2][1] + cost[3][0]\n", + "\n", + " assignment = pywrapgraph.LinearSumAssignment()\n", + " for source in range(0, num_sources):\n", + " for target in range(0, num_targets):\n", + " assignment.AddArcWithCost(source, target, cost[source][target])\n", + "\n", + " solve_status = assignment.Solve()\n", + " if solve_status == assignment.OPTIMAL:\n", + " print('Successful solve.')\n", + " print('Total cost', assignment.OptimalCost(), '/', expected_cost)\n", + " for i in range(0, assignment.NumNodes()):\n", + " print('Left node %d assigned to right node %d with cost %d.' %\n", + " (i, assignment.RightMate(i), assignment.AssignmentCost(i)))\n", + " elif solve_status == assignment.INFEASIBLE:\n", + " print('No perfect matching exists.')\n", + " elif solve_status == assignment.POSSIBLE_OVERFLOW:\n", + " print(\n", + " 'Some input costs are too large and may cause an integer overflow.')\n", + "\n", + "\n", + "RunAssignmentOn4x4Matrix()\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/examples/linear_programming.ipynb b/examples/notebook/examples/linear_programming.ipynb new file mode 100644 index 0000000000..850171be0d --- /dev/null +++ b/examples/notebook/examples/linear_programming.ipynb @@ -0,0 +1,96 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "#!/usr/bin/env python\n", + "# This Python file uses the following encoding: utf-8\n", + "# Copyright 2018 Google LLC\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"Linear optimization example\"\"\"\n", + "\n", + "from __future__ import print_function\n", + "from ortools.linear_solver import pywraplp\n", + "\n", + "\n", + "\"\"\"Entry point of the program\"\"\"\n", + "# Instantiate a Glop solver, naming it LinearExample.\n", + "solver = pywraplp.Solver('LinearExample',\n", + " pywraplp.Solver.GLOP_LINEAR_PROGRAMMING)\n", + "\n", + "# Create the two variables and let them take on any value.\n", + "x = solver.NumVar(-solver.infinity(), solver.infinity(), 'x')\n", + "y = solver.NumVar(-solver.infinity(), solver.infinity(), 'y')\n", + "\n", + "# Objective function: Maximize 3x + 4y.\n", + "objective = solver.Objective()\n", + "objective.SetCoefficient(x, 3)\n", + "objective.SetCoefficient(y, 4)\n", + "objective.SetMaximization()\n", + "\n", + "# Constraint 0: x + 2y <= 14.\n", + "constraint0 = solver.Constraint(-solver.infinity(), 14)\n", + "constraint0.SetCoefficient(x, 1)\n", + "constraint0.SetCoefficient(y, 2)\n", + "\n", + "# Constraint 1: 3x - y >= 0.\n", + "constraint1 = solver.Constraint(0, solver.infinity())\n", + "constraint1.SetCoefficient(x, 3)\n", + "constraint1.SetCoefficient(y, -1)\n", + "\n", + "# Constraint 2: x - y <= 2.\n", + "constraint2 = solver.Constraint(-solver.infinity(), 2)\n", + "constraint2.SetCoefficient(x, 1)\n", + "constraint2.SetCoefficient(y, -1)\n", + "\n", + "print('Number of variables =', solver.NumVariables())\n", + "print('Number of constraints =', solver.NumConstraints())\n", + "\n", + "# Solve the system.\n", + "status = solver.Solve()\n", + "# Check that the problem has an optimal solution.\n", + "if status != pywraplp.Solver.OPTIMAL:\n", + " print(\"The problem does not have an optimal solution!\")\n", + " exit(1)\n", + "\n", + "print('Solution:')\n", + "print('x =', x.solution_value())\n", + "print('y =', y.solution_value())\n", + "print('Optimal objective value =', objective.Value())\n", + "print('')\n", + "print('Advanced usage:')\n", + "print('Problem solved in ', solver.wall_time(), ' milliseconds')\n", + "print('Problem solved in ', solver.iterations(), ' iterations')\n", + "print('x: reduced cost =', x.reduced_cost())\n", + "print('y: reduced cost =', y.reduced_cost())\n", + "activities = solver.ComputeConstraintActivities()\n", + "print('constraint0: dual value =',\n", + " constraint0.dual_value(), ' activities =',\n", + " activities[constraint0.index()])\n", + "print('constraint1: dual value =',\n", + " constraint1.dual_value(), ' activities =',\n", + " activities[constraint1.index()])\n", + "print('constraint2: dual value =',\n", + " constraint2.dual_value(), ' activities =',\n", + " activities[constraint2.index()])\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/examples/magic_sequence_distribute.ipynb b/examples/notebook/examples/magic_sequence_distribute.ipynb new file mode 100644 index 0000000000..5e3a08ea6d --- /dev/null +++ b/examples/notebook/examples/magic_sequence_distribute.ipynb @@ -0,0 +1,56 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010-2018 Google LLC\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"Magic sequence problem.\n", + "\n", + "This models aims at building a sequence of numbers such that the number of\n", + "occurrences of i in this sequence is equal to the value of the ith number.\n", + "It uses an aggregated formulation of the count expression called\n", + "distribute().\n", + "\"\"\"\n", + "from __future__ import print_function\n", + "\n", + "from ortools.constraint_solver import pywrapcp\n", + "\n", + "\n", + "# Create the solver.\n", + "solver = pywrapcp.Solver('magic sequence')\n", + "\n", + "size = 100\n", + "all_values = list(range(0, size))\n", + "all_vars = [solver.IntVar(0, size, 'vars_%d' % i) for i in all_values]\n", + "\n", + "solver.Add(solver.Distribute(all_vars, all_values, all_vars))\n", + "solver.Add(solver.Sum(all_vars) == size)\n", + "\n", + "solver.NewSearch(\n", + " solver.Phase(all_vars, solver.CHOOSE_FIRST_UNBOUND,\n", + " solver.ASSIGN_MIN_VALUE))\n", + "solver.NextSolution()\n", + "print(all_vars)\n", + "solver.EndSearch()\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/nqueens_sat.ipynb b/examples/notebook/examples/nqueens_sat.ipynb similarity index 59% rename from examples/notebook/nqueens_sat.ipynb rename to examples/notebook/examples/nqueens_sat.ipynb index 795ba865b3..fafa95b7d8 100644 --- a/examples/notebook/nqueens_sat.ipynb +++ b/examples/notebook/examples/nqueens_sat.ipynb @@ -6,7 +6,7 @@ "metadata": {}, "outputs": [], "source": [ - "# Copyright 2010-2017 Google\n", + "# Copyright 2010-2018 Google LLC\n", "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", "# you may not use this file except in compliance with the License.\n", "# You may obtain a copy of the License at\n", @@ -26,33 +26,33 @@ "\n", "\n", "class NQueenSolutionPrinter(cp_model.CpSolverSolutionCallback):\n", - " \"\"\"Print intermediate solutions.\"\"\"\n", + " \"\"\"Print intermediate solutions.\"\"\"\n", "\n", - " def __init__(self, queens):\n", - " cp_model.CpSolverSolutionCallback.__init__(self)\n", - " self.__queens = queens\n", - " self.__solution_count = 0\n", - " self.__start_time = time.time()\n", + " def __init__(self, queens):\n", + " cp_model.CpSolverSolutionCallback.__init__(self)\n", + " self.__queens = queens\n", + " self.__solution_count = 0\n", + " self.__start_time = time.time()\n", "\n", - " def SolutionCount(self):\n", - " return self.__solution_count\n", + " def solution_count(self):\n", + " return self.__solution_count\n", "\n", - " def OnSolutionCallback(self):\n", - " current_time = time.time()\n", - " print('Solution %i, time = %f s' % (self.__solution_count,\n", - " current_time - self.__start_time))\n", - " self.__solution_count += 1\n", + " def on_solution_callback(self):\n", + " current_time = time.time()\n", + " print('Solution %i, time = %f s' % (self.__solution_count,\n", + " current_time - self.__start_time))\n", + " self.__solution_count += 1\n", "\n", - " all_queens = range(len(self.__queens))\n", - " for i in all_queens:\n", - " for j in all_queens:\n", - " if self.Value(self.__queens[j]) == i:\n", - " # There is a queen in column j, row i.\n", - " print('Q', end=' ')\n", - " else:\n", - " print('_', end=' ')\n", - " print()\n", - " print()\n", + " all_queens = range(len(self.__queens))\n", + " for i in all_queens:\n", + " for j in all_queens:\n", + " if self.Value(self.__queens[j]) == i:\n", + " # There is a queen in column j, row i.\n", + " print('Q', end=' ')\n", + " else:\n", + " print('_', end=' ')\n", + " print()\n", + " print()\n", "\n", "\n", "# Creates the solver.\n", @@ -74,12 +74,12 @@ "diag1 = []\n", "diag2 = []\n", "for i in range(board_size):\n", - " q1 = model.NewIntVar(0, 2 * board_size, 'diag1_%i' % i)\n", - " q2 = model.NewIntVar(-board_size, board_size, 'diag2_%i' % i)\n", - " diag1.append(q1)\n", - " diag2.append(q2)\n", - " model.Add(q1 == queens[i] + i)\n", - " model.Add(q2 == queens[i] - i)\n", + " q1 = model.NewIntVar(0, 2 * board_size, 'diag1_%i' % i)\n", + " q2 = model.NewIntVar(-board_size, board_size, 'diag2_%i' % i)\n", + " diag1.append(q1)\n", + " diag2.append(q2)\n", + " model.Add(q1 == queens[i] + i)\n", + " model.Add(q2 == queens[i] - i)\n", "model.AddAllDifferent(diag1)\n", "model.AddAllDifferent(diag2)\n", "\n", @@ -92,8 +92,8 @@ "print('Statistics')\n", "print(' - conflicts : %i' % solver.NumConflicts())\n", "print(' - branches : %i' % solver.NumBranches())\n", - "print(' - wall time : %f ms' % solver.WallTime())\n", - "print(' - solutions found : %i' % solution_printer.SolutionCount())\n", + "print(' - wall time : %f s' % solver.WallTime())\n", + "print(' - solutions found : %i' % solution_printer.solution_count())\n", "\n", "\n", "# By default, solve the 8x8 problem.board_size = 8\n", @@ -103,5 +103,5 @@ ], "metadata": {}, "nbformat": 4, - "nbformat_minor": 2 + "nbformat_minor": 4 } diff --git a/examples/notebook/examples/pyflow_example.ipynb b/examples/notebook/examples/pyflow_example.ipynb new file mode 100644 index 0000000000..77a661c592 --- /dev/null +++ b/examples/notebook/examples/pyflow_example.ipynb @@ -0,0 +1,91 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010-2018 Google LLC\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"MaxFlow and MinCostFlow examples.\"\"\"\n", + "\n", + "from __future__ import print_function\n", + "from ortools.graph import pywrapgraph\n", + "\n", + "\n", + "def MaxFlow():\n", + " \"\"\"MaxFlow simple interface example.\"\"\"\n", + " print('MaxFlow on a simple network.')\n", + " tails = [0, 0, 0, 0, 1, 2, 3, 3, 4]\n", + " heads = [1, 2, 3, 4, 3, 4, 4, 5, 5]\n", + " capacities = [5, 8, 5, 3, 4, 5, 6, 6, 4]\n", + " expected_total_flow = 10\n", + " max_flow = pywrapgraph.SimpleMaxFlow()\n", + " for i in range(0, len(tails)):\n", + " max_flow.AddArcWithCapacity(tails[i], heads[i], capacities[i])\n", + " if max_flow.Solve(0, 5) == max_flow.OPTIMAL:\n", + " print('Total flow', max_flow.OptimalFlow(), '/', expected_total_flow)\n", + " for i in range(max_flow.NumArcs()):\n", + " print(('From source %d to target %d: %d / %d' %\n", + " (max_flow.Tail(i), max_flow.Head(i), max_flow.Flow(i),\n", + " max_flow.Capacity(i))))\n", + " print('Source side min-cut:', max_flow.GetSourceSideMinCut())\n", + " print('Sink side min-cut:', max_flow.GetSinkSideMinCut())\n", + " else:\n", + " print('There was an issue with the max flow input.')\n", + "\n", + "\n", + "def MinCostFlow():\n", + " \"\"\"MinCostFlow simple interface example.\n", + "\n", + " Note that this example is actually a linear sum assignment example and will\n", + " be more efficiently solved with the pywrapgraph.LinearSumAssignement class.\n", + " \"\"\"\n", + " print('MinCostFlow on 4x4 matrix.')\n", + " num_sources = 4\n", + " num_targets = 4\n", + " costs = [[90, 75, 75, 80], [35, 85, 55, 65], [125, 95, 90, 105],\n", + " [45, 110, 95, 115]]\n", + " expected_cost = 275\n", + " min_cost_flow = pywrapgraph.SimpleMinCostFlow()\n", + " for source in range(0, num_sources):\n", + " for target in range(0, num_targets):\n", + " min_cost_flow.AddArcWithCapacityAndUnitCost(\n", + " source, num_sources + target, 1, costs[source][target])\n", + " for node in range(0, num_sources):\n", + " min_cost_flow.SetNodeSupply(node, 1)\n", + " min_cost_flow.SetNodeSupply(num_sources + node, -1)\n", + " status = min_cost_flow.Solve()\n", + " if status == min_cost_flow.OPTIMAL:\n", + " print('Total flow', min_cost_flow.OptimalCost(), '/', expected_cost)\n", + " for i in range(0, min_cost_flow.NumArcs()):\n", + " if min_cost_flow.Flow(i) > 0:\n", + " print('From source %d to target %d: cost %d' %\n", + " (min_cost_flow.Tail(i),\n", + " min_cost_flow.Head(i) - num_sources,\n", + " min_cost_flow.UnitCost(i)))\n", + " else:\n", + " print('There was an issue with the min cost flow input.')\n", + "\n", + "\n", + "MaxFlow()\n", + "MinCostFlow()\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/examples/qubo_sat.ipynb b/examples/notebook/examples/qubo_sat.ipynb new file mode 100644 index 0000000000..8789c6b8a2 --- /dev/null +++ b/examples/notebook/examples/qubo_sat.ipynb @@ -0,0 +1,600 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "\"\"\"Solves a Qubo program using the CP-SAT solver.\"\"\"\n", + "\n", + "from __future__ import print_function\n", + "\n", + "import time\n", + "\n", + "from ortools.sat.python import cp_model\n", + "\n", + "RAW_DATA = [[\n", + " 0, 0, 49.774821, -59.5968886, -46.0773896, 0, -65.166109, 0, 0, 0, 0, 0, 0,\n", + " 0, 0, 0, 0, 47.0957778, 15.259961, -98.7983264, 0, 0, 0, -20.7757184, 0,\n", + " 87.2645672, 0, -22.7888772, 0, 0, 0, -40.4980904, -19.7307486, -23.222078,\n", + " 0, -77.5263128, 0, 0, 56.6204008, 0, 0, 0, 0, 0, 0, 0, 31.378421, 0,\n", + " 97.3441448, 35.1309806, 0, 0, -40.5727886, -50.7308566, 0, 0, -69.9304568,\n", + " 0, 38.5385914, 0, -22.1243232, 0, 0, 0, 0, -62.5102538, 8.0801276,\n", + " 46.7998066, -2.3292106, 0, 0, 0, 8.774031, 0, 0, -65.6505736\n", + "], [\n", + " -67.2935466, -64.4354852, -96.6712204, 0, 0, -60.7812272, 0, 0, 0, 0, 0,\n", + " -7.9966864, 0, 0, 0, 0, 0, 0, 0, 89.7672338, 0, 0, 0, 0, 98.9607046, 0,\n", + " 28.6714432, 0, 0, 0, -26.2738856, 0, 0, 68.363956, 0, 0, 0, 0, 54.7406868,\n", + " 0, 0, 0, 0, 94.2320734, 0, 0, 0, 0, 0, 0, 0, 0, -2.9647794, 39.7161716,\n", + " -54.7931288, 0, 0, 0, 0, -47.2284892, 0, 0, 0, -8.6421808, -35.399612, 0, 0,\n", + " 62.1912668, 0, -6.8930716, 0, 0, -17.0801284, 0, 0, 68.6533416\n", + "], [\n", + " 0, 0, 0, 81.165396, 83.773254, 0, -25.1603, 0, 0, 50.225725, 0, -3.8242274,\n", + " 0, 0, 36.2078566, 0, 0, 0, 0, 0, 0, 0, 0, 15.551432, 0, 0, -33.6446236, 0,\n", + " 0, 0, 36.6171324, 0, 0, 0, 0, 67.9591934, 0, 22.1428016, 0, -27.2961928, 0,\n", + " 0, 0, -97.4961564, 90.4062526, 0, 0, 0, -90.0532814, 0, 98.8332924, 0, 0,\n", + " -13.8994926, 0, 17.1962884, 0, 0, 0, -55.1654678, 0, 0, 0, 85.829554, 0,\n", + " -37.971164, 64.233136, -17.9943296, 0, 0, 0, 0, -67.7509796, 0, 0,\n", + " 10.0750712\n", + "], [\n", + " 0, 37.2783148, 0, 0, 0, 36.4959506, 0, 0, 0, 0, 0, 0, 61.201323, 0,\n", + " 14.4328522, 48.4078064, 0, 0, 0, 0, 0, 0, 0, -47.0969056, 0, 0, 0, 0, 0, 0,\n", + " 0, 0, 0, 26.720439, 0, 0, 62.1987576, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, -65.2085246, 0, 0, 0, 0, 0, 0, 73.3019432, -14.3431238, 0, 0, 0, 0, 0, 0,\n", + " 0, 0, 2.1565846, 0, 0, 0.7733644, 0, 5.9090456, 0, -39.7724192\n", + "], [\n", + " 0, 0, 0, 0, -24.4555532, 0, 0, -5.5484574, 0, 25.4685054, 0.7906104,\n", + " 4.273133, 0, 52.12973, 0, -12.8040828, 0, 0, 81.888381, 64.0713498, 0, 0, 0,\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 62.9088768, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 20.869713,\n", + " 0, 0, -71.5835872, 0, 0, 80.7237808, 0, 0, 0, 0, -60.1883708, 0, 0, 0, 0, 0,\n", + " 0, 0, 0, 85.0393326, 23.6045316, -18.8849834, 0, 0, 0, -90.8065188,\n", + " -9.5037982, 14.3196654, 0, -28.9290306, 0, 0, -41.5575766\n", + "], [\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -8.9478934, 0,\n", + " -83.6040618, 0, 0, 0, 0, -14.3874822, 77.2528714, 0, 0, 99.2966066, 0,\n", + " 21.7889114, -37.7629282, 0, -11.6026582, 0, 0, 0, 0, 74.422603, 0, 0,\n", + " -79.239245, -31.9686324, 0, 0, 0, 0, 0, 0, -29.8797178, 0, 0, 85.2723062, 0,\n", + " 0, -8.8031188, 0, 0, -20.043565, 0, 0, -70.733454, 0, -94.7231762,\n", + " -85.4584516, 0, 0, 0, 0, -27.6068624, 0, -79.787783, 0, 0, -55.1894266\n", + "], [\n", + " 1.9374354, 0, 1.4807184, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 61.298952, 0, 0, 0, -90.5702054, 0, 67.381115, 0, -68.684637, 0, 0, 0, 0, 0,\n", + " 0, 0, 0, 93.7807934, 0, 8.8213302, -15.9020466, 0, 0, 0, 0, 23.8157662, 0,\n", + " 0, 0, 0, 0, 0, 0, 67.3461972, 0, 0, 0, 0, 0, 43.3263744, 0, 0, 0, 0, 0, 0,\n", + " 0, 0, 0, 0, 0, 87.7149706, 0, 0, 0, -18.5133574, -30.0338992\n", + "], [\n", + " 6.3937798, 78.7697644, 28.4485838, 0, 0, 0.1352466, 0, 0, 74.5767122,\n", + " -13.8340168, 0, 0, 0, 77.2929426, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 17.9243834,\n", + " 0, 0, 82.2239878, 0, 0, 0, 0, 64.3440016, 90.109577, 46.8926522, -2.4494366,\n", + " 0, 84.7413412, 0, 0, -4.216108, 0, 0, -79.8684776, 0, 0, 74.8706758,\n", + " -64.4518992, 0, 0, 0, 0, -34.4895004, 0, 0, 0, -74.1158858, -37.7803516, 0,\n", + " 0, 0, 0, 0, 80.0054296, 0, 0, 0, 0, 0, 0, 0, 0, -84.5832026, 0, 0,\n", + " 71.2540694\n", + "], [\n", + " 0, 0, 0, 0, -74.9257454, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 14.9675444, 0, 0, 0, 0, 0, -27.5236912, 0, 0, -80.4993438, 0, -81.8494538,\n", + " 0, 0, 0, 0, 0, -18.6802002, 0, 0, 0, 0, 0, 0, 0, 61.4131076, 0, 0,\n", + " -55.1421034, 0, 0, -18.576761, 72.3500914, 0, 0, 0, 0, 0, 0, -23.6460116,\n", + " 43.1258024, 0, 93.701872, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -86.5772554\n", + "], [\n", + " 0, 0, 93.0762916, 0, 0, 0, 0, 0, 0, 0, -37.2663938, 86.8303764, 0, 0, 0,\n", + " -51.9596226, 0, 35.6722618, -91.438259, 0, 0, -70.6277108, 0, -82.9146992,\n", + " 58.0327648, 0, 0, 0, 0, 0, 0, 0, 0, 13.3727302, 0, 0, 0, 23.5719942, 0,\n", + " -21.5445476, 74.1541634, 60.6365036, 97.4447708, 0, 0, 0, 0, 82.5869498, 0,\n", + " 85.1132108, 0, 0, 0, 0, 0, 0, 97.4012534, 0, 0, 0, 0, -45.1504048,\n", + " 1.0619934, 59.7140264, 0, 0, 0, 0, 0, 4.177461, 0, 0, 0, -75.7039276, 0,\n", + " 0.0421338\n", + "], [\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -71.171869, 0, 0, 8.685266, 0, 0, 0, 0,\n", + " 33.9089574, 0, -31.6154498, 0, 0, 0, 0, 0, 5.3182746, 0, 0, 0, 0, 0, 0,\n", + " 3.0361166, -10.364305, 0, 0, 0, 0, 0, 0, 0, 0, -83.9738444, 0, 0,\n", + " -7.9170212, 0, 0, 0, -28.7575682, 0, 0, 0, -29.9216686, 0, 0, 83.4050918,\n", + " -39.5247364, -6.7028846, 0, 0, 0, -23.6080482, 0, 0, 0, 0, 0, 0, -18.380154,\n", + " 46.9252306, 0, 0, 26.1618372, 99.6235254\n", + "], [\n", + " 0, 0, 0, 0, 0, 0, 0, 52.008307, 0, 0, 0, 0, 0, 92.4974102, -76.3015714, 0,\n", + " 0, 0, 0, 0, 0, 0, -56.4879132, 0, 0, 0, 50.1473938, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 40.2219566, 0, 0, 0, 84.5162074, 0, 0, 0, -73.3030606, 0, -10.9258316, 0, 0,\n", + " 0, 0, 97.5496436, -70.5026182, 0, 62.3611696, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 0, 96.4362226, 0, 0, 0, 0, 0, 0, 0, -6.0104764, 15.7466756\n", + "], [\n", + " -38.7678174, 0, 0, 0, 0, 10.5238486, 0, 0, 0, 31.6876676, 79.6111978, 0, 0,\n", + " 0, 0, 45.7314046, 0, 0, 0, -10.0125122, 0, 93.3170242, -96.4566224,\n", + " -5.853298, 0, -82.2848728, 0, 0, 0, 0, 0, 0, 0, 43.3427638, 24.6186934, 0,\n", + " 44.859548, 0, -63.8196424, 0, 32.6630616, 41.48423, 0, -42.9613722, 0, 0,\n", + " -68.8954844, 0, 0, 0, 0, 0, 0, 0, 0, 27.7424034, -33.4867534, -49.1827758,\n", + " 0, 18.7014116, 0, 0, 59.049662, 0, 0, 0, 0, 0, 0, -29.6305028, 0, 0, 0, 0,\n", + " 0, 98.2266078\n", + "], [\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -14.6583468, 0, -74.4490466, 0, 0, 0, 0, 0, 0,\n", + " 0, 0, 0, -94.9604028, 0, 0, -48.28403, -41.3534342, 0, 0, 0, 62.9532972, 0,\n", + " 0, 4.030284, 0, -60.478996, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, -46.5887848, 39.4565458, 0, 0, 0, 0, 0, 0, 0, -48.5071236, 0, 0, 0,\n", + " 41.8640204, 0, 0, 0, 74.271524, 0, 15.5769242, 0, -61.4793904, 52.4500934\n", + "], [\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 50.8387116, 0, 46.490051, 0, -54.9751352, 0,\n", + " 43.0696416, 0, 0, 0, 0, 80.5337704, 0, 0, -16.0325234, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 63.186351, 0, 0, 0, 0, 0, 75.2218604, -27.3783446, 0, 0, 0, 0,\n", + " -85.021934, 60.9043202, 55.7344594, 0, 41.1687556, 0, 5.574124, 0, 0,\n", + " -5.0028254, -40.2614834, 0, 0, 0, 0, 0, 0, 22.207679, 0, 0, 0, 0, 0,\n", + " 65.0504204, 0, -61.4580018, -90.137276, 19.2277196, 0, 0, -73.8615034\n", + "], [\n", + " 0, 0, 0, 0, 0, -21.3303052, -75.4586018, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 0, 0, 0, 0, 0, 84.6730538, -3.4478344, 0, -76.083503, 19.9372656,\n", + " -38.9938322, 0, 0, 36.0135034, 0, -56.8457144, 0, 0, 63.4198336, 0, 0, 0, 0,\n", + " 0, -63.4419798, 28.5629988, 0, 0, 0, 0, 38.8001126, 0, 0, 47.2148438, 0,\n", + " -19.256673, -24.8778354, -47.8193252, 0, 0, -38.7279908, 0, 0, 0,\n", + " -34.5546658, 0, -96.7675822, 0, 0, 0, 0, 0, 0, 0, 0, 27.9437518\n", + "], [\n", + " 0, -17.8703906, 0, 0, -47.2114204, 0, -73.3595682, 66.668341, 0, 0, 0, 0,\n", + " 30.250278, 0, -40.452007, 0, 0, 0, 0, 0, -37.5013244, 34.3045856, 0, 0, 0,\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12.898457, -15.2838318, 0, 0,\n", + " 0, 0, 0, 16.333021, 0, 72.0452526, 0, 2.285458, 0, 0, 0, 0, -17.8073046, 0,\n", + " 0, 0, 81.7271146, 0, 0, -17.7174538, 0, -62.6694996, 8.7298318, 0, 0, 0, 0,\n", + " 0, 0, -27.1131974, 0, -68.0964272\n", + "], [\n", + " 0, 0, 0, 0, -22.1791216, 16.588597, -19.4545486, 0, -74.8318248,\n", + " -74.4252462, 0, 0, 0, -46.1656546, 0, 0, -21.1620788, 0, -25.6883764, 0, 0,\n", + " 0, 0, 0, 0, 0, 0, 0, 34.5550634, 0, 0, 0, 0, 93.8894398, 0, -30.961635, 0,\n", + " 0, 0, 0, 0, 78.594791, 0, 0, -63.3427616, 52.6543374, 0, 0, 38.4578962, 0,\n", + " 0, -56.5589394, 0, 0, 0.6873802, 0, -83.496155, 0, 0, 0, 13.0737006, 0,\n", + " -41.7343216, 71.8170636, 69.0276666, 0, 0, 57.722026, 0, 0, -93.1746526, 0,\n", + " 0, 0, 0, -49.9838934\n", + "], [\n", + " 0, 4.2310134, -77.9001854, -57.1049418, 0, 53.3411444, 0, 0, 62.3456148, 0,\n", + " 0, 68.2636062, 0, 0, -97.5234598, 0, 87.5610236, 0, 0, 0, 0, -77.3855948, 0,\n", + " 0, -90.724008, 28.2231562, 0, 53.026918, 0, 0, 0, -76.15995, 0, 0, 0,\n", + " 15.413813, 0, 0, 0, 0, 0, 0, 0, 0, 13.0272308, 0, 0, -23.9738128,\n", + " 38.7553414, 0, 30.9290494, -35.5982432, 0, 0, 0, -45.1103148, 0, 0, 0,\n", + " 70.158022, 0, 0, 0, 0, 0, 54.120183, 0, 0, 0, -41.9285314, 0, 0, 0, 0,\n", + " 14.1035676, 33.7857218\n", + "], [\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 63.3716696, 0, 0, 0, 0, 0, 24.0919054, 0, 0, 0, 0,\n", + " 0, 0, 0, 0, 11.3748388, 0, 0, 95.3405052, 93.4694228, 0, 0, -45.255791, 0,\n", + " 0, 0, 0, 0, 0, 0, 0, -1.0475536, 60.84603, 0, 0, -10.47761, 0, 26.1100158,\n", + " -51.9159084, 0, 0, 0, 0, 0, 0, -65.6123578, 0, -91.0146766, 0, 0, 0, 0, 0,\n", + " 0, 0, -21.2845524, 0, 35.7297864, 0, 0, 0, 0, 0, 0, 15.911098, 0, 0,\n", + " -12.9287238\n", + "], [\n", + " 0, 0, 4.6786386, 0, 0, 1.6495644, 0, 0, 0, 26.96434, 0, 0, 0, 58.7515752, 0,\n", + " 0, 0, -47.6494254, 0, -54.2669514, 72.894442, 0, 0, 95.889445, 0,\n", + " 68.8888298, -66.11831, 0, -23.7891422, 79.7630012, 0, 0, 0, -63.9280642, 0,\n", + " 0, 0, 0, -32.1729936, 0, 0, -44.1408756, 0, 0, 0, 0, -43.6440432, 0, 0, 0,\n", + " 0, 0, 0, 0, 0, 9.0521906, 0, 0, -26.1975436, 0, 45.9278082, 0, 0, 29.678958,\n", + " 0, 0, 0, 0, 0, 5.9131246, -82.314248, 0, -56.8775976, -43.6011182, 0,\n", + " -28.0599468\n", + "], [\n", + " 44.0699428, 0, -0.2569744, 0, 0, 0, 10.53932, 0, -89.8739242, 0, 0, 0, 0,\n", + " -39.5334882, -60.036911, 96.86551, 0, -59.6306248, 0, 76.9520134, 0, 0, 0,\n", + " 0, 0, 55.2369732, 0, 0, 0, 0, -41.8466046, 0, 0, -5.291202, 0, -18.5051634,\n", + " 0, 0, 0, 0, 0, 47.1813778, 92.5194464, 90.690835, 56.7657076, 0, 0, 0,\n", + " -42.1944794, 0, 0, 0, -69.1124266, 0, 0, 0, 0, 0, 0, 0, -14.4018142,\n", + " -36.9699736, 0, 0, 0, 0, 0, 0, 41.4981516, -1.5870996, 0, -73.7309526,\n", + " -68.2179518, 0, 5.1895272, -29.7117264\n", + "], [\n", + " 90.3158852, 54.7711894, 0, 0, 0, 0, 0, 0, 0, -92.2564004, -20.8178774, 0,\n", + " 17.3192726, 0, 2.5685474, 0, 0, 0, 0, -21.96248, 0, -83.8507778, 0, 0, 0, 0,\n", + " -81.769375, 0, 0, 0, -73.8973162, 0, 0, 0, 0, 0, 0, -96.8790628, 0,\n", + " -29.2883476, 0, 0, -73.2399312, 0, 0, 0, 56.465223, 0, -10.1549238, 0, 0,\n", + " 44.7135732, 0, 0, 0, 0, -37.8912668, 61.0703958, 0, 0, 0, 94.563183,\n", + " 2.1777518, 0, 0, 0, 0, 0, 0, 69.8987148, 0, 0, 0, 58.5987754, 0, -73.701682\n", + "], [\n", + " 0, 25.7383596, 0, 43.2784374, 0, 0, 0, 0, 0, 0, 0, 65.3498334, 0,\n", + " -51.6680898, 0, 0, 0, -32.4960916, 0, -61.8512302, 0, 0, 0, 0, 66.0087116,\n", + " 0, 0, 0, 0, 0, -69.5971312, -68.5339006, 0, -87.9115714, 0, 0, 0, 0, 0, 0,\n", + " 0, 0, 0, 0, 0, 0, 0, 13.6412636, 0, -33.3575526, 0, -34.6876284, 0, 0, 0, 0,\n", + " 0, -5.195929, 0, 0, 0, 0, 0, 0, -62.551799, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " -85.6796076, 0, -69.9796424\n", + "], [\n", + " 0, -67.7487338, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -89.686036, 0, 0, 0, 0, 0, 0,\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 35.3396338, -51.2668318, 0, 0,\n", + " -9.4925338, 0, 0, 0, 0, 0, 0, 0, 0, -74.049299, 0, 0, 0, 6.669526, 0, 0, 0,\n", + " 0, 82.3839076, 0, 0, 0, -63.2986138, 0, 0, 0, 29.4639612, 0, 0, -75.2754458,\n", + " 0, 0, 10.6058324, 83.9439366, 48.4539264, 0, 0, 0, 0, 8.6922024, 17.82273\n", + "], [\n", + " -12.5659444, 0, 0, 0, 0, 0, -16.0039068, 0, 0, 0, 0, -64.5579896, 0,\n", + " 25.3425712, 0, 0, 0, 0, -73.3525686, 0, 0, 0, 41.4534476, 0, 0, 35.6355928,\n", + " 82.0438356, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16.5500598, 0, 17.4573382,\n", + " -30.4230828, 68.6250598, 0, 0, -70.7101786, 0, 0, 0, 0, 0, 19.070679, 0, 0,\n", + " 0, 0, 0, 0, 0, -69.0034118, 0, -32.8881618, 0, 99.6116696, -41.8557658,\n", + " -36.91302, 0, 0, 0, 0, 0, 25.1313946, 0, 0, 0, 0, 0, 66.1785624\n", + "], [\n", + " -28.5551034, -60.2641954, 0, 0, 0, 0, 0, 0, 0, 0, 0, 11.4765054, 0, 0, 0, 0,\n", + " 0, 0, 78.1818898, 0, 0, 0, 0, 34.0574966, 0, 0, 0, 3.3327304, 0, 0, 0, 0, 0,\n", + " 0, 0, -25.4031686, -6.4345882, 0, 0, 0, 0, 0, 70.724926, 0, 0, 0, 0, 0, 0,\n", + " -34.578727, 0, 0, 0, 0, 73.4821434, 0, 0, 0, 0, -78.7097278, 0, 0, 0,\n", + " -56.0390914, -77.1810362, 95.2972308, 0, -88.304829, 0, -11.4076234, 0, 0,\n", + " 0, 0, 0, -53.8368524\n", + "], [\n", + " 0, 0, 53.7291982, 0, 0, 0, 0, 0, 0, 0, 0, 90.7870612, -66.2974882, 0,\n", + " -92.2201462, -15.7252186, 0, 0, 0, 0, 0, -25.4072904, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 0, 32.8926792, 36.9923848, 0, 0, 0, 0, 33.293754, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 0, 0, 0, 5.689557, 0, 0, 89.5416534, 40.4300196, 0, 0, 2.8394972,\n", + " 90.7550328, 0, 0, 71.835872, 30.8157976, -96.7796296, 0, 44.1461388, 0, 0,\n", + " -32.5545222, 0, 75.597677, 0, 0, 0, 0, 33.146892\n", + "], [\n", + " 0, -36.157067, 0, 0, -0.4087578, 0, 0, 0, 0, 0, 0, 0, 0, 0, 13.1871604, 0,\n", + " 60.6086354, 84.4235272, 0, 0, 89.0383032, 0, 0, 0, -43.0195992, 0,\n", + " -99.31608, 0, -64.2154682, 0, 0, 0, 76.5304532, 0, 0, 0, 0, 0, 82.052369, 0,\n", + " -72.450166, 0, 0, 0, 23.4129134, 0, 0, 0, 0, 0, 0, 0, 0, 53.3291088, 0, 0,\n", + " 0, 73.435697, 87.3597806, 0, -94.1974698, 0, 0, 0, 0, 59.0496292, 0, 0, 0,\n", + " -13.8506028, 0, 0, -42.6003178, 0, 0, 55.1715212\n", + "], [\n", + " 0, 0, 0, -67.8709314, 0, 0, 0, 0, 0, 0, 0, -9.678326, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -28.014314, 0, 0, 0, 0,\n", + " 0, 0, 0, 0, -36.8865788, 0, 0, -98.739389, 0, 0, 0, 0, 0, 0, -86.2168946, 0,\n", + " 44.1228816, 0, 0, 0, 0, 0, 0, -36.6609072, 0, 0, 0, 0, 18.3461886,\n", + " 98.9990466, 30.4109678, 0, 0, 0, -39.2683046\n", + "], [\n", + " -49.3558704, 0, 0, -31.3665258, 0, 0, 0, 0, 0, 0, 1.7897898, 0, 0, 0, 0, 0,\n", + " 0, 0, 0, 0, 0, 0, 0, -15.7715374, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " -50.0629034, 0, 0, 0, 0, 0, 0, 0, 35.8856254, 0, -51.062155, 0, 0, 0, 0, 0,\n", + " 0, 0, 0, 0, 0, 0, 0, 78.136688, 0, 0, 0, 0, -41.5917514, 0, 0, 0, 0, 0, 0,\n", + " 0, 0, 0, 73.1472674, 0, -77.9015418\n", + "], [\n", + " 0, -6.5048614, -28.611729, 0, 0, -97.9680564, 33.7007078, -70.5347856, 0,\n", + " 17.197908, 0, 19.8776858, -24.4246618, 0, 0, 53.363481, 0, 0, 0,\n", + " -44.7872848, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -74.0599438, -81.2162694,\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -68.6473592, 0,\n", + " 39.894997, 0, 0, -38.5305628, 0, -5.244101, 0, 0, 46.6040974, 2.4384956, 0,\n", + " 0, -26.8264528, 0, 0, -98.5537452, 2.6463192, 0, 0, 0, 0.4769732\n", + "], [\n", + " 0, 0, 0, 0, -52.7430298, 1.8510158, -39.691072, 0, 0, 0, 0, 0, 95.6497418,\n", + " 0, 0, -48.04896, 0, -26.6728378, 0, 0, 0, 0, 0, -12.3921976, -65.5861706, 0,\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " -57.9815088, 0, 77.6808808, 0, 0, 0, 94.6506526, 0, 0, 0, 55.8427672, 0, 0,\n", + " -0.6995066, 0, -78.3071326, 0, 0, 0, 0, 0, 0, 0, 0, 0, -67.9654476\n", + "], [\n", + " 0, -23.0019946, 0, 0, 95.4877116, 0, 0, 0, 0, 0, -36.573767, 0, 0, 0, 0, 0,\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 90.1862622, -36.4728966, 0, 0, 0, 0, 0, 0,\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 86.6126918, 0, 0, 0, 0, 0, 0, 0,\n", + " -80.1067184, 0, 31.8472788, 27.496628, -66.6206162, 0, 0, 9.1957296, 0,\n", + " 37.2257526, 0, 0, 0, 0, 0, 0, 0, -39.1637322, 0, 0, 74.4924622\n", + "], [\n", + " 0, 0, -25.4147588, 6.2424662, 0, 0, 0, 0, 0, 0, 92.5623938, -92.810452, 0,\n", + " 0, 0, 0, 0, 0, 0, 0, 0, -45.0048688, 0, 0, -32.1678062, 0, 0, 0, 9.8719598,\n", + " -33.7145476, -16.3449354, 0, 70.462643, 0, 0, 14.5356206, 0, 0, 0, 0,\n", + " -95.1218374, 0, 0, 0, 0, -0.8077516, 0, 0, 0, 0, 0, 53.7434994, 0, 0, 0, 0,\n", + " 0, 5.376533, 0, 0, 0, 0, 0, 0, -1.125953, 75.3929928, 0, 0, 0, 0, 0,\n", + " -17.8555478, 0, 0, 87.130332, -46.977091\n", + "], [\n", + " -57.0064908, 0, -61.469472, 0, 0, 94.2906142, 0, 10.1214686, 0, 0, 0, 0, 0,\n", + " 0, 0, 0, 90.8859632, 0, -31.3550928, 25.4391198, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, -30.6974596, 0, 16.8162692, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -40.946388,\n", + " -23.914437, -39.0760436, 0, 0, 12.4664916, 0, 0, 0, 59.3854694, 0, 0,\n", + " -79.029102, 0, 48.0444832, 0, 0, 0, 0, 0, 0, 0, -34.447419, 0, 0, 0, 0, 0,\n", + " 0, 0, 0, 0, -42.8371356\n", + "], [\n", + " -46.3742298, 0, 0, 0, 0, 6.4096268, 0, 0, 0, 0, 0, 0, 0, 0, 53.7055136,\n", + " 41.0589284, 0, 0, 0, 0, 0, 0, -59.494163, 78.2644798, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 0, 0, 0, 0, 3.5467882, 0, 0, 0, 0, 0, -33.8146284, 0, 81.1209896, 0, 0,\n", + " 0, 0, 0, 0, -59.120982, 0, 0, 0, 0, 20.5082176, 0, 0, -32.2137818,\n", + " 41.6679682, 98.4426286, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 18.7911844\n", + "], [\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 22.5196126, -44.92426, 0, 0, 0, 0, -78.1154748, 95.3654376, 0, 0, 0,\n", + " -42.4266782, 73.3850132, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 94.1878774,\n", + " 90.3854666, 0, 0, 0, 0, 0, -15.9053536, -74.9423846, 47.214, 0, 0, 7.477562,\n", + " 0, 46.2206928, 19.1508454, 41.6978146, 39.03286, 0, 0, 0, 0, 0, -14.259302,\n", + " 0, 54.0542232, 0, 0, 44.5438142\n", + "], [\n", + " 95.3632006, 43.6928354, 75.8291588, -81.2577418, 0, 0, -91.248437, 0, 0,\n", + " 22.476879, -77.967431, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3.0308978, -22.0727056, 0,\n", + " 0, 0, 0, 0, 0, 0, 17.126454, 0, -45.7583606, 0, 0, 32.9187018, 0, 0, 0, 0,\n", + " 0, -58.6847152, 0, 0, 39.113676, 0, 0, 0, 0, 0, 0, -80.1176538, 0,\n", + " -86.9570556, 0, 0, 0, -9.3462492, 0, 49.3616864, 0, 60.4773586, 0, 0,\n", + " 48.9766746, 17.5735282, 75.126033, 0, -50.8306992, 0, 0, 61.3438194, 0, 0,\n", + " -95.9051914, 0, 25.6497354\n", + "], [\n", + " -89.5581772, 0, 0, 0, 0, -37.7576814, 0, 0, 0, -50.475431, 0, 0, 0, 0,\n", + " -75.575654, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6.7066106, 0, 0, 0, 68.702018, 0, 0,\n", + " 44.2841378, 0, 0, 0, 0, 0, 0, 0, 42.4183476, 80.6872178, 72.028214, 0, 0, 0,\n", + " -50.6912368, 0, 0, 0, 53.1913708, 0, 0, 0, 0, -50.0798868, 0, 0, 0, 0, 0, 0,\n", + " -99.54189, 0, 0, 0, 0, 87.8828622, 7.144766, 0, 0, 0, 71.8161494,\n", + " 91.0414654, 0, 0, -7.240427\n", + "], [\n", + " 0, -73.397517, 0, 0, 0, 0, -42.4633614, 0, 0, 0, 0, -2.3988294, -60.1970288,\n", + " -31.1370786, -16.4428054, 0, -86.5694254, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " -33.759363, 0, 88.9440556, 0, 0, 0, 48.8687358, 0, 0, 60.1841648, 0, 0,\n", + " 81.5798018, 0, 0, 0, 0, 22.265044, 0, 0, 0, 0, -98.612946, 0, 0, 0,\n", + " -49.44052, 0, 46.9606012, 0, 0, 0, -23.7990468, 0, 0, 0, 0, -72.1702852, 0,\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 26.2623134, 0, -17.3386012\n", + "], [\n", + " 0, 16.4596174, 0, 0, 0, 0, -85.6599392, 0, 0, 0, 0, 0, 0, 93.708794, 0,\n", + " -37.5698758, 0, 0, 0, 0, 0, 0, -82.8927936, 0, 82.4183808, 0, 0, 0, 0, 0, 0,\n", + " 55.028266, 0, 0, 11.3484192, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " -60.7651522, -4.822581, 0, 29.0627604, 0, 0, 61.7042716, 41.5363722,\n", + " 73.9967868, 0, 0, 0, 0, 50.2308124, 0, 33.8231702, 0, 0, 0, 0, 0,\n", + " -14.7226996, 14.5401778, 0, -72.8145596, 19.9220286, -76.4609286\n", + "], [\n", + " 11.1687196, 0, 0, 0, 0, 0, 0, 0, -37.365205, 0, 0, 0, 52.0314298, 0,\n", + " -58.0558462, 0, 0, 18.6738906, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 59.6046802, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -26.5944948, 0, 0, 0,\n", + " 77.0271992, 46.521059, 61.8258634, 0, 0, 36.079546, 0, -14.3080494, 0,\n", + " -50.1618478, 0, 0, 95.9445656, 0, 0, 0, 0, 0, 0, 0, 0, 0, -53.478982,\n", + " -90.3623976, 0, 0, -37.1575466\n", + "], [\n", + " 0, 0, 0, 0, 0, 0, 0, 78.882051, 0, 0, 57.8056512, 7.2056626, 0, 2.822132,\n", + " 40.311822, 0, 0, 0, 83.8935006, 0, 0, 91.2774482, 3.160849, 91.7410132, 0,\n", + " 0, 0, 0, 0, 2.7544652, 0, 0, 0, 0, 0, 16.8419108, 0, 0, 84.1171174, 0,\n", + " 21.3119752, -69.869284, 0, 0, 0, 0, 0, 92.1087118, 0, 0, 0, 0, 34.3473744,\n", + " 21.9890278, 0, -36.3139526, 0, 0, 0, 0, 0, 25.613497, -2.989159, 0, 0, 0, 0,\n", + " -49.2456622, 0, 0, 27.3140788, 0, 49.210258, 0, 0, -90.6896972\n", + "], [\n", + " 0, 0, 0, 0, 0, 0, -56.1947046, 0, 0, 52.6617176, 61.6283016, 0, 0, 0, 0, 0,\n", + " 0, 0, 0, 0, -34.0759312, 0, 92.0200254, 0, 0, -9.7999914, 0, 0, 34.0572616,\n", + " 0, 0, 0, 0, 0, 59.7637334, 0, 0, 0, 0, 0, 43.5437732, 29.9690024, 0, 0,\n", + " 93.9438036, 0, 0, 0, 52.3867986, 0, 0, 0, 38.0567542, 0, -63.9851954, 0,\n", + " 73.1679634, 0, 0, -28.6636244, 0, 0, 0, 0, 0, 39.2894916, 0, -28.4364668, 0,\n", + " 0, 0, 0, 0, 0, 0, -32.2899456\n", + "], [\n", + " 15.3399588, 0, 0, 0, 0, 0, 0, -66.2540916, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 0, 0, 0, 0, 14.7613734, 0, 0, 0, -32.9550622, 0, 0, -17.2710502, 0, 0, 0,\n", + " -66.7611862, 76.5708962, 93.3868072, 12.036471, 0, 0, 0, -74.2189184, 0, 0,\n", + " 0, 0, 58.0343866, 0, 71.0145124, 0, 0, 0, 0, 0, 0, 0, 80.7131208, 0,\n", + " -49.5191686, 40.2489602, 0, 0, 39.9664558, 0, 0, 0, 0, 0, 0, 51.704979, 0,\n", + " 20.9209752, 0, 0, -63.5800814\n", + "], [\n", + " 0, 0, 35.1676872, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8.326191, 0, 0, 0,\n", + " -42.1405488, 0, 0, 0, 0, -64.9203894, 0, 0, 0, 86.5653072, 0, 17.1250418, 0,\n", + " 15.224998, -32.788739, 0, 0, 0, 13.1550612, 0, 0, 0, 93.3049176, 65.5504482,\n", + " 0, 35.8126186, -16.2474202, 0, -76.7788518, -51.9001008, 0, -89.725966,\n", + " 53.4895596, 0, 0, 0, 0, 0, 0, 0, 0, 0, -82.6077236, 0, 0, 0, -57.075872,\n", + " -52.5166376, 99.9479554, 0, 0, -89.7491862, 0, 18.6163306, 0, 29.1454254\n", + "], [\n", + " -18.8864514, 0, 0, 0, 0, 0, -46.6968628, -83.8123266, 0, 0, 0, 0, 0, 0,\n", + " -91.631792, 0, 0, 0, 0, -57.3455082, 66.459894, 0, 0, 0, -73.9341878, 0, 0,\n", + " 0, 81.8859882, 0, 0, 0, 0, 0, 28.0747908, 0, 0, 99.7796988, 0, 87.0296656,\n", + " 0, 0, 43.7722526, -60.65313, 98.6287198, 0, 20.8634118, 0, 0, 25.6519386,\n", + " 0.4939554, 0, 0, 0, 0, 0, 0, 0, 0, -74.9266526, 0, 0, -25.4234142, 0,\n", + " -91.054582, 0, -42.534282, 0, 0, 0, 0, 0, 0, 0, 0, 12.1491094\n", + "], [\n", + " 0, -90.5331764, 0, 0, 0, 0, 0, -8.1166918, 0, 0, 0, 0, 0, -34.0013692,\n", + " 21.9272646, 0, 0, 0, 0, 0, 93.2245032, 0, 21.8275426, 0, 0, 0, 38.5279608,\n", + " -6.0022692, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 39.644863, 0, 0, 0, 0, 0,\n", + " 0, 0, 0, 0, 0, 0, 0, -93.7962256, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 11.5363598,\n", + " 70.354452, 0, 0, 0, 32.2282298, 0, 36.659058, 0, 0, 53.992344\n", + "], [\n", + " -74.4430478, 0, 0, -93.4496218, 14.6437598, 0, 0, 0, -33.8297902, 0, 0, 0,\n", + " 0, 0, 0, -35.949213, 0, 0, -93.1678628, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " -22.8806328, 0, 0, 0, 0, 0, 0, 0, 45.1466816, 88.4821604, 0, 0, -49.7769388,\n", + " 0, 0, 0, 0, 0, 0, 53.6290382, -40.4388272\n", + "], [\n", + " 71.2065308, 94.4966808, 0, 0, 0, 0, -84.2404402, 0, 0, 0, 0, 0, 61.7641462,\n", + " 0, 0, 0, -0.608018, -94.7698384, 0, 0, 0, 0, -3.4562834, 0, 0, 0, 0, 0, 0,\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, -26.0708126, -73.3657794, 26.6482556, 0, 0, 0, 0,\n", + " 0, 0, -66.9699546, -98.6726948, 0, 0, -25.4137958, 0, -88.7254432,\n", + " -81.2735544, 0, -23.3081526, 0, -51.8917252, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " -52.3004828, 0, 0, 0, 0, 67.8542398, 0, -67.7489638, 32.2212522\n", + "], [\n", + " 0, 77.0252092, 0, 0, 0, 0, 0, -85.3271924, 0, 0, 0, -2.9308596, 0,\n", + " 83.448547, 0, 4.7835886, 0, 0, 0, 0, 76.590577, 0, 0, 0, 0, 0, 86.0180794,\n", + " 0, 0, -88.4030016, 0, 0, 0, -13.770426, 57.6068646, 0, 0, 0, 0, 0,\n", + " 12.6896788, 0, 0, 0, -78.1078136, -92.3074796, 0, 0, 0, 0, -94.3200626, 0,\n", + " 0, -7.987837, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 52.7655986, 0, -78.2727176,\n", + " 0, 74.4309632, 48.3867546, -85.4142468, 0, 91.3888914, -6.0372808,\n", + " 51.1643348\n", + "], [\n", + " -77.4193002, 0, 0, 0, 0, 0, 0, 0, 41.0846988, -78.3265056, 0, 0, 0, 0, 0, 0,\n", + " 0, -20.408123, 10.7055032, 0, -19.5848354, 0, -32.624054, 0, 47.333306, 0,\n", + " -41.6545398, 0, 0, 0, 0, 0, 46.8131656, 0, 52.5387768, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 0, -75.3511542, 0, 0, 0, 0, -27.6140242, 0, 0, -63.3032372, 0, 0, 0, 0,\n", + " 0, 0, 0, 0, -93.1854838, 0, 0, 0, 0, 32.3353436, -75.8659438, 89.852816, 0,\n", + " 9.7044216, 0, 94.9239572, 0, -57.0391726, 25.4894998\n", + "], [\n", + " 0, 0, 0, -27.2226392, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -89.0780816, 0, 0,\n", + " 0, 0, 6.6342408, 0, 0, 92.3634346, -41.5559582, 0, 0, 0, 0, 0, 0,\n", + " -4.3499792, 18.9554522, 0, 29.992962, 0, 0, 0, 0, 0, 0, 0, 0, -51.9952794,\n", + " -40.3571344, 0, -61.0098328, 0, 0, 0, 0, 71.8571838, 97.3056784, 0, 0,\n", + " 27.798026, 0, 0, 20.3198544, 0, 0, 0, -82.1043534, 0, 0, 0, 0, 0,\n", + " 55.2807134, 0, 0, 0, 0, -90.8562594, -43.2271604\n", + "], [\n", + " 0, 0, 0, 0, 0, 15.2015056, 18.3740448, -11.2614058, 0, 0, 0, 0, 0,\n", + " 53.0086248, 0, -91.038908, 0, 0, 88.2707986, 3.2580272, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 88.8189254, 0, 0, 0, 0, -3.742353, 0, 0, 0, -41.4198488, 90.0966416, 0,\n", + " -22.474875, 0, -25.610863, -79.5943706, 0, 0, 0, 0, 0, 0, 0, -3.5616438, 0,\n", + " 0, 0, 88.5984932, 0, 0, 0, 0, 0, 0, 0, 22.4398688, 0, 0, 0, 0, 0, 0, 0,\n", + " -63.7422676, 0, 0, 0, -82.7150752\n", + "], [\n", + " 0, 0, 75.634243, -60.420708, 0, 0, 0, 0.8383984, 0, 0, -72.2791914, 0, 0, 0,\n", + " 17.1476022, 0, 0, -14.5930276, 9.4352026, 0, -30.1475326, 0, -53.7249052, 0,\n", + " 19.3293368, 0, 0, 0, -80.867335, 0, -3.344608, 71.3546388, -91.098817, 0, 0,\n", + " 0, -26.83234, 0, 3.7009, 0, 0, 0, 0, 0, 28.4006138, 0, 0, 57.1433046,\n", + " 14.4086186, 0, 0, 0, 49.5828354, 0, 0, 0, 0, 0, 0, 0, 0, 0, 17.0012008, 0,\n", + " 30.5581294, -98.7868224, 0, 0, 0, 70.0467486, -9.4444084, 0, 19.9250998, 0,\n", + " 0, -79.4970662\n", + "], [\n", + " 0, 54.0067274, 0, 0, 0, 37.9936634, 0, -20.1071476, -58.981429, 0, 0,\n", + " -83.9927614, 0, 0, 0, 0, 0, 0, -70.8571636, 0, 0, 68.4686496, 0, 0, 0, 0, 0,\n", + " 0, 0, 0, 0, 0, 0, 0, -8.1687508, 0, 0, 16.6835288, -10.1286606, 0, 0, 0, 0,\n", + " 0, 0, 0, 0, 0, 0, 86.4747972, 0, -34.6316042, 0.9788672, 0, 0, -41.4513542,\n", + " 0, 0, 0, 0, 0, 0, 0, 0, -28.4021576, -74.9076708, 0, -5.0425708, 0, 0, 0,\n", + " 78.9139852, -50.7082204, 0, 34.0684852, 28.2502302\n", + "], [\n", + " 0, 0, 0, 0, 0, 0, 0, 57.544994, 0, 0, -22.4411072, 0, -94.2568866,\n", + " -80.7849138, 0, 0, -10.0010618, 33.3160792, 0, 0, 0, 0, 0, 0, -82.6811248,\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -11.0390072, 0, 0, 32.0523166, 0, -80.6937396,\n", + " 0, -94.2671164, 0, 0, -16.2170198, 0, 0, 0, -5.7387768, 0, 0, 0, 0,\n", + " -83.6048238, 0, 0, 0, 0, 0, 0, 33.6877392, 0, -41.9784856, 0, 0, 0,\n", + " -50.6512854, 0, 78.737702, 0, 0, 0, 0, 0, 9.0618996\n", + "], [\n", + " 0, 0, 0, 0, 0, -94.8072128, 0, 21.9767716, 0, 0, 0, 57.7817378, 35.3840102,\n", + " 0, 0, 0, 60.4679182, -94.9978498, 0, -18.9377882, -42.3270372, 0, 0, 0, 0,\n", + " -3.9058912, 0, 0, 44.440235, 0, 0.41471, 0, 0, -88.0148602, 53.9755254, 0,\n", + " 0, 74.7772774, -19.633854, -66.6129164, -25.4637816, -13.1642082, 0, 0, 0,\n", + " 0, 57.6068044, 0, 0, -75.9060014, 24.0447026, 0, 0, 29.9750648, 0, 0, 0,\n", + " -54.938857, -4.4090126, 0, 0, 0, 0, 0, 0, -57.1202194, 0, -11.130658, 0, 0,\n", + " 95.6177756, -78.5403868, 0, 0, 30.4589622, -93.6950296\n", + "], [\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -50.308444, 0, 0, 0, 0, 0, 0, 0, -20.3462364,\n", + " 0, 0, 25.6081088, 0, 0, 0, 0, 0, 0, 90.9877902, 0, -56.0922542, 99.2456868,\n", + " 0, 45.4316172, 0, 70.1339288, -54.576692, 0, 0, 0, -73.6910292, 0, 0, 0, 0,\n", + " 0, 0, 38.6032202, 0, 0, 0, 60.7387286, 0, 0, 0, 0, 0, 0, 0, 0, 95.207832, 0,\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 58.4721284\n", + "], [\n", + " 0.3163476, 0, 0, 13.0918568, 5.13089, 0, 40.7266308, 0, 0, 0, 0, 0,\n", + " -98.7077428, 0, 81.000503, 0, 0, 76.3945372, 0, 0, 0, 0, 0, 0, -10.2289084,\n", + " -70.592702, 0, 0, 0, 0, 0, 0, 69.59571, 0, 0, 0, 0, 0, -61.4746908,\n", + " 53.4041094, 0, 90.4397278, -33.1874988, 0, 0, 71.5512744, 5.846105, 0, 0, 0,\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 56.7568638, 0, 0, 66.7588344, 0, 0, 0,\n", + " 99.284785, 0, 0, 0, 0, 0, 0, -99.2850048, -85.4466004\n", + "], [\n", + " 0, 0, -34.544278, 0, 0, -36.135124, -33.4259354, 0, 0, -60.4256326, 0,\n", + " 44.7734168, 0, 0, 0, 0, -88.6744704, 0, 0, 0, 0, 0, 0, 0, 0, -95.3752508,\n", + " 4.596855, 85.2924422, 70.9081648, 22.0390844, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 1.6872456, 0, 0, 0, 0, 0, 0, 0, -37.4782422, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 4.7642096, 0, 0, -33.6313218, 0, 0, 72.977526, 0, 0, 0, 0, 0, 0, 2.5063252,\n", + " 0, 0, 0, 0, 0, -64.8677902\n", + "], [\n", + " 0, 0, 0, 3.718989, 0, 0, 49.3814782, 77.868826, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " -49.7008846, 0, 4.696494, 0, 0, 0, 0, 0, 0, 0, -59.4928602, 0, 0, 0, 0, 0,\n", + " 82.6840644, -0.996624, -15.8014198, -93.098215, 0, 0, 41.2709314, 0,\n", + " -90.9633198, -56.911012, 0, 0, 0, 0, 0, 0, 0, 0, 0, -26.02585, 0, 0, 0, 0,\n", + " 0, 0, 61.2353938, 0, 0, 0, 0, 21.376448, 0, 0, 0, 0, 0, 0, 0, 97.0578858, 0,\n", + " 0, 0, 5.1265226\n", + "], [\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -19.5643656, 0, 0,\n", + " -92.0307416, 0, 0, 0, 0, 55.003856, 0, 0, -20.4708104, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 54.4588824, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " -57.0182252, 0, 0, -48.3222128, 0, 0, 0, -41.1121382, 0, 0, 0, 0, 0, 0,\n", + " -12.2773602, 0, -28.6924808, 50.0415338\n", + "], [\n", + " 0, 0, 0, -73.9719246, 0, 0, 0, 0, 0, 0, 0, 39.4110332, 0, 0, -21.6757132, 0,\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 74.8739354, 0, 0,\n", + " -52.9210426, 0, 46.7750368, 63.6311858, 0, 0, 0, 0, 25.5310902, 0,\n", + " -3.0369126, 0, 32.5437816, 0, 0, 78.9107496, 32.0416448, 0, 0, 15.9408846,\n", + " 0, 0, 0, 0, -67.7582854, -52.6103086, 0, 0, 0, 0, 0, 0, 11.8676176, 0, 0,\n", + " -41.8820812, 43.608357, 0, 0, -64.3752572\n", + "], [\n", + " 0, -15.1648222, -57.5273074, -94.2919124, -3.0777222, -77.345826,\n", + " 91.2777856, 0, 0, 0, 0, 13.5044774, 0, 0, 0, 0, 0, 0, 0, 0, 33.5509618, 0,\n", + " 0, -33.291092, -60.3050638, 0, 0, 0, 94.610171, 0, 0, 0, 0, 0, 0,\n", + " -47.270154, -81.403122, 0, 0, 47.829269, 0, 0, 0, 0, -47.4699122, 0, 0,\n", + " -31.0161918, 0, 0, -54.993563, -9.6544012, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " -17.0644648, 0, 0, 0, 0, 0, 0, 49.0555938, -82.4686508, 0, 0, 97.7299292, 0,\n", + " 0, -93.1718028\n", + "], [\n", + " 0, 0, 0, 45.5639954, 0, 0, 0, 0, 0, 92.3127826, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 0, 0, -5.8746296, 22.9079182, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 25.2727812, 0, 0, 0, 0, 0, 0, 65.0012614, 0, 67.4376806, 0, 0, 14.0298002,\n", + " 0, 0, 0, 0, 0, 33.432514, 43.000429, 0, 0, 0, 0, 0, 0, 0, 0, 0, 20.2757068,\n", + " 4.0567932, 0, 0, -15.8737758, 0, 0, 0, 0, 29.8957398, 50.0218946, 17.0262346\n", + "], [\n", + " 46.1658228, 19.630724, 0, 0, 0, 0, 0, 80.6073204, 24.5091878, 0, 0, 0,\n", + " 73.9258848, 0, 0, 0, -98.2044476, 0, 0, 0, 0, -59.2213968, 0, -70.1450004,\n", + " 0, 0, 0, 42.4721292, 0, -73.0346074, 0, 67.9829686, 0, 0, 0, 0, 6.6597886,\n", + " 39.7215082, 0, 0, 0, 52.505292, 95.9204336, 0, 0, 5.8797776, 0, 0, 0,\n", + " 36.0738246, 0, -15.2667614, 0, 0, 0, 0, 0, 0, 0, 0, 0, 50.963131, 0, 0, 0,\n", + " 49.3354032, 13.7868492, 0, 77.3708062, 26.0888548, 0, 0, 0, 0, -60.0881692,\n", + " 24.2459968\n", + "], [\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 56.562023, 0, 0, 24.9145996, 0, 0, 0, 0,\n", + " 32.8196724, -75.8879774, -12.4005106, 0, 0, 0, 0, -18.824482, -45.0330046,\n", + " 0, 0, -47.5493022, 0, 0, 0, 8.1879572, 0, 0, 4.5700222, -28.6731266, 0, 0,\n", + " 0, 0, -83.2735948, 5.4624948, 0, 0, 0, 0, 0, 0, 0, 83.664375, -52.8630198,\n", + " 58.5805764, 0, 0, 0, 45.5781336, 0, 0, -1.9768322, 0, 0, 0, 0, 91.1151128,\n", + " 0, 0, 0, 0, 0, 0, -65.5158586, 0, -22.12762\n", + "], [\n", + " 0, 0, 0, 0, 0, -50.8301006, 0, -57.183698, -24.666489, 43.0557936, 0, 0, 0,\n", + " -44.0812116, 0, 0, 0, 97.1670056, 0, 0, 0, 0, 83.4511366, 0, -96.8912356,\n", + " -6.1095452, 87.6643268, 0, 0, 0, -32.5223614, 0, 0, 0, 0, -18.5596964, 0,\n", + " 56.1790224, 0, 25.6035684, 0, 0, 0, 0, -46.8740154, 0, 0, 0, 0, 0, 0, 0,\n", + " -44.4329434, -34.719678, 85.6384822, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 48.9669952, 0, 0, 0, 0, 0, 0, 0, 0, 0, -28.2571694\n", + "], [\n", + " 0, 0, 0, 0, 42.1192158, 18.3800218, 0, 0, 33.4475094, 0, -39.4713432, 0,\n", + " 54.9744572, 0, -6.6305664, 79.0252374, 77.6908818, -61.5099746, 0, 0,\n", + " 38.4501814, 0, 0, -96.4250704, -27.809694, -34.9250884, 63.2997728, 0,\n", + " -98.8800042, 0, -7.682428, 0, 0, 0, 0, 0, 0, 7.7256608, 0, 48.5546152, 0, 0,\n", + " 0, 0, 0, 0, -58.963373, 0, 0, 0, 0, 0, 86.5062664, 7.0006556, 0, 0, 0, 0, 0,\n", + " 0, 3.1958572, 0, -46.2861844, 0, 0, 0, 0, 30.0710902, 46.6948898, 0,\n", + " 68.4681908, 0, 0, 0, 0, -51.3930766\n", + "], [\n", + " 7.1969236, 0, -79.6160432, -39.947334, -15.2636342, 0, 10.0645898,\n", + " -57.8968238, -31.9945476, 0, 91.5839662, -52.777566, 0, 0, 0, 0,\n", + " -40.9252974, 0, 0, 0, -45.5113982, 70.585349, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " -66.2523308, 0, 0, 0, 0, 0, 0, 0, 0, 85.7454324, -7.1148372, 0, 93.6584844,\n", + " 0, 0, 0, 0, 0, 0, -8.0414788, 0, 0, 0, 0, 51.3263388, 0, 0, 79.892422, 0, 0,\n", + " -64.6934098, 0, 0, -45.9577622, 0, 0, 0, -46.8290526, 0, 48.2417506, 0, 0,\n", + " 0, 0, -2.4115812, -50.7156922\n", + "], [\n", + " 62.9870494, -81.5584552, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 53.7333104, 0,\n", + " 50.6438202, -57.7938262, 0, 0, 0, 0, -78.1108892, 0, 0, 0, 0, 0, 0, 0,\n", + " 87.6584692, 0, 58.2960112, -17.5355398, 12.0497204, -60.5414862, 0, 0, 0, 0,\n", + " 0, 0, 0, 0, 38.586472, -19.7940052, 94.423359, -89.557933, 0, 0,\n", + " -77.0715722, 0, -87.707166, 70.8585278, 0, 9.0616914, 91.333051, 0, 0, 0,\n", + " 5.8166112, 0, 24.7793442, -51.000038, 0, 0, 0, 0, 0, 0, 79.9657776,\n", + " 97.4969126, 0, 0, 0, 0, -73.8994394\n", + "], [\n", + " 10.133741, 0, 0, 0, 95.6910336, 0, 0, 0, 0, -22.6696236, 0, 0, 0, 0,\n", + " 15.137996, 35.7088464, 0, -16.1971956, 0, -29.4834358, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 0, 0, 0, 0, 0, 0, -89.868739, 24.0040126, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 40.7292396, 35.7709458, 0, 34.690347, 22.3083532, 0, 0, 0, 0, 0,\n", + " -48.4224164, 0, 0, 0, 0, 91.7670744, 0, 0, 69.4045014, 0, 60.5937114,\n", + " -38.9993134, 0, 0, 0, 0, 0, 55.4599018, 0, 86.689944\n", + "], [\n", + " 0, 0, -24.842169, -52.997003, 0, 0, 0, 0, 0, 0, 0, 5.9075842, 0, 0,\n", + " -91.1447252, -5.3147106, 0, 0, 0, 4.4670454, 34.97343, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 0, 0, -5.3957052, 0, 0, 0, 0, -19.2118838, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 64.9559324, 0, 0, -10.0586402, 0, -74.8523334, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 84.8495464, 0, 0, 0, 0, 0, 0, 33.0825986, 46.995148, 0, 0, 0, -4.243203, 0,\n", + " 0, -24.0124188\n", + "]]\n", + "\n", + "def solve_qubo():\n", + " \"\"\"Solve the Qubo problem.\"\"\"\n", + "\n", + " # Constraint programming engine\n", + " model = cp_model.CpModel()\n", + "\n", + " num_vars = len(RAW_DATA)\n", + " all_vars = range(num_vars)\n", + " variables = [model.NewBoolVar('x_%i' % i) for i in all_vars]\n", + "\n", + " obj_vars = []\n", + " obj_coeffs = []\n", + "\n", + " for i in range(num_vars - 1):\n", + " x_i = variables[i]\n", + " for j in range(i + 1, num_vars):\n", + " coeff = int((RAW_DATA[i][j] + RAW_DATA[j][i]) * 1000.0)\n", + " if coeff == 0.0:\n", + " continue\n", + " x_j = variables[j]\n", + " var = model.NewBoolVar('')\n", + " model.AddBoolOr([x_i.Not(), x_j.Not(), var])\n", + " model.AddImplication(var, x_i)\n", + " model.AddImplication(var, x_j)\n", + " obj_vars.append(var)\n", + " obj_coeffs.append(coeff)\n", + "\n", + " for i in all_vars:\n", + " self_coeff = int((RAW_DATA[i][i] + RAW_DATA[i][-1]) * 1000.0)\n", + " if self_coeff != 0.0:\n", + " obj_vars.append(variables[i])\n", + " obj_coeffs.append(self_coeff)\n", + "\n", + "\n", + " model.Minimize(\n", + " sum(obj_vars[i] * obj_coeffs[i] for i in range(len(obj_vars))))\n", + " # Patch the scaling factor of the objective.\n", + " model.Proto().objective.scaling_factor = 1.0 / 1000.0\n", + " print(model.ModelStats())\n", + "\n", + " ### Solve model.\n", + " solver = cp_model.CpSolver()\n", + " solver.parameters.num_search_workers = 8\n", + " solver.parameters.log_search_progress = True\n", + " solver.Solve(model)\n", + " print(solver.ResponseStats())\n", + "\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/examples/random_tsp.ipynb b/examples/notebook/examples/random_tsp.ipynb new file mode 100644 index 0000000000..028f8722a1 --- /dev/null +++ b/examples/notebook/examples/random_tsp.ipynb @@ -0,0 +1,159 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010-2018 Google LLC\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"Traveling Salesman Sample.\n", + "\n", + " This is a sample using the routing library python wrapper to solve a\n", + " Traveling Salesman Problem.\n", + " The description of the problem can be found here:\n", + " http://en.wikipedia.org/wiki/Travelling_salesman_problem.\n", + " The optimization engine uses local search to improve solutions, first\n", + " solutions being generated using a cheapest addition heuristic.\n", + " Optionally one can randomly forbid a set of random connections between nodes\n", + " (forbidden arcs).\n", + "\"\"\"\n", + "\n", + "import argparse\n", + "from functools import partial\n", + "import random\n", + "\n", + "from ortools.constraint_solver import routing_enums_pb2\n", + "from ortools.constraint_solver import pywrapcp\n", + "\n", + "parser = argparse.ArgumentParser()\n", + "\n", + "parser.add_argument(\n", + " '--tsp_size',\n", + " default=10,\n", + " type=int,\n", + " help='Size of Traveling Salesman Problem instance.')\n", + "parser.add_argument(\n", + " '--tsp_use_random_matrix',\n", + " default=True,\n", + " type=bool,\n", + " help='Use random cost matrix.')\n", + "parser.add_argument(\n", + " '--tsp_random_forbidden_connections',\n", + " default=0,\n", + " type=int,\n", + " help='Number of random forbidden connections.')\n", + "parser.add_argument(\n", + " '--tsp_random_seed', default=0, type=int, help='Random seed.')\n", + "\n", + "# Cost/distance functions.\n", + "\n", + "\n", + "def Distance(manager, i, j):\n", + " \"\"\"Sample function.\"\"\"\n", + " # Put your distance code here.\n", + " node_i = manager.IndexToNode(i)\n", + " node_j = manager.IndexToNode(j)\n", + " return node_i + node_j\n", + "\n", + "\n", + "class RandomMatrix(object):\n", + " \"\"\"Random matrix.\"\"\"\n", + "\n", + " def __init__(self, size, seed):\n", + " \"\"\"Initialize random matrix.\"\"\"\n", + "\n", + " rand = random.Random()\n", + " rand.seed(seed)\n", + " distance_max = 100\n", + " self.matrix = {}\n", + " for from_node in range(size):\n", + " self.matrix[from_node] = {}\n", + " for to_node in range(size):\n", + " if from_node == to_node:\n", + " self.matrix[from_node][to_node] = 0\n", + " else:\n", + " self.matrix[from_node][to_node] = rand.randrange(\n", + " distance_max)\n", + "\n", + " def Distance(self, manager, from_index, to_index):\n", + " return self.matrix[manager.IndexToNode(from_index)][manager.IndexToNode(\n", + " to_index)]\n", + "\n", + "\n", + "# Create routing model\n", + "if args.tsp_size > 0:\n", + " # TSP of size args.tsp_size\n", + " # Second argument = 1 to build a single tour (it's a TSP).\n", + " # Nodes are indexed from 0 to args_tsp_size - 1, by default the start of\n", + " # the route is node 0.\n", + " manager = pywrapcp.RoutingIndexManager(args.tsp_size, 1, 0)\n", + " routing = pywrapcp.RoutingModel(manager)\n", + " search_parameters = pywrapcp.DefaultRoutingSearchParameters()\n", + " # Setting first solution heuristic (cheapest addition).\n", + " search_parameters.first_solution_strategy = (\n", + " routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC)\n", + "\n", + " # Setting the cost function.\n", + " # Put a callback to the distance accessor here. The callback takes two\n", + " # arguments (the from and to node indices) and returns the distance between\n", + " # these indices.\n", + " cost = 0\n", + " if args.tsp_use_random_matrix:\n", + " matrix = RandomMatrix(args.tsp_size, args.tsp_random_seed)\n", + " cost = routing.RegisterTransitCallback(\n", + " partial(matrix.Distance, manager))\n", + " else:\n", + " cost = routing.RegisterTransitCallback(partial(Distance, manager))\n", + " routing.SetArcCostEvaluatorOfAllVehicles(cost)\n", + " # Forbid node connections (randomly).\n", + " rand = random.Random()\n", + " rand.seed(args.tsp_random_seed)\n", + " forbidden_connections = 0\n", + " while forbidden_connections < args.tsp_random_forbidden_connections:\n", + " from_node = rand.randrange(args.tsp_size - 1)\n", + " to_node = rand.randrange(args.tsp_size - 1) + 1\n", + " if routing.NextVar(from_node).Contains(to_node):\n", + " print('Forbidding connection ' + str(from_node) + ' -> ' +\n", + " str(to_node))\n", + " routing.NextVar(from_node).RemoveValue(to_node)\n", + " forbidden_connections += 1\n", + "\n", + " # Solve, returns a solution if any.\n", + " assignment = routing.Solve()\n", + " if assignment:\n", + " # Solution cost.\n", + " print(assignment.ObjectiveValue())\n", + " # Inspect solution.\n", + " # Only one route here; otherwise iterate from 0 to routing.vehicles() - 1\n", + " route_number = 0\n", + " node = routing.Start(route_number)\n", + " route = ''\n", + " while not routing.IsEnd(node):\n", + " route += str(node) + ' -> '\n", + " node = assignment.Value(routing.NextVar(node))\n", + " route += '0'\n", + " print(route)\n", + " else:\n", + " print('No solution found.')\n", + "else:\n", + " print('Specify an instance greater than 0.')\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/examples/rcpsp_sat.ipynb b/examples/notebook/examples/rcpsp_sat.ipynb new file mode 100644 index 0000000000..790239399f --- /dev/null +++ b/examples/notebook/examples/rcpsp_sat.ipynb @@ -0,0 +1,299 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010-2018 Google LLC\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"Sat based solver for the RCPSP problems (see rcpsp.proto).\"\"\"\n", + "\n", + "from __future__ import absolute_import\n", + "from __future__ import division\n", + "from __future__ import print_function\n", + "\n", + "import argparse\n", + "import collections\n", + "import time\n", + "\n", + "from google.protobuf import text_format\n", + "from ortools.data import pywraprcpsp\n", + "from ortools.sat.python import cp_model\n", + "\n", + "PARSER = argparse.ArgumentParser()\n", + "PARSER.add_argument(\n", + " '--input', default=\"\", help='Input file to parse and solve.')\n", + "PARSER.add_argument(\n", + " '--output_proto',\n", + " default=\"\",\n", + " help='Output file to write the cp_model'\n", + " 'proto to.')\n", + "PARSER.add_argument('--params', default=\"\", help='Sat solver parameters.')\n", + "\n", + "\n", + "class SolutionPrinter(cp_model.CpSolverSolutionCallback):\n", + " \"\"\"Print intermediate solutions.\"\"\"\n", + "\n", + " def __init__(self):\n", + " cp_model.CpSolverSolutionCallback.__init__(self)\n", + " self.__solution_count = 0\n", + " self.__start_time = time.time()\n", + "\n", + " def on_solution_callback(self):\n", + " current_time = time.time()\n", + " objective = self.ObjectiveValue()\n", + " print('Solution %i, time = %f s, objective = %i' %\n", + " (self.__solution_count, current_time - self.__start_time,\n", + " objective))\n", + " self.__solution_count += 1\n", + "\n", + "\n", + "def solve_rcpsp(problem, proto_file, params):\n", + " \"\"\"Parse and solve a given RCPSP problem in proto format.\"\"\"\n", + "\n", + " # Determine problem type.\n", + " problem_type = ('Resource Investment Problem'\n", + " if problem.is_resource_investment else 'RCPSP')\n", + "\n", + " if problem.is_rcpsp_max:\n", + " problem_type += '/Max delay'\n", + " # We print 2 less tasks as these are sentinel tasks that are not counted in\n", + " # the description of the rcpsp models.\n", + " if problem.is_consumer_producer:\n", + " print('Solving %s with %i reservoir resources and %i tasks' %\n", + " (problem_type, len(problem.resources), len(problem.tasks) - 2))\n", + " else:\n", + " print('Solving %s with %i resources and %i tasks' %\n", + " (problem_type, len(problem.resources), len(problem.tasks) - 2))\n", + "\n", + " # Create the model.\n", + " model = cp_model.CpModel()\n", + "\n", + " num_tasks = len(problem.tasks)\n", + " num_resources = len(problem.resources)\n", + "\n", + " all_active_tasks = range(1, num_tasks - 1)\n", + " all_resources = range(num_resources)\n", + "\n", + " horizon = problem.deadline if problem.deadline != -1 else problem.horizon\n", + " if horizon == -1: # Naive computation.\n", + " horizon = sum(max(r.duration for r in t.recipes) for t in problem.tasks)\n", + " if problem.is_rcpsp_max:\n", + " for t in problem.tasks:\n", + " for sd in t.successor_delays:\n", + " for rd in sd.recipe_delays:\n", + " for d in rd.min_delays:\n", + " horizon += abs(d)\n", + " print(' - horizon = %i' % horizon)\n", + "\n", + " # Containers used to build resources.\n", + " intervals_per_resource = collections.defaultdict(list)\n", + " demands_per_resource = collections.defaultdict(list)\n", + " presences_per_resource = collections.defaultdict(list)\n", + " starts_per_resource = collections.defaultdict(list)\n", + "\n", + " # Starts and ends for master interval variables.\n", + " task_starts = {}\n", + " task_ends = {}\n", + "\n", + " # Containers for per-recipe per task variables.\n", + " alternatives_per_task = collections.defaultdict(list)\n", + " presences_per_task = collections.defaultdict(list)\n", + " starts_per_task = collections.defaultdict(list)\n", + " ends_per_task = collections.defaultdict(list)\n", + "\n", + " one = model.NewIntVar(1, 1, 'one')\n", + "\n", + " # Create tasks.\n", + " for t in all_active_tasks:\n", + " task = problem.tasks[t]\n", + "\n", + " if len(task.recipes) == 1:\n", + " # Create interval.\n", + " recipe = task.recipes[0]\n", + " task_starts[t] = model.NewIntVar(0, horizon, 'start_of_task_%i' % t)\n", + " task_ends[t] = model.NewIntVar(0, horizon, 'end_of_task_%i' % t)\n", + " interval = model.NewIntervalVar(task_starts[t], recipe.duration,\n", + " task_ends[t], 'interval_%i' % t)\n", + "\n", + " # Store for later.\n", + " alternatives_per_task[t].append(interval)\n", + " starts_per_task[t].append(task_starts[t])\n", + " ends_per_task[t].append(task_ends[t])\n", + " presences_per_task[t].append(one)\n", + "\n", + " # Register for resources.\n", + " for i in range(len(recipe.demands)):\n", + " demand = recipe.demands[i]\n", + " res = recipe.resources[i]\n", + " demands_per_resource[res].append(demand)\n", + " if problem.resources[res].renewable:\n", + " intervals_per_resource[res].append(interval)\n", + " else:\n", + " starts_per_resource[res].append(task_starts[t])\n", + " presences_per_resource[res].append(1)\n", + " else:\n", + " all_recipes = range(len(task.recipes))\n", + "\n", + " # Compute duration range.\n", + " min_size = min(recipe.duration for recipe in task.recipes)\n", + " max_size = max(recipe.duration for recipe in task.recipes)\n", + "\n", + " # Create one optional interval per recipe.\n", + " for r in all_recipes:\n", + " recipe = task.recipes[r]\n", + " is_present = model.NewBoolVar('is_present_%i_r%i' % (t, r))\n", + " start = model.NewIntVar(0, horizon, 'start_%i_r%i' % (t, r))\n", + " end = model.NewIntVar(0, horizon, 'end_%i_r%i' % (t, r))\n", + " interval = model.NewOptionalIntervalVar(\n", + " start, recipe.duration, end, is_present,\n", + " 'interval_%i_r%i' % (t, r))\n", + "\n", + " # Store variables.\n", + " alternatives_per_task[t].append(interval)\n", + " starts_per_task[t].append(start)\n", + " ends_per_task[t].append(end)\n", + " presences_per_task[t].append(is_present)\n", + "\n", + " # Register intervals in resources.\n", + " for i in range(len(recipe.demands)):\n", + " demand = recipe.demands[i]\n", + " res = recipe.resources[i]\n", + " demands_per_resource[res].append(demand)\n", + " if problem.resources[res].renewable:\n", + " intervals_per_resource[res].append(interval)\n", + " else:\n", + " starts_per_resource[res].append(start)\n", + " presences_per_resource[res].append(is_present)\n", + "\n", + " # Create the master interval for the task.\n", + " task_starts[t] = model.NewIntVar(0, horizon, 'start_of_task_%i' % t)\n", + " task_ends[t] = model.NewIntVar(0, horizon, 'end_of_task_%i' % t)\n", + " duration = model.NewIntVar(min_size, max_size,\n", + " 'duration_of_task_%i' % t)\n", + " interval = model.NewIntervalVar(task_starts[t], duration,\n", + " task_ends[t], 'interval_%i' % t)\n", + "\n", + " # Link with optional per-recipe copies.\n", + " for r in all_recipes:\n", + " p = presences_per_task[t][r]\n", + " model.Add(\n", + " task_starts[t] == starts_per_task[t][r]).OnlyEnforceIf(p)\n", + " model.Add(task_ends[t] == ends_per_task[t][r]).OnlyEnforceIf(p)\n", + " model.Add(duration == task.recipes[r].duration).OnlyEnforceIf(p)\n", + " model.Add(sum(presences_per_task[t]) == 1)\n", + "\n", + " # Create makespan variable\n", + " makespan = model.NewIntVar(0, horizon, 'makespan')\n", + "\n", + " # Add precedences.\n", + " if problem.is_rcpsp_max:\n", + " for task_id in all_active_tasks:\n", + " task = problem.tasks[task_id]\n", + " num_modes = len(task.recipes)\n", + "\n", + " for successor_index in range(len(task.successors)):\n", + " next_id = task.successors[successor_index]\n", + " delay_matrix = task.successor_delays[successor_index]\n", + " num_next_modes = len(problem.tasks[next_id].recipes)\n", + " for m1 in range(num_modes):\n", + " s1 = starts_per_task[task_id][m1]\n", + " p1 = presences_per_task[task_id][m1]\n", + " if next_id == num_tasks - 1:\n", + " delay = delay_matrix.recipe_delays[m1].min_delays[0]\n", + " model.Add(s1 + delay <= makespan).OnlyEnforceIf(p1)\n", + " else:\n", + " for m2 in range(num_next_modes):\n", + " delay = delay_matrix.recipe_delays[m1].min_delays[\n", + " m2]\n", + " s2 = starts_per_task[next_id][m2]\n", + " p2 = presences_per_task[next_id][m2]\n", + " model.Add(s1 + delay <= s2).OnlyEnforceIf([p1, p2])\n", + " else: # Normal dependencies (task ends before the start of successors).\n", + " for t in all_active_tasks:\n", + " for n in problem.tasks[t].successors:\n", + " if n == num_tasks - 1:\n", + " model.Add(task_ends[t] <= makespan)\n", + " else:\n", + " model.Add(task_ends[t] <= task_starts[n])\n", + "\n", + " # Containers for resource investment problems.\n", + " capacities = []\n", + " max_cost = 0\n", + "\n", + " # Create resources.\n", + " for r in all_resources:\n", + " resource = problem.resources[r]\n", + " c = resource.max_capacity\n", + " if c == -1:\n", + " c = sum(demands_per_resource[r])\n", + "\n", + " if problem.is_resource_investment:\n", + " # RIP problems have only renewable resources.\n", + " capacity = model.NewIntVar(0, c, 'capacity_of_%i' % r)\n", + " model.AddCumulative(intervals_per_resource[r],\n", + " demands_per_resource[r], capacity)\n", + " capacities.append(capacity)\n", + " max_cost += c * resource.unit_cost\n", + " elif resource.renewable:\n", + " if intervals_per_resource[r]:\n", + " model.AddCumulative(intervals_per_resource[r],\n", + " demands_per_resource[r], c)\n", + " elif presences_per_resource[r]: # Non empty non renewable resource.\n", + " if problem.is_consumer_producer:\n", + " model.AddReservoirConstraint(\n", + " starts_per_resource[r], demands_per_resource[r],\n", + " resource.min_capacity, resource.max_capacity)\n", + " else:\n", + " model.Add(\n", + " sum(presences_per_resource[r][i] *\n", + " demands_per_resource[r][i]\n", + " for i in range(len(presences_per_resource[r]))) <= c)\n", + "\n", + " # Objective.\n", + " if problem.is_resource_investment:\n", + " objective = model.NewIntVar(0, max_cost, 'capacity_costs')\n", + " model.Add(objective == sum(problem.resources[i].unit_cost * capacities[\n", + " i] for i in range(len(capacities))))\n", + " else:\n", + " objective = makespan\n", + "\n", + " model.Minimize(objective)\n", + "\n", + " if proto_file:\n", + " print('Writing proto to %s' % proto_file)\n", + " with open(proto_file, 'w') as text_file:\n", + " text_file.write(str(model))\n", + "\n", + " # Solve model.\n", + " solver = cp_model.CpSolver()\n", + " if params:\n", + " text_format.Merge(params, solver.parameters)\n", + " solution_printer = SolutionPrinter()\n", + " solver.SolveWithSolutionCallback(model, solution_printer)\n", + " print(solver.ResponseStats())\n", + "\n", + "\n", + "rcpsp_parser = pywraprcpsp.RcpspParser()\n", + "rcpsp_parser.ParseFile(args.input)\n", + "solve_rcpsp(rcpsp_parser.Problem(), args.output_proto, args.params)\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/examples/reallocate_sat.ipynb b/examples/notebook/examples/reallocate_sat.ipynb new file mode 100644 index 0000000000..6970210784 --- /dev/null +++ b/examples/notebook/examples/reallocate_sat.ipynb @@ -0,0 +1,148 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010-2018 Google LLC\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"Reallocate production to smooth it over years.\"\"\"\n", + "\n", + "from __future__ import print_function\n", + "\n", + "import collections\n", + "\n", + "from ortools.sat.python import cp_model\n", + "\n", + "\n", + "\n", + "# Data\n", + "data_0 = [\n", + " [107, 107, 107, 0, 0], # pr1\n", + " [0, 47, 47, 47, 0], # pr2\n", + " [10, 10, 10, 0, 0], # pr3\n", + " [0, 55, 55, 55, 55], # pr4\n", + "]\n", + "\n", + "data_1 = [\n", + " [119444030, 0, 0, 0],\n", + " [34585586, 38358559, 31860661, 0],\n", + " [19654655, 21798799, 18106106, 0],\n", + " [298836792, 0, 0, 0],\n", + " [3713428, 4118530, 4107277, 3072018],\n", + " [6477273, 7183884, 5358471, 0],\n", + " [1485371, 1647412, 1642911, 1228807]\n", + "]\n", + "\n", + "data_2 = [\n", + " [1194440, 0, 0, 0],\n", + " [345855, 383585, 318606, 0],\n", + " [196546, 217987, 181061, 0],\n", + " [2988367, 0, 0, 0],\n", + " [37134, 41185, 41072, 30720],\n", + " [64772, 71838, 53584, 0],\n", + " [14853, 16474, 16429, 12288]\n", + "]\n", + "\n", + "pr = data_0\n", + "\n", + "num_pr = len(pr)\n", + "num_years = len(pr[1])\n", + "total = sum(pr[p][y] for p in range(num_pr) for y in range(num_years))\n", + "avg = total // num_years\n", + "\n", + "# Model\n", + "model = cp_model.CpModel()\n", + "\n", + "# Variables\n", + "delta = model.NewIntVar(0, total, 'delta')\n", + "\n", + "contributions_per_years = collections.defaultdict(list)\n", + "contributions_per_prs = collections.defaultdict(list)\n", + "all_contribs = {}\n", + "\n", + "for p, inner_l in enumerate(pr):\n", + " for y, item in enumerate(inner_l):\n", + " if item != 0:\n", + " contrib = model.NewIntVar(0, total, 'r%d c%d' % (p, y))\n", + " contributions_per_years[y].append(contrib)\n", + " contributions_per_prs[p].append(contrib)\n", + " all_contribs[p, y] = contrib\n", + "\n", + "year_var = [\n", + " model.NewIntVar(0, total, 'y[%i]' % i) for i in range(num_years)\n", + "]\n", + "\n", + "# Constraints\n", + "\n", + "# Maintain year_var.\n", + "for y in range(num_years):\n", + " model.Add(year_var[y] == sum(contributions_per_years[y]))\n", + "\n", + "# Fixed contributions per pr.\n", + "for p in range(num_pr):\n", + " model.Add(sum(pr[p]) == sum(contributions_per_prs[p]))\n", + "\n", + "# Link delta with variables.\n", + "for y in range(num_years):\n", + " model.Add(year_var[y] >= avg - delta)\n", + "\n", + "for y in range(num_years):\n", + " model.Add(year_var[y] <= avg + delta)\n", + "\n", + "# Solve and output\n", + "model.Minimize(delta)\n", + "\n", + "# Solve model.\n", + "solver = cp_model.CpSolver()\n", + "status = solver.Solve(model)\n", + "\n", + "# Output solution.\n", + "if status == cp_model.OPTIMAL:\n", + " print('Data')\n", + " print(' - total = ', total)\n", + " print(' - year_average = ', avg)\n", + " print(' - number of projects = ', num_pr)\n", + " print(' - number of years = ', num_years)\n", + "\n", + " print(' - input production')\n", + " for p in range(num_pr):\n", + " for y in range(num_years):\n", + " if pr[p][y] == 0:\n", + " print(' ', end='')\n", + " else:\n", + " print('%10i' % pr[p][y], end='')\n", + " print()\n", + "\n", + " print('Solution')\n", + " for p in range(num_pr):\n", + " for y in range(num_years):\n", + " if pr[p][y] == 0:\n", + " print(' ', end='')\n", + " else:\n", + " print('%10i' % solver.Value(all_contribs[p, y]), end='')\n", + " print()\n", + "\n", + " for y in range(num_years):\n", + " print('%10i' % solver.Value(year_var[y]), end='')\n", + " print()\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/examples/shift_scheduling_sat.ipynb b/examples/notebook/examples/shift_scheduling_sat.ipynb new file mode 100644 index 0000000000..8dca07a63c --- /dev/null +++ b/examples/notebook/examples/shift_scheduling_sat.ipynb @@ -0,0 +1,437 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010-2018 Google LLC\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"Creates a shift scheduling problem and solves it.\"\"\"\n", + "\n", + "from __future__ import print_function\n", + "\n", + "import argparse\n", + "\n", + "from ortools.sat.python import cp_model\n", + "\n", + "from google.protobuf import text_format\n", + "\n", + "PARSER = argparse.ArgumentParser()\n", + "PARSER.add_argument(\n", + " '--output_proto',\n", + " default=\"\",\n", + " help='Output file to write the cp_model'\n", + " 'proto to.')\n", + "PARSER.add_argument('--params', default=\"\", help='Sat solver parameters.')\n", + "\n", + "\n", + "def negated_bounded_span(works, start, length):\n", + " \"\"\"Filters an isolated sub-sequence of variables assined to True.\n", + "\n", + " Extract the span of Boolean variables [start, start + length), negate them,\n", + " and if there is variables to the left/right of this span, surround the span by\n", + " them in non negated form.\n", + "\n", + " Args:\n", + " works: a list of variables to extract the span from.\n", + " start: the start to the span.\n", + " length: the length of the span.\n", + "\n", + " Returns:\n", + " a list of variables which conjunction will be false if the sub-list is\n", + " assigned to True, and correctly bounded by variables assigned to False,\n", + " or by the start or end of works.\n", + " \"\"\"\n", + " sequence = []\n", + " # Left border (start of works, or works[start - 1])\n", + " if start > 0:\n", + " sequence.append(works[start - 1])\n", + " for i in range(length):\n", + " sequence.append(works[start + i].Not())\n", + " # Right border (end of works or works[start + length])\n", + " if start + length < len(works):\n", + " sequence.append(works[start + length])\n", + " return sequence\n", + "\n", + "\n", + "def add_soft_sequence_constraint(model, works, hard_min, soft_min, min_cost,\n", + " soft_max, hard_max, max_cost, prefix):\n", + " \"\"\"Sequence constraint on true variables with soft and hard bounds.\n", + "\n", + " This constraint look at every maximal contiguous sequence of variables\n", + " assigned to true. If forbids sequence of length < hard_min or > hard_max.\n", + " Then it creates penalty terms if the length is < soft_min or > soft_max.\n", + "\n", + " Args:\n", + " model: the sequence constraint is built on this model.\n", + " works: a list of Boolean variables.\n", + " hard_min: any sequence of true variables must have a length of at least\n", + " hard_min.\n", + " soft_min: any sequence should have a length of at least soft_min, or a\n", + " linear penalty on the delta will be added to the objective.\n", + " min_cost: the coefficient of the linear penalty if the length is less than\n", + " soft_min.\n", + " soft_max: any sequence should have a length of at most soft_max, or a linear\n", + " penalty on the delta will be added to the objective.\n", + " hard_max: any sequence of true variables must have a length of at most\n", + " hard_max.\n", + " max_cost: the coefficient of the linear penalty if the length is more than\n", + " soft_max.\n", + " prefix: a base name for penalty literals.\n", + "\n", + " Returns:\n", + " a tuple (variables_list, coefficient_list) containing the different\n", + " penalties created by the sequence constraint.\n", + " \"\"\"\n", + " cost_literals = []\n", + " cost_coefficients = []\n", + "\n", + " # Forbid sequences that are too short.\n", + " for length in range(1, hard_min):\n", + " for start in range(len(works) - length + 1):\n", + " model.AddBoolOr(negated_bounded_span(works, start, length))\n", + "\n", + " # Penalize sequences that are below the soft limit.\n", + " if min_cost > 0:\n", + " for length in range(hard_min, soft_min):\n", + " for start in range(len(works) - length + 1):\n", + " span = negated_bounded_span(works, start, length)\n", + " name = ': under_span(start=%i, length=%i)' % (start, length)\n", + " lit = model.NewBoolVar(prefix + name)\n", + " span.append(lit)\n", + " model.AddBoolOr(span)\n", + " cost_literals.append(lit)\n", + " # We filter exactly the sequence with a short length.\n", + " # The penalty is proportional to the delta with soft_min.\n", + " cost_coefficients.append(min_cost * (soft_min - length))\n", + "\n", + " # Penalize sequences that are above the soft limit.\n", + " if max_cost > 0:\n", + " for length in range(soft_max + 1, hard_max + 1):\n", + " for start in range(len(works) - length + 1):\n", + " span = negated_bounded_span(works, start, length)\n", + " name = ': over_span(start=%i, length=%i)' % (start, length)\n", + " lit = model.NewBoolVar(prefix + name)\n", + " span.append(lit)\n", + " model.AddBoolOr(span)\n", + " cost_literals.append(lit)\n", + " # Cost paid is max_cost * excess length.\n", + " cost_coefficients.append(max_cost * (length - soft_max))\n", + "\n", + " # Just forbid any sequence of true variables with length hard_max + 1\n", + " for start in range(len(works) - hard_max):\n", + " model.AddBoolOr(\n", + " [works[i].Not() for i in range(start, start + hard_max + 1)])\n", + " return cost_literals, cost_coefficients\n", + "\n", + "\n", + "def add_soft_sum_constraint(model, works, hard_min, soft_min, min_cost,\n", + " soft_max, hard_max, max_cost, prefix):\n", + " \"\"\"Sum constraint with soft and hard bounds.\n", + "\n", + " This constraint counts the variables assigned to true from works.\n", + " If forbids sum < hard_min or > hard_max.\n", + " Then it creates penalty terms if the sum is < soft_min or > soft_max.\n", + "\n", + " Args:\n", + " model: the sequence constraint is built on this model.\n", + " works: a list of Boolean variables.\n", + " hard_min: any sequence of true variables must have a sum of at least\n", + " hard_min.\n", + " soft_min: any sequence should have a sum of at least soft_min, or a linear\n", + " penalty on the delta will be added to the objective.\n", + " min_cost: the coefficient of the linear penalty if the sum is less than\n", + " soft_min.\n", + " soft_max: any sequence should have a sum of at most soft_max, or a linear\n", + " penalty on the delta will be added to the objective.\n", + " hard_max: any sequence of true variables must have a sum of at most\n", + " hard_max.\n", + " max_cost: the coefficient of the linear penalty if the sum is more than\n", + " soft_max.\n", + " prefix: a base name for penalty variables.\n", + "\n", + " Returns:\n", + " a tuple (variables_list, coefficient_list) containing the different\n", + " penalties created by the sequence constraint.\n", + " \"\"\"\n", + " cost_variables = []\n", + " cost_coefficients = []\n", + " sum_var = model.NewIntVar(hard_min, hard_max, '')\n", + " # This adds the hard constraints on the sum.\n", + " model.Add(sum_var == sum(works))\n", + "\n", + " # Penalize sums below the soft_min target.\n", + " if soft_min > hard_min and min_cost > 0:\n", + " delta = model.NewIntVar(-len(works), len(works), '')\n", + " model.Add(delta == soft_min - sum_var)\n", + " # TODO(user): Compare efficiency with only excess >= soft_min - sum_var.\n", + " excess = model.NewIntVar(0, 7, prefix + ': under_sum')\n", + " model.AddMaxEquality(excess, [delta, 0])\n", + " cost_variables.append(excess)\n", + " cost_coefficients.append(min_cost)\n", + "\n", + " # Penalize sums above the soft_max target.\n", + " if soft_max < hard_max and max_cost > 0:\n", + " delta = model.NewIntVar(-7, 7, '')\n", + " model.Add(delta == sum_var - soft_max)\n", + " excess = model.NewIntVar(0, 7, prefix + ': over_sum')\n", + " model.AddMaxEquality(excess, [delta, 0])\n", + " cost_variables.append(excess)\n", + " cost_coefficients.append(max_cost)\n", + "\n", + " return cost_variables, cost_coefficients\n", + "\n", + "\n", + "def solve_shift_scheduling(params, output_proto):\n", + " \"\"\"Solves the shift scheduling problem.\"\"\"\n", + " # Data\n", + " num_employees = 8\n", + " num_weeks = 3\n", + " shifts = ['O', 'M', 'A', 'N']\n", + "\n", + " # Fixed assignment: (employee, shift, day).\n", + " # This fixes the first 2 days of the schedule.\n", + " fixed_assignments = [\n", + " (0, 0, 0),\n", + " (1, 0, 0),\n", + " (2, 1, 0),\n", + " (3, 1, 0),\n", + " (4, 2, 0),\n", + " (5, 2, 0),\n", + " (6, 2, 3),\n", + " (7, 3, 0),\n", + " (0, 1, 1),\n", + " (1, 1, 1),\n", + " (2, 2, 1),\n", + " (3, 2, 1),\n", + " (4, 2, 1),\n", + " (5, 0, 1),\n", + " (6, 0, 1),\n", + " (7, 3, 1),\n", + " ]\n", + "\n", + " # Request: (employee, shift, day, weight)\n", + " # A negative weight indicates that the employee desire this assignment.\n", + " requests = [\n", + " # Employee 3 wants the first Saturday off.\n", + " (3, 0, 5, -2),\n", + " # Employee 5 wants a night shift on the second Thursday.\n", + " (5, 3, 10, -2),\n", + " # Employee 2 does not want a night shift on the first Friday.\n", + " (2, 3, 4, 4)\n", + " ]\n", + "\n", + " # Shift constraints on continuous sequence :\n", + " # (shift, hard_min, soft_min, min_penalty,\n", + " # soft_max, hard_max, max_penalty)\n", + " shift_constraints = [\n", + " # One or two consecutive days of rest, this is a hard constraint.\n", + " (0, 1, 1, 0, 2, 2, 0),\n", + " # betweem 2 and 3 consecutive days of night shifts, 1 and 4 are\n", + " # possible but penalized.\n", + " (3, 1, 2, 20, 3, 4, 5),\n", + " ]\n", + "\n", + " # Weekly sum constraints on shifts days:\n", + " # (shift, hard_min, soft_min, min_penalty,\n", + " # soft_max, hard_max, max_penalty)\n", + " weekly_sum_constraints = [\n", + " # Constraints on rests per week.\n", + " (0, 1, 2, 7, 2, 3, 4),\n", + " # At least 1 night shift per week (penalized). At most 4 (hard).\n", + " (3, 0, 1, 3, 4, 4, 0),\n", + " ]\n", + "\n", + " # Penalized transitions:\n", + " # (previous_shift, next_shift, penalty (0 means forbidden))\n", + " penalized_transitions = [\n", + " # Afternoon to night has a penalty of 4.\n", + " (2, 3, 4),\n", + " # Night to morning is forbidden.\n", + " (3, 1, 0),\n", + " ]\n", + "\n", + " # daily demands for work shifts (morning, afternon, night) for each day\n", + " # of the week starting on Monday.\n", + " weekly_cover_demands = [\n", + " (2, 3, 1), # Monday\n", + " (2, 3, 1), # Tuesday\n", + " (2, 2, 2), # Wednesday\n", + " (2, 3, 1), # Thursday\n", + " (2, 2, 2), # Friday\n", + " (1, 2, 3), # Saturday\n", + " (1, 3, 1), # Sunday\n", + " ]\n", + "\n", + " # Penalty for exceeding the cover constraint per shift type.\n", + " excess_cover_penalties = (2, 2, 5)\n", + "\n", + " num_days = num_weeks * 7\n", + " num_shifts = len(shifts)\n", + "\n", + " model = cp_model.CpModel()\n", + "\n", + " work = {}\n", + " for e in range(num_employees):\n", + " for s in range(num_shifts):\n", + " for d in range(num_days):\n", + " work[e, s, d] = model.NewBoolVar('work%i_%i_%i' % (e, s, d))\n", + "\n", + " # Linear terms of the objective in a minimization context.\n", + " obj_int_vars = []\n", + " obj_int_coeffs = []\n", + " obj_bool_vars = []\n", + " obj_bool_coeffs = []\n", + "\n", + " # Exactly one shift per day.\n", + " for e in range(num_employees):\n", + " for d in range(num_days):\n", + " model.Add(sum(work[e, s, d] for s in range(num_shifts)) == 1)\n", + "\n", + " # Fixed assignments.\n", + " for e, s, d in fixed_assignments:\n", + " model.Add(work[e, s, d] == 1)\n", + "\n", + " # Employee requests\n", + " for e, s, d, w in requests:\n", + " obj_bool_vars.append(work[e, s, d])\n", + " obj_bool_coeffs.append(w)\n", + "\n", + " # Shift constraints\n", + " for ct in shift_constraints:\n", + " shift, hard_min, soft_min, min_cost, soft_max, hard_max, max_cost = ct\n", + " for e in range(num_employees):\n", + " works = [work[e, shift, d] for d in range(num_days)]\n", + " variables, coeffs = add_soft_sequence_constraint(\n", + " model, works, hard_min, soft_min, min_cost, soft_max, hard_max,\n", + " max_cost, 'shift_constraint(employee %i, shift %i)' % (e,\n", + " shift))\n", + " obj_bool_vars.extend(variables)\n", + " obj_bool_coeffs.extend(coeffs)\n", + "\n", + " # Weekly sum constraints\n", + " for ct in weekly_sum_constraints:\n", + " shift, hard_min, soft_min, min_cost, soft_max, hard_max, max_cost = ct\n", + " for e in range(num_employees):\n", + " for w in range(num_weeks):\n", + " works = [work[e, shift, d + w * 7] for d in range(7)]\n", + " variables, coeffs = add_soft_sum_constraint(\n", + " model, works, hard_min, soft_min, min_cost, soft_max,\n", + " hard_max, max_cost,\n", + " 'weekly_sum_constraint(employee %i, shift %i, week %i)' %\n", + " (e, shift, w))\n", + " obj_int_vars.extend(variables)\n", + " obj_int_coeffs.extend(coeffs)\n", + "\n", + " # Penalized transitions\n", + " for previous_shift, next_shift, cost in penalized_transitions:\n", + " for e in range(num_employees):\n", + " for d in range(num_days - 1):\n", + " transition = [\n", + " work[e, previous_shift, d].Not(),\n", + " work[e, next_shift, d + 1].Not()\n", + " ]\n", + " if cost == 0:\n", + " model.AddBoolOr(transition)\n", + " else:\n", + " trans_var = model.NewBoolVar(\n", + " 'transition (employee=%i, day=%i)' % (e, d))\n", + " transition.append(trans_var)\n", + " model.AddBoolOr(transition)\n", + " obj_bool_vars.append(trans_var)\n", + " obj_bool_coeffs.append(cost)\n", + "\n", + " # Cover constraints\n", + " for s in range(1, num_shifts):\n", + " for w in range(num_weeks):\n", + " for d in range(7):\n", + " works = [work[e, s, w * 7 + d] for e in range(num_employees)]\n", + " # Ignore Off shift.\n", + " min_demand = weekly_cover_demands[d][s - 1]\n", + " worked = model.NewIntVar(min_demand, num_employees, '')\n", + " model.Add(worked == sum(works))\n", + " over_penalty = excess_cover_penalties[s - 1]\n", + " if over_penalty > 0:\n", + " name = 'excess_demand(shift=%i, week=%i, day=%i)' % (s, w,\n", + " d)\n", + " excess = model.NewIntVar(0, num_employees - min_demand,\n", + " name)\n", + " model.Add(excess == worked - min_demand)\n", + " obj_int_vars.append(excess)\n", + " obj_int_coeffs.append(over_penalty)\n", + "\n", + " # Objective\n", + " model.Minimize(\n", + " sum(obj_bool_vars[i] * obj_bool_coeffs[i]\n", + " for i in range(len(obj_bool_vars)))\n", + " + sum(obj_int_vars[i] * obj_int_coeffs[i]\n", + " for i in range(len(obj_int_vars))))\n", + "\n", + " if output_proto:\n", + " print('Writing proto to %s' % output_proto)\n", + " with open(output_proto, 'w') as text_file:\n", + " text_file.write(str(model))\n", + "\n", + " # Solve the model.\n", + " solver = cp_model.CpSolver()\n", + " solver.parameters.num_search_workers = 8\n", + " if params:\n", + " text_format.Merge(params, solver.parameters)\n", + " solution_printer = cp_model.ObjectiveSolutionPrinter()\n", + " status = solver.SolveWithSolutionCallback(model, solution_printer)\n", + "\n", + " # Print solution.\n", + " if status == cp_model.OPTIMAL or status == cp_model.FEASIBLE:\n", + " print()\n", + " header = ' '\n", + " for w in range(num_weeks):\n", + " header += 'M T W T F S S '\n", + " print(header)\n", + " for e in range(num_employees):\n", + " schedule = ''\n", + " for d in range(num_days):\n", + " for s in range(num_shifts):\n", + " if solver.BooleanValue(work[e, s, d]):\n", + " schedule += shifts[s] + ' '\n", + " print('worker %i: %s' % (e, schedule))\n", + " print()\n", + " print('Penalties:')\n", + " for i, var in enumerate(obj_bool_vars):\n", + " if solver.BooleanValue(var):\n", + " penalty = obj_bool_coeffs[i]\n", + " if penalty > 0:\n", + " print(' %s violated, penalty=%i' % (var.Name(), penalty))\n", + " else:\n", + " print(' %s fulfilled, gain=%i' % (var.Name(), -penalty))\n", + "\n", + " for i, var in enumerate(obj_int_vars):\n", + " if solver.Value(var) > 0:\n", + " print(' %s violated by %i, linear penalty=%i' %\n", + " (var.Name(), solver.Value(var), obj_int_coeffs[i]))\n", + "\n", + " print()\n", + " print(solver.ResponseStats())\n", + "\n", + "\n", + "\"\"\"Main.\"\"\"\n", + "solve_shift_scheduling(args.params, args.output_proto)\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/examples/single_machine_scheduling_with_setup_release_due_dates_sat.ipynb b/examples/notebook/examples/single_machine_scheduling_with_setup_release_due_dates_sat.ipynb new file mode 100644 index 0000000000..6550dcef67 --- /dev/null +++ b/examples/notebook/examples/single_machine_scheduling_with_setup_release_due_dates_sat.ipynb @@ -0,0 +1,289 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010-2018 Google LLC\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"Single machine jobshop with setup times, release dates and due dates.\"\"\"\n", + "from __future__ import print_function\n", + "from __future__ import absolute_import\n", + "from __future__ import division\n", + "\n", + "import argparse\n", + "\n", + "from google.protobuf import text_format\n", + "from ortools.sat.python import cp_model\n", + "\n", + "#----------------------------------------------------------------------------\n", + "# Command line arguments.\n", + "PARSER = argparse.ArgumentParser()\n", + "PARSER.add_argument(\n", + " '--output_proto',\n", + " default='',\n", + " help='Output file to write the cp_model'\n", + " 'proto to.')\n", + "PARSER.add_argument('--params', default='', help='Sat solver parameters.')\n", + "PARSER.add_argument(\n", + " '--preprocess_times',\n", + " default=True,\n", + " type=bool,\n", + " help='Preprocess setup times and durations')\n", + "\n", + "\n", + "#----------------------------------------------------------------------------\n", + "# Intermediate solution printer\n", + "class SolutionPrinter(cp_model.CpSolverSolutionCallback):\n", + " \"\"\"Print intermediate solutions.\"\"\"\n", + "\n", + " def __init__(self):\n", + " cp_model.CpSolverSolutionCallback.__init__(self)\n", + " self.__solution_count = 0\n", + "\n", + " def on_solution_callback(self):\n", + " \"\"\"Called after each new solution found.\"\"\"\n", + " print('Solution %i, time = %f s, objective = %i' %\n", + " (self.__solution_count, self.WallTime(), self.ObjectiveValue()))\n", + " self.__solution_count += 1\n", + "\n", + "\n", + "\"\"\"Solves a complex single machine jobshop scheduling problem.\"\"\"\n", + "\n", + "parameters = args.params\n", + "output_proto = args.output_proto\n", + "\n", + "#----------------------------------------------------------------------------\n", + "# Data.\n", + "\n", + "job_durations = [\n", + " 2546, 8589, 5953, 3710, 3630, 3016, 4148, 8706, 1604, 5502, 9983, 6209,\n", + " 9920, 7860, 2176\n", + "]\n", + "\n", + "setup_times = [\n", + " [\n", + " 3559, 1638, 2000, 3676, 2741, 2439, 2406, 1526, 1600, 3356, 4324,\n", + " 1923, 3663, 4103, 2215\n", + " ],\n", + " [\n", + " 1442, 3010, 1641, 4490, 2060, 2143, 3376, 3891, 3513, 2855, 2653,\n", + " 1471, 2257, 1186, 2354\n", + " ],\n", + " [\n", + " 1728, 3583, 3243, 4080, 2191, 3644, 4023, 3510, 2135, 1346, 1410,\n", + " 3565, 3181, 1126, 4169\n", + " ],\n", + " [\n", + " 1291, 1703, 3103, 4001, 1712, 1137, 3341, 3485, 2557, 2435, 1972,\n", + " 1986, 1522, 4734, 2520\n", + " ],\n", + " [\n", + " 4134, 2200, 1502, 3995, 1277, 1808, 1020, 2078, 2999, 1605, 1697,\n", + " 2323, 2268, 2288, 4856\n", + " ],\n", + " [\n", + " 4974, 2480, 2492, 4088, 2587, 4652, 1478, 3942, 1222, 3305, 1206,\n", + " 1024, 2605, 3080, 3516\n", + " ],\n", + " [\n", + " 1903, 2584, 2104, 1609, 4745, 2691, 1539, 2544, 2499, 2074, 4793,\n", + " 1756, 2190, 1298, 2605\n", + " ],\n", + " [\n", + " 1407, 2536, 2296, 1769, 1449, 3386, 3046, 1180, 4132, 4783, 3386,\n", + " 3429, 2450, 3376, 3719\n", + " ],\n", + " [\n", + " 3026, 1637, 3628, 3096, 1498, 4947, 1912, 3703, 4107, 4730, 1805,\n", + " 2189, 1789, 1985, 3586\n", + " ],\n", + " [\n", + " 3940, 1342, 1601, 2737, 1748, 3771, 4052, 1619, 2558, 3782, 4383,\n", + " 3451, 4904, 1108, 1750\n", + " ],\n", + " [\n", + " 1348, 3162, 1507, 3936, 1453, 2953, 4182, 2968, 3134, 1042, 3175,\n", + " 2805, 4901, 1735, 1654\n", + " ],\n", + " [\n", + " 1099, 1711, 1245, 1067, 4343, 3407, 1108, 1784, 4803, 2342, 3377,\n", + " 2037, 3563, 1621, 2840\n", + " ],\n", + " [\n", + " 2573, 4222, 3164, 2563, 3231, 4731, 2395, 1033, 4795, 3288, 2335,\n", + " 4935, 4066, 1440, 4979\n", + " ],\n", + " [\n", + " 3321, 1666, 3573, 2377, 4649, 4600, 1065, 2475, 3658, 3374, 1138,\n", + " 4367, 4728, 3032, 2198\n", + " ],\n", + " [\n", + " 2986, 1180, 4095, 3132, 3987, 3880, 3526, 1460, 4885, 3827, 4945,\n", + " 4419, 3486, 3805, 3804\n", + " ],\n", + " [\n", + " 4163, 3441, 1217, 2941, 1210, 3794, 1779, 1904, 4255, 4967, 4003,\n", + " 3873, 1002, 2055, 4295\n", + " ],\n", + "]\n", + "\n", + "due_dates = [\n", + " -1, -1, 28569, -1, 98104, 27644, 55274, 57364, -1, -1, 60875, 96637,\n", + " 77888, -1, -1\n", + "]\n", + "release_dates = [\n", + " 0, 0, 0, 0, 19380, 0, 0, 48657, 0, 27932, 0, 0, 24876, 0, 0\n", + "]\n", + "\n", + "precedences = [(0, 2), (1, 2)]\n", + "\n", + "#----------------------------------------------------------------------------\n", + "# Helper data.\n", + "num_jobs = len(job_durations)\n", + "all_jobs = range(num_jobs)\n", + "\n", + "#----------------------------------------------------------------------------\n", + "# Preprocess.\n", + "if args.preprocess_times:\n", + " for job_id in all_jobs:\n", + " min_incoming_setup = min(\n", + " setup_times[j][job_id] for j in range(num_jobs + 1))\n", + " if release_dates[job_id] != 0:\n", + " min_incoming_setup = min(min_incoming_setup,\n", + " release_dates[job_id])\n", + " if min_incoming_setup == 0:\n", + " continue\n", + "\n", + " print('job %i has a min incoming setup of %i' %\n", + " (job_id, min_incoming_setup))\n", + " # We can transfer some setup times to the duration of the job.\n", + " job_durations[job_id] += min_incoming_setup\n", + " # Decrease corresponding incoming setup times.\n", + " for j in range(num_jobs + 1):\n", + " setup_times[j][job_id] -= min_incoming_setup\n", + " # Adjust release dates if needed.\n", + " if release_dates[job_id] != 0:\n", + " release_dates[job_id] -= min_incoming_setup\n", + "\n", + "#----------------------------------------------------------------------------\n", + "# Model.\n", + "model = cp_model.CpModel()\n", + "\n", + "#----------------------------------------------------------------------------\n", + "# Compute a maximum makespan greedily.\n", + "horizon = sum(job_durations) + sum(\n", + " max(setup_times[i][j] for i in range(num_jobs + 1))\n", + " for j in range(num_jobs))\n", + "print('Greedy horizon =', horizon)\n", + "\n", + "#----------------------------------------------------------------------------\n", + "# Global storage of variables.\n", + "intervals = []\n", + "starts = []\n", + "ends = []\n", + "\n", + "#----------------------------------------------------------------------------\n", + "# Scan the jobs and create the relevant variables and intervals.\n", + "for job_id in all_jobs:\n", + " duration = job_durations[job_id]\n", + " release_date = release_dates[job_id]\n", + " due_date = due_dates[job_id] if due_dates[job_id] != -1 else horizon\n", + " print('job %2i: start = %5i, duration = %4i, end = %6i' %\n", + " (job_id, release_date, duration, due_date))\n", + " name_suffix = '_%i' % job_id\n", + " start = model.NewIntVar(release_date, due_date, 's' + name_suffix)\n", + " end = model.NewIntVar(release_date, due_date, 'e' + name_suffix)\n", + " interval = model.NewIntervalVar(start, duration, end, 'i' + name_suffix)\n", + " starts.append(start)\n", + " ends.append(end)\n", + " intervals.append(interval)\n", + "\n", + "# No overlap constraint.\n", + "model.AddNoOverlap(intervals)\n", + "\n", + "#----------------------------------------------------------------------------\n", + "# Transition times using a circuit constraint.\n", + "arcs = []\n", + "for i in all_jobs:\n", + " # Initial arc from the dummy node (0) to a task.\n", + " start_lit = model.NewBoolVar('')\n", + " arcs.append([0, i + 1, start_lit])\n", + " # If this task is the first, set to minimum starting time.\n", + " min_start_time = max(release_dates[i], setup_times[0][i])\n", + " model.Add(starts[i] == min_start_time).OnlyEnforceIf(start_lit)\n", + " # Final arc from an arc to the dummy node.\n", + " arcs.append([i + 1, 0, model.NewBoolVar('')])\n", + "\n", + " for j in all_jobs:\n", + " if i == j:\n", + " continue\n", + "\n", + " lit = model.NewBoolVar('%i follows %i' % (j, i))\n", + " arcs.append([i + 1, j + 1, lit])\n", + "\n", + " # We add the reified precedence to link the literal with the times of the\n", + " # two tasks.\n", + " # If release_dates[j] == 0, we can strenghten this precedence into an\n", + " # equality as we are minimizing the makespan.\n", + " if release_dates[j] == 0:\n", + " model.Add(starts[j] == ends[i] +\n", + " setup_times[i + 1][j]).OnlyEnforceIf(lit)\n", + " else:\n", + " model.Add(starts[j] >=\n", + " ends[i] + setup_times[i + 1][j]).OnlyEnforceIf(lit)\n", + "\n", + "model.AddCircuit(arcs)\n", + "\n", + "#----------------------------------------------------------------------------\n", + "# Precedences.\n", + "for before, after in precedences:\n", + " print('job %i is after job %i' % (after, before))\n", + " model.Add(ends[before] <= starts[after])\n", + "\n", + "#----------------------------------------------------------------------------\n", + "# Objective.\n", + "makespan = model.NewIntVar(0, horizon, 'makespan')\n", + "model.AddMaxEquality(makespan, ends)\n", + "model.Minimize(makespan)\n", + "\n", + "#----------------------------------------------------------------------------\n", + "# Write problem to file.\n", + "if output_proto:\n", + " print('Writing proto to %s' % output_proto)\n", + " with open(output_proto, 'w') as text_file:\n", + " text_file.write(str(model))\n", + "\n", + "#----------------------------------------------------------------------------\n", + "# Solve.\n", + "solver = cp_model.CpSolver()\n", + "solver.parameters.max_time_in_seconds = 60 * 60 * 2\n", + "if parameters:\n", + " text_format.Merge(parameters, solver.parameters)\n", + "solution_printer = SolutionPrinter()\n", + "solver.SolveWithSolutionCallback(model, solution_printer)\n", + "print(solver.ResponseStats())\n", + "for job_id in all_jobs:\n", + " print('job %i starts at %i end ends at %i' %\n", + " (job_id, solver.Value(starts[job_id]),\n", + " solver.Value(ends[job_id])))\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/examples/steel_mill_slab_sat.ipynb b/examples/notebook/examples/steel_mill_slab_sat.ipynb new file mode 100644 index 0000000000..a02bf99e03 --- /dev/null +++ b/examples/notebook/examples/steel_mill_slab_sat.ipynb @@ -0,0 +1,780 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010-2018 Google LLC\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"Solves the Stell Mill Slab problem with 4 different techniques.\"\"\"\n", + "\n", + "from __future__ import print_function\n", + "\n", + "import argparse\n", + "import collections\n", + "import time\n", + "\n", + "from ortools.sat.python import cp_model\n", + "from ortools.linear_solver import pywraplp\n", + "\n", + "PARSER = argparse.ArgumentParser()\n", + "\n", + "PARSER.add_argument(\n", + " '--problem', default=2, type=int, help='Problem id to solve.')\n", + "PARSER.add_argument(\n", + " '--break_symmetries',\n", + " default=True,\n", + " type=bool,\n", + " help='Break symmetries between equivalent orders.')\n", + "PARSER.add_argument(\n", + " '--solver',\n", + " default='sat_table',\n", + " help='Method used to solve: sat, sat_table, sat_column, mip_column.')\n", + "PARSER.add_argument(\n", + " '--output_proto',\n", + " default='',\n", + " help='Output file to write the cp_model proto to.')\n", + "\n", + "\n", + "def build_problem(problem_id):\n", + " \"\"\"Build problem data.\"\"\"\n", + " if problem_id == 0:\n", + " capacities = [\n", + " 0, 12, 14, 17, 18, 19, 20, 23, 24, 25, 26, 27, 28, 29, 30, 32, 35,\n", + " 39, 42, 43, 44\n", + " ]\n", + " num_colors = 88\n", + " num_slabs = 111\n", + " orders = [\n", + " (4, 1), # (size, color)\n", + " (22, 2),\n", + " (9, 3),\n", + " (5, 4),\n", + " (8, 5),\n", + " (3, 6),\n", + " (3, 4),\n", + " (4, 7),\n", + " (7, 4),\n", + " (7, 8),\n", + " (3, 6),\n", + " (2, 6),\n", + " (2, 4),\n", + " (8, 9),\n", + " (5, 10),\n", + " (7, 11),\n", + " (4, 7),\n", + " (7, 11),\n", + " (5, 10),\n", + " (7, 11),\n", + " (8, 9),\n", + " (3, 1),\n", + " (25, 12),\n", + " (14, 13),\n", + " (3, 6),\n", + " (22, 14),\n", + " (19, 15),\n", + " (19, 15),\n", + " (22, 16),\n", + " (22, 17),\n", + " (22, 18),\n", + " (20, 19),\n", + " (22, 20),\n", + " (5, 21),\n", + " (4, 22),\n", + " (10, 23),\n", + " (26, 24),\n", + " (17, 25),\n", + " (20, 26),\n", + " (16, 27),\n", + " (10, 28),\n", + " (19, 29),\n", + " (10, 30),\n", + " (10, 31),\n", + " (23, 32),\n", + " (22, 33),\n", + " (26, 34),\n", + " (27, 35),\n", + " (22, 36),\n", + " (27, 37),\n", + " (22, 38),\n", + " (22, 39),\n", + " (13, 40),\n", + " (14, 41),\n", + " (16, 27),\n", + " (26, 34),\n", + " (26, 42),\n", + " (27, 35),\n", + " (22, 36),\n", + " (20, 43),\n", + " (26, 24),\n", + " (22, 44),\n", + " (13, 45),\n", + " (19, 46),\n", + " (20, 47),\n", + " (16, 48),\n", + " (15, 49),\n", + " (17, 50),\n", + " (10, 28),\n", + " (20, 51),\n", + " (5, 52),\n", + " (26, 24),\n", + " (19, 53),\n", + " (15, 54),\n", + " (10, 55),\n", + " (10, 56),\n", + " (13, 57),\n", + " (13, 58),\n", + " (13, 59),\n", + " (12, 60),\n", + " (12, 61),\n", + " (18, 62),\n", + " (10, 63),\n", + " (18, 64),\n", + " (16, 65),\n", + " (20, 66),\n", + " (12, 67),\n", + " (6, 68),\n", + " (6, 68),\n", + " (15, 69),\n", + " (15, 70),\n", + " (15, 70),\n", + " (21, 71),\n", + " (30, 72),\n", + " (30, 73),\n", + " (30, 74),\n", + " (30, 75),\n", + " (23, 76),\n", + " (15, 77),\n", + " (15, 78),\n", + " (27, 79),\n", + " (27, 80),\n", + " (27, 81),\n", + " (27, 82),\n", + " (27, 83),\n", + " (27, 84),\n", + " (27, 79),\n", + " (27, 85),\n", + " (27, 86),\n", + " (10, 87),\n", + " (3, 88)\n", + " ]\n", + " elif problem_id == 1:\n", + " capacities = [0, 17, 44]\n", + " num_colors = 23\n", + " num_slabs = 30\n", + " orders = [\n", + " (4, 1), # (size, color)\n", + " (22, 2),\n", + " (9, 3),\n", + " (5, 4),\n", + " (8, 5),\n", + " (3, 6),\n", + " (3, 4),\n", + " (4, 7),\n", + " (7, 4),\n", + " (7, 8),\n", + " (3, 6),\n", + " (2, 6),\n", + " (2, 4),\n", + " (8, 9),\n", + " (5, 10),\n", + " (7, 11),\n", + " (4, 7),\n", + " (7, 11),\n", + " (5, 10),\n", + " (7, 11),\n", + " (8, 9),\n", + " (3, 1),\n", + " (25, 12),\n", + " (14, 13),\n", + " (3, 6),\n", + " (22, 14),\n", + " (19, 15),\n", + " (19, 15),\n", + " (22, 16),\n", + " (22, 17),\n", + " (22, 18),\n", + " (20, 19),\n", + " (22, 20),\n", + " (5, 21),\n", + " (4, 22),\n", + " (10, 23)\n", + " ]\n", + " elif problem_id == 2:\n", + " capacities = [0, 17, 44]\n", + " num_colors = 15\n", + " num_slabs = 20\n", + " orders = [\n", + " (4, 1), # (size, color)\n", + " (22, 2),\n", + " (9, 3),\n", + " (5, 4),\n", + " (8, 5),\n", + " (3, 6),\n", + " (3, 4),\n", + " (4, 7),\n", + " (7, 4),\n", + " (7, 8),\n", + " (3, 6),\n", + " (2, 6),\n", + " (2, 4),\n", + " (8, 9),\n", + " (5, 10),\n", + " (7, 11),\n", + " (4, 7),\n", + " (7, 11),\n", + " (5, 10),\n", + " (7, 11),\n", + " (8, 9),\n", + " (3, 1),\n", + " (25, 12),\n", + " (14, 13),\n", + " (3, 6),\n", + " (22, 14),\n", + " (19, 15),\n", + " (19, 15)\n", + " ]\n", + "\n", + " elif problem_id == 3:\n", + " capacities = [0, 17, 44]\n", + " num_colors = 8\n", + " num_slabs = 10\n", + " orders = [\n", + " (4, 1), # (size, color)\n", + " (22, 2),\n", + " (9, 3),\n", + " (5, 4),\n", + " (8, 5),\n", + " (3, 6),\n", + " (3, 4),\n", + " (4, 7),\n", + " (7, 4),\n", + " (7, 8),\n", + " (3, 6)\n", + " ]\n", + "\n", + " return (num_slabs, capacities, num_colors, orders)\n", + "\n", + "\n", + "class SteelMillSlabSolutionPrinter(cp_model.CpSolverSolutionCallback):\n", + " \"\"\"Print intermediate solutions.\"\"\"\n", + "\n", + " def __init__(self, orders, assign, load, loss):\n", + " cp_model.CpSolverSolutionCallback.__init__(self)\n", + " self.__orders = orders\n", + " self.__assign = assign\n", + " self.__load = load\n", + " self.__loss = loss\n", + " self.__solution_count = 0\n", + " self.__all_orders = range(len(orders))\n", + " self.__all_slabs = range(len(assign[0]))\n", + " self.__start_time = time.time()\n", + "\n", + " def on_solution_callback(self):\n", + " \"\"\"Called on each new solution.\"\"\"\n", + " current_time = time.time()\n", + " objective = sum(self.Value(l) for l in self.__loss)\n", + " print('Solution %i, time = %f s, objective = %i' %\n", + " (self.__solution_count, current_time - self.__start_time,\n", + " objective))\n", + " self.__solution_count += 1\n", + " orders_in_slab = [[\n", + " o for o in self.__all_orders if self.Value(self.__assign[o][s])\n", + " ] for s in self.__all_slabs]\n", + " for s in self.__all_slabs:\n", + " if orders_in_slab[s]:\n", + " line = ' - slab %i, load = %i, loss = %i, orders = [' % (\n", + " s, self.Value(self.__load[s]), self.Value(self.__loss[s]))\n", + " for o in orders_in_slab[s]:\n", + " line += '#%i(w%i, c%i) ' % (o, self.__orders[o][0],\n", + " self.__orders[o][1])\n", + " line += ']'\n", + " print(line)\n", + "\n", + "\n", + "def steel_mill_slab(problem, break_symmetries, output_proto):\n", + " \"\"\"Solves the Steel Mill Slab Problem.\"\"\"\n", + " ### Load problem.\n", + " (num_slabs, capacities, num_colors, orders) = build_problem(problem)\n", + "\n", + " num_orders = len(orders)\n", + " num_capacities = len(capacities)\n", + " all_slabs = range(num_slabs)\n", + " all_colors = range(num_colors)\n", + " all_orders = range(len(orders))\n", + " print('Solving steel mill with %i orders, %i slabs, and %i capacities' %\n", + " (num_orders, num_slabs, num_capacities - 1))\n", + "\n", + " # Compute auxilliary data.\n", + " widths = [x[0] for x in orders]\n", + " colors = [x[1] for x in orders]\n", + " max_capacity = max(capacities)\n", + " loss_array = [\n", + " min(x for x in capacities if x >= c) - c\n", + " for c in range(max_capacity + 1)\n", + " ]\n", + " max_loss = max(loss_array)\n", + " orders_per_color = [[o for o in all_orders if colors[o] == c + 1]\n", + " for c in all_colors]\n", + " unique_color_orders = [\n", + " o for o in all_orders if len(orders_per_color[colors[o] - 1]) == 1\n", + " ]\n", + "\n", + " ### Model problem.\n", + "\n", + " # Create the model and the decision variables.\n", + " model = cp_model.CpModel()\n", + " assign = [[\n", + " model.NewBoolVar('assign_%i_to_slab_%i' % (o, s)) for s in all_slabs\n", + " ] for o in all_orders]\n", + " loads = [\n", + " model.NewIntVar(0, max_capacity, 'load_of_slab_%i' % s)\n", + " for s in all_slabs\n", + " ]\n", + " color_is_in_slab = [[\n", + " model.NewBoolVar('color_%i_in_slab_%i' % (c + 1, s))\n", + " for c in all_colors\n", + " ] for s in all_slabs]\n", + "\n", + " # Compute load of all slabs.\n", + " for s in all_slabs:\n", + " model.Add(\n", + " sum(assign[o][s] * widths[o] for o in all_orders) == loads[s])\n", + "\n", + " # Orders are assigned to one slab.\n", + " for o in all_orders:\n", + " model.Add(sum(assign[o]) == 1)\n", + "\n", + " # Redundant constraint (sum of loads == sum of widths).\n", + " model.Add(sum(loads) == sum(widths))\n", + "\n", + " # Link present_colors and assign.\n", + " for c in all_colors:\n", + " for s in all_slabs:\n", + " for o in orders_per_color[c]:\n", + " model.AddImplication(assign[o][s], color_is_in_slab[s][c])\n", + " model.AddImplication(color_is_in_slab[s][c].Not(),\n", + " assign[o][s].Not())\n", + "\n", + " # At most two colors per slab.\n", + " for s in all_slabs:\n", + " model.Add(sum(color_is_in_slab[s]) <= 2)\n", + "\n", + " # Project previous constraint on unique_color_orders\n", + " for s in all_slabs:\n", + " model.Add(sum(assign[o][s] for o in unique_color_orders) <= 2)\n", + "\n", + " # Symmetry breaking.\n", + " for s in range(num_slabs - 1):\n", + " model.Add(loads[s] >= loads[s + 1])\n", + "\n", + " # Collect equivalent orders.\n", + " width_to_unique_color_order = {}\n", + " ordered_equivalent_orders = []\n", + " for c in all_colors:\n", + " colored_orders = orders_per_color[c]\n", + " if not colored_orders:\n", + " continue\n", + " if len(colored_orders) == 1:\n", + " o = colored_orders[0]\n", + " w = widths[o]\n", + " if w not in width_to_unique_color_order:\n", + " width_to_unique_color_order[w] = [o]\n", + " else:\n", + " width_to_unique_color_order[w].append(o)\n", + " else:\n", + " local_width_to_order = {}\n", + " for o in colored_orders:\n", + " w = widths[o]\n", + " if w not in local_width_to_order:\n", + " local_width_to_order[w] = []\n", + " local_width_to_order[w].append(o)\n", + " for w, os in local_width_to_order.items():\n", + " if len(os) > 1:\n", + " for p in range(len(os) - 1):\n", + " ordered_equivalent_orders.append((os[p], os[p + 1]))\n", + " for w, os in width_to_unique_color_order.items():\n", + " if len(os) > 1:\n", + " for p in range(len(os) - 1):\n", + " ordered_equivalent_orders.append((os[p], os[p + 1]))\n", + "\n", + " # Create position variables if there are symmetries to be broken.\n", + " if break_symmetries and ordered_equivalent_orders:\n", + " print(' - creating %i symmetry breaking constraints' %\n", + " len(ordered_equivalent_orders))\n", + " positions = {}\n", + " for p in ordered_equivalent_orders:\n", + " if p[0] not in positions:\n", + " positions[p[0]] = model.NewIntVar(0, num_slabs - 1,\n", + " 'position_of_slab_%i' % p[0])\n", + " model.AddMapDomain(positions[p[0]], assign[p[0]])\n", + " if p[1] not in positions:\n", + " positions[p[1]] = model.NewIntVar(0, num_slabs - 1,\n", + " 'position_of_slab_%i' % p[1])\n", + " model.AddMapDomain(positions[p[1]], assign[p[1]])\n", + " # Finally add the symmetry breaking constraint.\n", + " model.Add(positions[p[0]] <= positions[p[1]])\n", + "\n", + " # Objective.\n", + " obj = model.NewIntVar(0, num_slabs * max_loss, 'obj')\n", + " losses = [model.NewIntVar(0, max_loss, 'loss_%i' % s) for s in all_slabs]\n", + " for s in all_slabs:\n", + " model.AddElement(loads[s], loss_array, losses[s])\n", + " model.Add(obj == sum(losses))\n", + " model.Minimize(obj)\n", + "\n", + " ### Solve model.\n", + " solver = cp_model.CpSolver()\n", + " solver.parameters.num_search_workers = 8\n", + " objective_printer = cp_model.ObjectiveSolutionPrinter()\n", + " status = solver.SolveWithSolutionCallback(model, objective_printer)\n", + "\n", + " ### Output the solution.\n", + " if status in (cp_model.OPTIMAL, cp_model.FEASIBLE):\n", + " print('Loss = %i, time = %f s, %i conflicts' % (\n", + " solver.ObjectiveValue(), solver.WallTime(), solver.NumConflicts()))\n", + " else:\n", + " print('No solution')\n", + "\n", + "\n", + "def collect_valid_slabs_dp(capacities, colors, widths, loss_array):\n", + " \"\"\"Collect valid columns (assign, loss) for one slab.\"\"\"\n", + " start_time = time.time()\n", + "\n", + " max_capacity = max(capacities)\n", + "\n", + " valid_assignment = collections.namedtuple('valid_assignment',\n", + " 'orders load colors')\n", + " all_valid_assignments = [valid_assignment(orders=[], load=0, colors=[])]\n", + "\n", + " for order_id in range(len(colors)):\n", + " new_width = widths[order_id]\n", + " new_color = colors[order_id]\n", + " new_assignments = []\n", + " for assignment in all_valid_assignments:\n", + " if assignment.load + new_width > max_capacity:\n", + " continue\n", + " new_colors = list(assignment.colors)\n", + " if not new_color in new_colors:\n", + " new_colors.append(new_color)\n", + " if len(new_colors) > 2:\n", + " continue\n", + " new_assignment = valid_assignment(\n", + " orders=assignment.orders + [order_id],\n", + " load=assignment.load + new_width,\n", + " colors=new_colors)\n", + " new_assignments.append(new_assignment)\n", + " all_valid_assignments.extend(new_assignments)\n", + "\n", + " print('%i assignments created in %.2f s' % (len(all_valid_assignments),\n", + " time.time() - start_time))\n", + " tuples = []\n", + " for assignment in all_valid_assignments:\n", + " solution = [0 for _ in range(len(colors))]\n", + " for i in assignment.orders:\n", + " solution[i] = 1\n", + " solution.append(loss_array[assignment.load])\n", + " solution.append(assignment.load)\n", + " tuples.append(solution)\n", + "\n", + " return tuples\n", + "\n", + "\n", + "def steel_mill_slab_with_valid_slabs(problem, break_symmetries, output_proto):\n", + " \"\"\"Solves the Steel Mill Slab Problem.\"\"\"\n", + " ### Load problem.\n", + " (num_slabs, capacities, num_colors, orders) = build_problem(problem)\n", + "\n", + " num_orders = len(orders)\n", + " num_capacities = len(capacities)\n", + " all_slabs = range(num_slabs)\n", + " all_colors = range(num_colors)\n", + " all_orders = range(len(orders))\n", + " print('Solving steel mill with %i orders, %i slabs, and %i capacities' %\n", + " (num_orders, num_slabs, num_capacities - 1))\n", + "\n", + " # Compute auxilliary data.\n", + " widths = [x[0] for x in orders]\n", + " colors = [x[1] for x in orders]\n", + " max_capacity = max(capacities)\n", + " loss_array = [\n", + " min(x for x in capacities if x >= c) - c\n", + " for c in range(max_capacity + 1)\n", + " ]\n", + " max_loss = max(loss_array)\n", + "\n", + " ### Model problem.\n", + "\n", + " # Create the model and the decision variables.\n", + " model = cp_model.CpModel()\n", + " assign = [[\n", + " model.NewBoolVar('assign_%i_to_slab_%i' % (o, s)) for s in all_slabs\n", + " ] for o in all_orders]\n", + " loads = [\n", + " model.NewIntVar(0, max_capacity, 'load_%i' % s) for s in all_slabs\n", + " ]\n", + " losses = [model.NewIntVar(0, max_loss, 'loss_%i' % s) for s in all_slabs]\n", + "\n", + " unsorted_valid_slabs = collect_valid_slabs_dp(capacities, colors, widths,\n", + " loss_array)\n", + " # Sort slab by descending load/loss. Remove duplicates.\n", + " valid_slabs = sorted(\n", + " unsorted_valid_slabs, key=lambda c: 1000 * c[-1] + c[-2])\n", + "\n", + " for s in all_slabs:\n", + " model.AddAllowedAssignments(\n", + " [assign[o][s] for o in all_orders] + [losses[s], loads[s]],\n", + " valid_slabs)\n", + "\n", + " # Orders are assigned to one slab.\n", + " for o in all_orders:\n", + " model.Add(sum(assign[o]) == 1)\n", + "\n", + " # Redundant constraint (sum of loads == sum of widths).\n", + " model.Add(sum(loads) == sum(widths))\n", + "\n", + " # Symmetry breaking.\n", + " for s in range(num_slabs - 1):\n", + " model.Add(loads[s] >= loads[s + 1])\n", + "\n", + " # Collect equivalent orders.\n", + " if break_symmetries:\n", + " print('Breaking symmetries')\n", + " width_to_unique_color_order = {}\n", + " ordered_equivalent_orders = []\n", + " orders_per_color = [[o for o in all_orders if colors[o] == c + 1]\n", + " for c in all_colors]\n", + " for c in all_colors:\n", + " colored_orders = orders_per_color[c]\n", + " if not colored_orders:\n", + " continue\n", + " if len(colored_orders) == 1:\n", + " o = colored_orders[0]\n", + " w = widths[o]\n", + " if w not in width_to_unique_color_order:\n", + " width_to_unique_color_order[w] = [o]\n", + " else:\n", + " width_to_unique_color_order[w].append(o)\n", + " else:\n", + " local_width_to_order = {}\n", + " for o in colored_orders:\n", + " w = widths[o]\n", + " if w not in local_width_to_order:\n", + " local_width_to_order[w] = []\n", + " local_width_to_order[w].append(o)\n", + " for w, os in local_width_to_order.items():\n", + " if len(os) > 1:\n", + " for p in range(len(os) - 1):\n", + " ordered_equivalent_orders.append((os[p],\n", + " os[p + 1]))\n", + " for w, os in width_to_unique_color_order.items():\n", + " if len(os) > 1:\n", + " for p in range(len(os) - 1):\n", + " ordered_equivalent_orders.append((os[p], os[p + 1]))\n", + "\n", + " # Create position variables if there are symmetries to be broken.\n", + " if ordered_equivalent_orders:\n", + " print(' - creating %i symmetry breaking constraints' %\n", + " len(ordered_equivalent_orders))\n", + " positions = {}\n", + " for p in ordered_equivalent_orders:\n", + " if p[0] not in positions:\n", + " positions[p[0]] = model.NewIntVar(\n", + " 0, num_slabs - 1, 'position_of_slab_%i' % p[0])\n", + " model.AddMapDomain(positions[p[0]], assign[p[0]])\n", + " if p[1] not in positions:\n", + " positions[p[1]] = model.NewIntVar(\n", + " 0, num_slabs - 1, 'position_of_slab_%i' % p[1])\n", + " model.AddMapDomain(positions[p[1]], assign[p[1]])\n", + " # Finally add the symmetry breaking constraint.\n", + " model.Add(positions[p[0]] <= positions[p[1]])\n", + "\n", + " # Objective.\n", + " model.Minimize(sum(losses))\n", + "\n", + " print('Model created')\n", + "\n", + " # Output model proto to file.\n", + " if output_proto:\n", + " output_file = open(output_proto, 'w')\n", + " output_file.write(str(model.Proto()))\n", + " output_file.close()\n", + "\n", + " ### Solve model.\n", + " solver = cp_model.CpSolver()\n", + " solver.num_search_workers = 8\n", + " solution_printer = SteelMillSlabSolutionPrinter(orders, assign, loads,\n", + " losses)\n", + " status = solver.SolveWithSolutionCallback(model, solution_printer)\n", + "\n", + " ### Output the solution.\n", + " if status == cp_model.OPTIMAL:\n", + " print('Loss = %i, time = %.2f s, %i conflicts' % (\n", + " solver.ObjectiveValue(), solver.WallTime(), solver.NumConflicts()))\n", + " else:\n", + " print('No solution')\n", + "\n", + "\n", + "def steel_mill_slab_with_column_generation(problem, output_proto):\n", + " \"\"\"Solves the Steel Mill Slab Problem.\"\"\"\n", + " ### Load problem.\n", + " (num_slabs, capacities, _, orders) = build_problem(problem)\n", + "\n", + " num_orders = len(orders)\n", + " num_capacities = len(capacities)\n", + " all_orders = range(len(orders))\n", + " print('Solving steel mill with %i orders, %i slabs, and %i capacities' %\n", + " (num_orders, num_slabs, num_capacities - 1))\n", + "\n", + " # Compute auxilliary data.\n", + " widths = [x[0] for x in orders]\n", + " colors = [x[1] for x in orders]\n", + " max_capacity = max(capacities)\n", + " loss_array = [\n", + " min(x for x in capacities if x >= c) - c\n", + " for c in range(max_capacity + 1)\n", + " ]\n", + "\n", + " ### Model problem.\n", + "\n", + " # Generate all valid slabs (columns)\n", + " unsorted_valid_slabs = collect_valid_slabs_dp(capacities, colors, widths,\n", + " loss_array)\n", + "\n", + " # Sort slab by descending load/loss. Remove duplicates.\n", + " valid_slabs = sorted(\n", + " unsorted_valid_slabs, key=lambda c: 1000 * c[-1] + c[-2])\n", + " all_valid_slabs = range(len(valid_slabs))\n", + "\n", + " # create model and decision variables.\n", + " model = cp_model.CpModel()\n", + " selected = [model.NewBoolVar('selected_%i' % i) for i in all_valid_slabs]\n", + "\n", + " for order_id in all_orders:\n", + " model.Add(\n", + " sum(selected[i] for i, slab in enumerate(valid_slabs)\n", + " if slab[order_id]) == 1)\n", + "\n", + " # Redundant constraint (sum of loads == sum of widths).\n", + " model.Add(\n", + " sum(selected[i] * valid_slabs[i][-1]\n", + " for i in all_valid_slabs) == sum(widths))\n", + "\n", + " # Objective.\n", + " model.Minimize(\n", + " sum(selected[i] * valid_slabs[i][-2] for i in all_valid_slabs))\n", + "\n", + " print('Model created')\n", + "\n", + " # Output model proto to file.\n", + " if output_proto:\n", + " output_file = open(output_proto, 'w')\n", + " output_file.write(str(model.Proto()))\n", + " output_file.close()\n", + "\n", + " ### Solve model.\n", + " solver = cp_model.CpSolver()\n", + " solver.parameters.num_search_workers = 8\n", + " solution_printer = cp_model.ObjectiveSolutionPrinter()\n", + " status = solver.SolveWithSolutionCallback(model, solution_printer)\n", + "\n", + " ### Output the solution.\n", + " if status in (cp_model.OPTIMAL, cp_model.FEASIBLE):\n", + " print('Loss = %i, time = %.2f s, %i conflicts' % (\n", + " solver.ObjectiveValue(), solver.WallTime(), solver.NumConflicts()))\n", + " else:\n", + " print('No solution')\n", + "\n", + "\n", + "def steel_mill_slab_with_mip_column_generation(problem):\n", + " \"\"\"Solves the Steel Mill Slab Problem.\"\"\"\n", + " ### Load problem.\n", + " (num_slabs, capacities, _, orders) = build_problem(problem)\n", + "\n", + " num_orders = len(orders)\n", + " num_capacities = len(capacities)\n", + " all_orders = range(len(orders))\n", + " print('Solving steel mill with %i orders, %i slabs, and %i capacities' %\n", + " (num_orders, num_slabs, num_capacities - 1))\n", + "\n", + " # Compute auxilliary data.\n", + " widths = [x[0] for x in orders]\n", + " colors = [x[1] for x in orders]\n", + " max_capacity = max(capacities)\n", + " loss_array = [\n", + " min(x for x in capacities if x >= c) - c\n", + " for c in range(max_capacity + 1)\n", + " ]\n", + "\n", + " ### Model problem.\n", + "\n", + " # Generate all valid slabs (columns)\n", + " unsorted_valid_slabs = collect_valid_slabs_dp(capacities, colors, widths,\n", + " loss_array)\n", + " # Sort slab by descending load/loss. Remove duplicates.\n", + " valid_slabs = sorted(\n", + " unsorted_valid_slabs, key=lambda c: 1000 * c[-1] + c[-2])\n", + " all_valid_slabs = range(len(valid_slabs))\n", + "\n", + " # create model and decision variables.\n", + " start_time = time.time()\n", + " solver = pywraplp.Solver('Steel',\n", + " pywraplp.Solver.CBC_MIXED_INTEGER_PROGRAMMING)\n", + " selected = [\n", + " solver.IntVar(0.0, 1.0, 'selected_%i' % i) for i in all_valid_slabs\n", + " ]\n", + "\n", + " for order in all_orders:\n", + " solver.Add(\n", + " sum(selected[i] for i in all_valid_slabs\n", + " if valid_slabs[i][order]) == 1)\n", + "\n", + " # Redundant constraint (sum of loads == sum of widths).\n", + " solver.Add(\n", + " sum(selected[i] * valid_slabs[i][-1]\n", + " for i in all_valid_slabs) == sum(widths))\n", + "\n", + " # Objective.\n", + " solver.Minimize(\n", + " sum(selected[i] * valid_slabs[i][-2] for i in all_valid_slabs))\n", + "\n", + " status = solver.Solve()\n", + "\n", + " ### Output the solution.\n", + " if status == pywraplp.Solver.OPTIMAL:\n", + " print('Objective value = %f found in %.2f s' %\n", + " (solver.Objective().Value(), time.time() - start_time))\n", + " else:\n", + " print('No solution')\n", + "\n", + "\n", + "'''Main function'''\n", + "if args.solver == 'sat':\n", + " steel_mill_slab(args.problem, args.break_symmetries, args.output_proto)\n", + "elif args.solver == 'sat_table':\n", + " steel_mill_slab_with_valid_slabs(args.problem, args.break_symmetries,\n", + " args.output_proto)\n", + "elif args.solver == 'sat_column':\n", + " steel_mill_slab_with_column_generation(args.problem, args.output_proto)\n", + "else: # 'mip_column'\n", + " steel_mill_slab_with_mip_column_generation(args.problem)\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/examples/stigler_diet.ipynb b/examples/notebook/examples/stigler_diet.ipynb new file mode 100644 index 0000000000..81db30e9d1 --- /dev/null +++ b/examples/notebook/examples/stigler_diet.ipynb @@ -0,0 +1,239 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "#!/usr/bin/env python\n", + "# This Python file uses the following encoding: utf-8\n", + "# Copyright 2018 Google LLC\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"Stigler diet example\"\"\"\n", + "\n", + "from __future__ import print_function\n", + "from six.moves import xrange\n", + "from ortools.linear_solver import pywraplp\n", + "\n", + "\n", + "\"\"\"Entry point of the program\"\"\"\n", + "# Nutrient minimums.\n", + "nutrients = [['Calories (kcal)', 3], ['Protein (g)', 70], [\n", + " 'Calcium (g)', 0.8\n", + "], ['Iron (mg)', 12], ['Vitamin A (KIU)', 5], ['Vitamin B1 (mg)', 1.8],\n", + " ['Vitamin B2 (mg)', 2.7], ['Niacin (mg)',\n", + " 18], ['Vitamin C (mg)', 75]]\n", + "\n", + "# Commodity, Unit, 1939 price (cents), Calories (kcal), Protein (g), Calcium (g), Iron (mg),\n", + "# Vitamin A (KIU), Vitamin B1 (mg), Vitamin B2 (mg), Niacin (mg), Vitamin C (mg)\n", + "data = [[\n", + " 'Wheat Flour (Enriched)', '10 lb.', 36, 44.7, 1411, 2, 365, 0, 55.4,\n", + " 33.3, 441, 0\n", + "], ['Macaroni', '1 lb.', 14.1, 11.6, 418, 0.7, 54, 0, 3.2, 1.9, 68, 0], [\n", + " 'Wheat Cereal (Enriched)', '28 oz.', 24.2, 11.8, 377, 14.4, 175, 0,\n", + " 14.4, 8.8, 114, 0\n", + "], ['Corn Flakes', '8 oz.', 7.1, 11.4, 252, 0.1, 56, 0, 13.5, 2.3, 68, 0], [\n", + " 'Corn Meal', '1 lb.', 4.6, 36.0, 897, 1.7, 99, 30.9, 17.4, 7.9, 106, 0\n", + "], [\n", + " 'Hominy Grits', '24 oz.', 8.5, 28.6, 680, 0.8, 80, 0, 10.6, 1.6, 110, 0\n", + "], ['Rice', '1 lb.', 7.5, 21.2, 460, 0.6, 41, 0, 2, 4.8, 60, 0], [\n", + " 'Rolled Oats', '1 lb.', 7.1, 25.3, 907, 5.1, 341, 0, 37.1, 8.9, 64, 0\n", + "], [\n", + " 'White Bread (Enriched)', '1 lb.', 7.9, 15.0, 488, 2.5, 115, 0, 13.8,\n", + " 8.5, 126, 0\n", + "], [\n", + " 'Whole Wheat Bread', '1 lb.', 9.1, 12.2, 484, 2.7, 125, 0, 13.9, 6.4,\n", + " 160, 0\n", + "], ['Rye Bread', '1 lb.', 9.1, 12.4, 439, 1.1, 82, 0, 9.9, 3, 66, 0], [\n", + " 'Pound Cake', '1 lb.', 24.8, 8.0, 130, 0.4, 31, 18.9, 2.8, 3, 17, 0\n", + "], ['Soda Crackers', '1 lb.', 15.1, 12.5, 288, 0.5, 50, 0, 0, 0, 0, 0], [\n", + " 'Milk', '1 qt.', 11, 6.1, 310, 10.5, 18, 16.8, 4, 16, 7, 177\n", + "], [\n", + " 'Evaporated Milk (can)', '14.5 oz.', 6.7, 8.4, 422, 15.1, 9, 26, 3,\n", + " 23.5, 11, 60\n", + "], ['Butter', '1 lb.', 30.8, 10.8, 9, 0.2, 3, 44.2, 0, 0.2, 2, 0], [\n", + " 'Oleomargarine', '1 lb.', 16.1, 20.6, 17, 0.6, 6, 55.8, 0.2, 0, 0, 0\n", + "], ['Eggs', '1 doz.', 32.6, 2.9, 238, 1.0, 52, 18.6, 2.8, 6.5, 1, 0], [\n", + " 'Cheese (Cheddar)', '1 lb.', 24.2, 7.4, 448, 16.4, 19, 28.1, 0.8, 10.3,\n", + " 4, 0\n", + "], ['Cream', '1/2 pt.', 14.1, 3.5, 49, 1.7, 3, 16.9, 0.6, 2.5, 0, 17], [\n", + " 'Peanut Butter', '1 lb.', 17.9, 15.7, 661, 1.0, 48, 0, 9.6, 8.1, 471, 0\n", + "], ['Mayonnaise', '1/2 pt.', 16.7, 8.6, 18, 0.2, 8, 2.7, 0.4, 0.5, 0, 0], [\n", + " 'Crisco', '1 lb.', 20.3, 20.1, 0, 0, 0, 0, 0, 0, 0, 0\n", + "], ['Lard', '1 lb.', 9.8, 41.7, 0, 0, 0, 0.2, 0, 0.5, 5, 0], [\n", + " 'Sirloin Steak', '1 lb.', 39.6, 2.9, 166, 0.1, 34, 0.2, 2.1, 2.9, 69, 0\n", + "], ['Round Steak', '1 lb.', 36.4, 2.2, 214, 0.1, 32, 0.4, 2.5, 2.4, 87, 0\n", + " ], ['Rib Roast', '1 lb.', 29.2, 3.4, 213, 0.1, 33, 0, 0, 2, 0, 0], [\n", + " 'Chuck Roast', '1 lb.', 22.6, 3.6, 309, 0.2, 46, 0.4, 1, 4, 120, 0\n", + " ], ['Plate', '1 lb.', 14.6, 8.5, 404, 0.2, 62, 0, 0.9, 0, 0, 0], [\n", + " 'Liver (Beef)', '1 lb.', 26.8, 2.2, 333, 0.2, 139, 169.2, 6.4, 50.8,\n", + " 316, 525\n", + " ], [\n", + " 'Leg of Lamb', '1 lb.', 27.6, 3.1, 245, 0.1, 20, 0, 2.8, 3.9, 86, 0\n", + " ], [\n", + " 'Lamb Chops (Rib)',\n", + " '1 lb.', 36.6, 3.3, 140, 0.1, 15, 0, 1.7, 2.7, 54, 0\n", + " ], [\n", + " 'Pork Chops', '1 lb.', 30.7, 3.5, 196, 0.2, 30, 0, 17.4, 2.7, 60, 0\n", + " ], [\n", + " 'Pork Loin Roast',\n", + " '1 lb.', 24.2, 4.4, 249, 0.3, 37, 0, 18.2, 3.6, 79, 0\n", + " ], ['Bacon', '1 lb.', 25.6, 10.4, 152, 0.2, 23, 0, 1.8, 1.8, 71, 0], [\n", + " 'Ham, smoked', '1 lb.', 27.4, 6.7, 212, 0.2, 31, 0, 9.9, 3.3, 50, 0\n", + " ], ['Salt Pork', '1 lb.', 16, 18.8, 164, 0.1, 26, 0, 1.4, 1.8, 0, 0], [\n", + " 'Roasting Chicken', '1 lb.', 30.3, 1.8, 184, 0.1, 30, 0.1, 0.9, 1.8,\n", + " 68, 46\n", + " ], [\n", + " 'Veal Cutlets', '1 lb.', 42.3, 1.7, 156, 0.1, 24, 0, 1.4, 2.4, 57, 0\n", + " ], [\n", + " 'Salmon, Pink (can)', '16 oz.', 13, 5.8, 705, 6.8, 45, 3.5,\n", + " 1, 4.9, 209, 0\n", + " ], ['Apples', '1 lb.', 4.4, 5.8, 27, 0.5, 36, 7.3, 3.6, 2.7, 5, 544], [\n", + " 'Bananas', '1 lb.', 6.1, 4.9, 60, 0.4, 30, 17.4, 2.5, 3.5, 28, 498\n", + " ], ['Lemons', '1 doz.', 26, 1.0, 21, 0.5, 14, 0, 0.5, 0, 4, 952], [\n", + " 'Oranges', '1 doz.', 30.9, 2.2, 40, 1.1, 18, 11.1, 3.6, 1.3, 10, 1998\n", + " ], [\n", + " 'Green Beans', '1 lb.', 7.1, 2.4, 138, 3.7, 80, 69, 4.3, 5.8, 37, 862\n", + " ], ['Cabbage', '1 lb.', 3.7, 2.6, 125, 4.0, 36, 7.2, 9, 4.5, 26, 5369], [\n", + " 'Carrots', '1 bunch', 4.7, 2.7, 73, 2.8, 43, 188.5, 6.1, 4.3, 89, 608\n", + " ], ['Celery', '1 stalk', 7.3, 0.9, 51, 3.0, 23, 0.9, 1.4, 1.4, 9, 313], [\n", + " 'Lettuce', '1 head', 8.2, 0.4, 27, 1.1, 22, 112.4, 1.8, 3.4, 11, 449\n", + " ], ['Onions', '1 lb.', 3.6, 5.8, 166, 3.8, 59, 16.6, 4.7, 5.9, 21,\n", + " 1184], [\n", + " 'Potatoes', '15 lb.', 34, 14.3, 336, 1.8, 118, 6.7, 29.4, 7.1,\n", + " 198, 2522\n", + " ], [\n", + " 'Spinach', '1 lb.', 8.1, 1.1, 106, 0, 138, 918.4, 5.7, 13.8, 33,\n", + " 2755\n", + " ], [\n", + " 'Sweet Potatoes', '1 lb.', 5.1, 9.6, 138, 2.7, 54, 290.7, 8.4,\n", + " 5.4, 83, 1912\n", + " ], [\n", + " 'Peaches (can)', 'No. 2 1/2', 16.8, 3.7, 20, 0.4, 10, 21.5, 0.5,\n", + " 1, 31, 196\n", + " ], [\n", + " 'Pears (can)', 'No. 2 1/2', 20.4, 3.0, 8, 0.3, 8, 0.8, 0.8, 0.8,\n", + " 5, 81\n", + " ], [\n", + " 'Pineapple (can)', 'No. 2 1/2', 21.3, 2.4, 16, 0.4, 8, 2, 2.8,\n", + " 0.8, 7, 399\n", + " ], [\n", + " 'Asparagus (can)', 'No. 2', 27.7, 0.4, 33, 0.3, 12, 16.3, 1.4,\n", + " 2.1, 17, 272\n", + " ], [\n", + " 'Green Beans (can)', 'No. 2', 10, 1.0, 54, 2, 65, 53.9, 1.6, 4.3,\n", + " 32, 431\n", + " ], [\n", + " 'Pork and Beans (can)', '16 oz.', 7.1, 7.5, 364, 4, 134, 3.5,\n", + " 8.3, 7.7, 56, 0\n", + " ], [\n", + " 'Corn (can)', 'No. 2', 10.4, 5.2, 136, 0.2, 16, 12, 1.6, 2.7, 42,\n", + " 218\n", + " ], [\n", + " 'Peas (can)', 'No. 2', 13.8, 2.3, 136, 0.6, 45, 34.9, 4.9, 2.5,\n", + " 37, 370\n", + " ], [\n", + " 'Tomatoes (can)', 'No. 2', 8.6, 1.3, 63, 0.7, 38, 53.2, 3.4, 2.5,\n", + " 36, 1253\n", + " ], [\n", + " 'Tomato Soup (can)', '10 1/2 oz.', 7.6, 1.6, 71, 0.6, 43, 57.9,\n", + " 3.5, 2.4, 67, 862\n", + " ], [\n", + " 'Peaches, Dried', '1 lb.', 15.7, 8.5, 87, 1.7, 173, 86.8, 1.2,\n", + " 4.3, 55, 57\n", + " ], [\n", + " 'Prunes, Dried', '1 lb.', 9, 12.8, 99, 2.5, 154, 85.7, 3.9, 4.3,\n", + " 65, 257\n", + " ], [\n", + " 'Raisins, Dried', '15 oz.', 9.4, 13.5, 104, 2.5, 136, 4.5, 6.3,\n", + " 1.4, 24, 136\n", + " ], [\n", + " 'Peas, Dried', '1 lb.', 7.9, 20.0, 1367, 4.2, 345, 2.9, 28.7,\n", + " 18.4, 162, 0\n", + " ], [\n", + " 'Lima Beans, Dried', '1 lb.', 8.9, 17.4, 1055, 3.7, 459, 5.1,\n", + " 26.9, 38.2, 93, 0\n", + " ], [\n", + " 'Navy Beans, Dried', '1 lb.', 5.9, 26.9, 1691, 11.4, 792, 0,\n", + " 38.4, 24.6, 217, 0\n", + " ], ['Coffee', '1 lb.', 22.4, 0, 0, 0, 0, 0, 4, 5.1, 50,\n", + " 0], ['Tea', '1/4 lb.', 17.4, 0, 0, 0, 0, 0, 0, 2.3, 42, 0],\n", + " ['Cocoa', '8 oz.', 8.6, 8.7, 237, 3, 72, 0, 2, 11.9, 40, 0], [\n", + " 'Chocolate', '8 oz.', 16.2, 8.0, 77, 1.3, 39, 0, 0.9, 3.4, 14, 0\n", + " ], ['Sugar', '10 lb.', 51.7, 34.9, 0, 0, 0, 0, 0, 0, 0, 0],\n", + " ['Corn Syrup', '24 oz.', 13.7, 14.7, 0, 0.5, 74, 0, 0, 0, 5, 0], [\n", + " 'Molasses', '18 oz.', 13.6, 9.0, 0, 10.3, 244, 0, 1.9, 7.5, 146,\n", + " 0\n", + " ], [\n", + " 'Strawberry Preserves', '1 lb.', 20.5, 6.4, 11, 0.4, 7, 0.2,\n", + " 0.2, 0.4, 3, 0\n", + " ]]\n", + "\n", + "# Instantiate a Glop solver, naming it LinearExample.\n", + "solver = pywraplp.Solver('StiglerDietExample',\n", + " pywraplp.Solver.GLOP_LINEAR_PROGRAMMING)\n", + "\n", + "# Declare an array to hold our variables.\n", + "foods = [solver.NumVar(0.0, solver.infinity(), item[0]) for item in data]\n", + "\n", + "# Objective function: Minimize the sum of (price-normalized) foods.\n", + "objective = solver.Objective()\n", + "for food in foods:\n", + " objective.SetCoefficient(food, 1)\n", + "objective.SetMinimization()\n", + "\n", + "# Create the constraints, one per nutrient.\n", + "constraints = []\n", + "for i, nutrient in enumerate(nutrients):\n", + " constraints.append(solver.Constraint(nutrient[1], solver.infinity()))\n", + " for j, item in enumerate(data):\n", + " constraints[i].SetCoefficient(foods[j], item[i + 3])\n", + "\n", + "print('Number of variables =', solver.NumVariables())\n", + "print('Number of constraints =', solver.NumConstraints())\n", + "\n", + "# Solve the system.\n", + "status = solver.Solve()\n", + "# Check that the problem has an optimal solution.\n", + "if status != pywraplp.Solver.OPTIMAL:\n", + " print(\"The problem does not have an optimal solution!\")\n", + " exit(1)\n", + "\n", + "nutrients_result = [0] * len(nutrients)\n", + "print('')\n", + "print('Annual Foods:')\n", + "for i, food in enumerate(foods):\n", + " if food.solution_value() > 0.0:\n", + " print('{}: ${}'.format(data[i][0], 365. * food.solution_value()))\n", + " for j, nutrient in enumerate(nutrients):\n", + " nutrients_result[j] += data[i][j + 3] * food.solution_value()\n", + "print('')\n", + "print('Optimal annual price: ${:.4f}'.format(365. * objective.Value()))\n", + "print('')\n", + "print('Nutrients per day:')\n", + "for i, nutrient in enumerate(nutrients):\n", + " print('{}: {:.2f} (min {})'.format(nutrient[0], nutrients_result[i],\n", + " nutrient[1]))\n", + "print('')\n", + "print('Advanced usage:')\n", + "print('Problem solved in ', solver.wall_time(), ' milliseconds')\n", + "print('Problem solved in ', solver.iterations(), ' iterations')\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/examples/sudoku_sat.ipynb b/examples/notebook/examples/sudoku_sat.ipynb new file mode 100644 index 0000000000..0fe578ad61 --- /dev/null +++ b/examples/notebook/examples/sudoku_sat.ipynb @@ -0,0 +1,90 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010-2018 Google LLC\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"This model implements a sudoku solver.\"\"\"\n", + "\n", + "from __future__ import print_function\n", + "\n", + "from ortools.sat.python import cp_model\n", + "\n", + "\n", + "def solve_sudoku():\n", + " \"\"\"Solves the sudoku problem with the CP-SAT solver.\"\"\"\n", + " # Create the model.\n", + " model = cp_model.CpModel()\n", + "\n", + " cell_size = 3\n", + " line_size = cell_size**2\n", + " line = list(range(0, line_size))\n", + " cell = list(range(0, cell_size))\n", + "\n", + " initial_grid = [[0, 6, 0, 0, 5, 0, 0, 2, 0], [0, 0, 0, 3, 0, 0, 0, 9, 0],\n", + " [7, 0, 0, 6, 0, 0, 0, 1, 0], [0, 0, 6, 0, 3, 0, 4, 0, 0], [\n", + " 0, 0, 4, 0, 7, 0, 1, 0, 0\n", + " ], [0, 0, 5, 0, 9, 0, 8, 0, 0], [0, 4, 0, 0, 0, 1, 0, 0, 6],\n", + " [0, 3, 0, 0, 0, 8, 0, 0, 0], [0, 2, 0, 0, 4, 0, 0, 5, 0]]\n", + "\n", + " grid = {}\n", + " for i in line:\n", + " for j in line:\n", + " grid[(i, j)] = model.NewIntVar(1, line_size, 'grid %i %i' % (i, j))\n", + "\n", + " # AllDifferent on rows.\n", + " for i in line:\n", + " model.AddAllDifferent([grid[(i, j)] for j in line])\n", + "\n", + " # AllDifferent on columns.\n", + " for j in line:\n", + " model.AddAllDifferent([grid[(i, j)] for i in line])\n", + "\n", + " # AllDifferent on cells.\n", + " for i in cell:\n", + " for j in cell:\n", + " one_cell = []\n", + " for di in cell:\n", + " for dj in cell:\n", + " one_cell.append(grid[(i * cell_size + di,\n", + " j * cell_size + dj)])\n", + "\n", + " model.AddAllDifferent(one_cell)\n", + "\n", + " # Initial values.\n", + " for i in line:\n", + " for j in line:\n", + " if initial_grid[i][j]:\n", + " model.Add(grid[(i, j)] == initial_grid[i][j])\n", + "\n", + " # Solve and print out the solution.\n", + " solver = cp_model.CpSolver()\n", + " status = solver.Solve(model)\n", + " if status == cp_model.FEASIBLE:\n", + " for i in line:\n", + " print([int(solver.Value(grid[(i, j)])) for j in line])\n", + "\n", + "\n", + "solve_sudoku()\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/examples/task_allocation_sat.ipynb b/examples/notebook/examples/task_allocation_sat.ipynb new file mode 100644 index 0000000000..5b257485bf --- /dev/null +++ b/examples/notebook/examples/task_allocation_sat.ipynb @@ -0,0 +1,298 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010-2018 Google LLC\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"CP-SAT model for task allocation problem.\n", + "see\n", + "http://yetanothermathprogrammingconsultant.blogspot.com/2018/09/minizinc-cpsat-vs-mip.html\n", + "\"\"\"\n", + "\n", + "from __future__ import print_function\n", + "\n", + "from ortools.sat.python import cp_model\n", + "\n", + "\n", + "\"\"\"Solves the task allocation problem.\"\"\"\n", + "# Availability matrix.\n", + "available = [[\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 0\n", + "], [\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1,\n", + " 1, 1\n", + "], [\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 0\n", + "], [\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0,\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 0\n", + "], [\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 0\n", + "], [\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 0\n", + "], [\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1,\n", + " 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 0\n", + "], [\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1,\n", + " 1, 0\n", + "], [\n", + " 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 0\n", + "], [\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 0\n", + "], [\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 1\n", + "], [\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 0\n", + "], [\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 1\n", + "], [\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0,\n", + " 0, 0\n", + "], [\n", + " 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 0\n", + "], [\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 0\n", + "], [\n", + " 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 0\n", + "], [\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0,\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 0\n", + "], [\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 0\n", + "], [\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1,\n", + " 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 0\n", + "], [\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0,\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 0\n", + "], [\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0,\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 0\n", + "], [\n", + " 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 0\n", + "], [\n", + " 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 0\n", + "], [\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 0\n", + "], [\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1,\n", + " 1, 1\n", + "], [\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 0\n", + "], [\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 0\n", + "], [\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0,\n", + " 0, 0\n", + "], [\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0,\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 0\n", + "], [\n", + " 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 0\n", + "], [\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 0\n", + "], [\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 0\n", + "], [\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1,\n", + " 1, 0\n", + "], [\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 0\n", + "], [\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0,\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 0\n", + "], [\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 0\n", + "], [\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 0\n", + "], [\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 0\n", + "], [\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,\n", + " 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 0\n", + "], [\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1,\n", + " 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 0\n", + "], [\n", + " 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 0\n", + "], [\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0,\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 0\n", + "], [\n", + " 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 0\n", + "], [\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0,\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 0\n", + "], [\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0,\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 0\n", + "], [\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 0\n", + "], [\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 0\n", + "], [\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 0\n", + "], [\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0,\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 0\n", + "]]\n", + "\n", + "ntasks = len(available)\n", + "nslots = len(available[0])\n", + "\n", + "# sets\n", + "all_tasks = range(ntasks)\n", + "all_slots = range(nslots)\n", + "\n", + "# max tasks per time slot\n", + "capacity = 3\n", + "\n", + "# Model\n", + "model = cp_model.CpModel()\n", + "assign = {}\n", + "for task in all_tasks:\n", + " for slot in all_slots:\n", + " assign[(task, slot)] = model.NewBoolVar('x[%i][%i]' % (task, slot))\n", + "count = model.NewIntVar(0, nslots, 'count')\n", + "slot_used = [model.NewBoolVar('slot_used[%i]' % s) for s in all_slots]\n", + "\n", + "for task in all_tasks:\n", + " model.Add(\n", + " sum(assign[(task, slot)] for slot in all_slots\n", + " if available[task][slot] == 1) == 1)\n", + "\n", + "for slot in all_slots:\n", + " model.Add(\n", + " sum(assign[(task, slot)] for task in all_tasks\n", + " if available[task][slot] == 1) <= capacity)\n", + " model.AddBoolOr([\n", + " assign[(task, slot)] for task in all_tasks\n", + " if available[task][slot] == 1\n", + " ]).OnlyEnforceIf(slot_used[slot])\n", + " for task in all_tasks:\n", + " if available[task][slot] == 1:\n", + " model.AddImplication(slot_used[slot].Not(),\n", + " assign[(task, slot)].Not())\n", + " else:\n", + " model.Add(assign[(task, slot)] == 0)\n", + "\n", + "model.Add(count == sum(slot_used))\n", + "# Redundant constraint. This instance is easier if we add this constraint.\n", + "# model.Add(count >= (nslots + capacity - 1) // capacity)\n", + "\n", + "model.Minimize(count)\n", + "\n", + "# Create a solver and solve the problem.\n", + "solver = cp_model.CpSolver()\n", + "# Uses the portfolion of heuristics.\n", + "solver.parameters.log_search_progress = True\n", + "solver.parameters.num_search_workers = 6\n", + "status = solver.Solve(model)\n", + "\n", + "print('Statistics')\n", + "print(' - status =', solver.StatusName(status))\n", + "print(' - optimal solution =', solver.ObjectiveValue())\n", + "print(' - wall time : %f s' % solver.WallTime())\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/examples/tasks_and_workers_assignment_sat.ipynb b/examples/notebook/examples/tasks_and_workers_assignment_sat.ipynb new file mode 100644 index 0000000000..98c514d5d3 --- /dev/null +++ b/examples/notebook/examples/tasks_and_workers_assignment_sat.ipynb @@ -0,0 +1,136 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010-2018 Google LLC\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"Tasks and workers to group assignment to average sum(cost) / #workers\"\"\"\n", + "\n", + "from __future__ import print_function\n", + "\n", + "from ortools.sat.python import cp_model\n", + "\n", + "\n", + "class ObjectivePrinter(cp_model.CpSolverSolutionCallback):\n", + " \"\"\"Print intermediate solutions.\"\"\"\n", + "\n", + " def __init__(self):\n", + " cp_model.CpSolverSolutionCallback.__init__(self)\n", + " self.__solution_count = 0\n", + "\n", + " def on_solution_callback(self):\n", + " print('Solution %i, time = %f s, objective = %i' %\n", + " (self.__solution_count, self.WallTime(), self.ObjectiveValue()))\n", + " self.__solution_count += 1\n", + "\n", + "\n", + "def tasks_and_workers_assignment_sat():\n", + " \"\"\"Solve the assignment problem.\"\"\"\n", + " model = cp_model.CpModel()\n", + "\n", + " # CP-SAT solver is integer only.\n", + " task_cost = [24, 10, 7, 2, 11, 16, 1, 13, 9, 27]\n", + " num_tasks = len(task_cost)\n", + " num_workers = 3\n", + " num_groups = 2\n", + " all_workers = range(num_workers)\n", + " all_groups = range(num_groups)\n", + " all_tasks = range(num_tasks)\n", + "\n", + " # Variables\n", + "\n", + " ## x_ij = 1 if worker i is assigned to group j\n", + " x = {}\n", + " for i in all_workers:\n", + " for j in all_groups:\n", + " x[i, j] = model.NewBoolVar('x[%i,%i]' % (i, j))\n", + "\n", + " ## y_kj is 1 if task k is assigned to group j\n", + " y = {}\n", + " for k in all_tasks:\n", + " for j in all_groups:\n", + " y[k, j] = model.NewBoolVar('x[%i,%i]' % (k, j))\n", + "\n", + " # Constraints\n", + "\n", + " # Each task k is assigned to a group and only one.\n", + " for k in all_tasks:\n", + " model.Add(sum(y[k, j] for j in all_groups) == 1)\n", + "\n", + " # Each worker i is assigned to a group and only one.\n", + " for i in all_workers:\n", + " model.Add(sum(x[i, j] for j in all_groups) == 1)\n", + "\n", + " # cost per group\n", + " sum_of_costs = sum(task_cost)\n", + " averages = []\n", + " num_workers_in_group = []\n", + " scaled_sum_of_costs_in_group = []\n", + " scaling = 1000 # We introduce scaling to deal with floating point average.\n", + " for j in all_groups:\n", + " n = model.NewIntVar(1, num_workers, 'num_workers_in_group_%i' % j)\n", + " model.Add(n == sum(x[i, j] for i in all_workers))\n", + " c = model.NewIntVar(0, sum_of_costs * scaling,\n", + " 'sum_of_costs_of_group_%i' % j)\n", + " model.Add(c == sum(y[k, j] * task_cost[k] * scaling for k in all_tasks))\n", + " a = model.NewIntVar(0, sum_of_costs * scaling,\n", + " 'average_cost_of_group_%i' % j)\n", + " model.AddDivisionEquality(a, c, n)\n", + "\n", + " averages.append(a)\n", + " num_workers_in_group.append(n)\n", + " scaled_sum_of_costs_in_group.append(c)\n", + "\n", + " # All workers are assigned.\n", + " model.Add(sum(num_workers_in_group) == num_workers)\n", + "\n", + " # Objective.\n", + " obj = model.NewIntVar(0, sum_of_costs * scaling, 'obj')\n", + " model.AddMaxEquality(obj, averages)\n", + " model.Minimize(obj)\n", + "\n", + " # Solve and print out the solution.\n", + " solver = cp_model.CpSolver()\n", + " solver.parameters.max_time_in_seconds = 60 * 60 * 2\n", + " objective_printer = ObjectivePrinter()\n", + " status = solver.SolveWithSolutionCallback(model, objective_printer)\n", + " print(solver.ResponseStats())\n", + "\n", + " if status == cp_model.OPTIMAL:\n", + " for j in all_groups:\n", + " print('Group %i' % j)\n", + " for i in all_workers:\n", + " if solver.BooleanValue(x[i, j]):\n", + " print(' - worker %i' % i)\n", + " for k in all_tasks:\n", + " if solver.BooleanValue(y[k, j]):\n", + " print(' - task %i with cost %i' % (k, task_cost[k]))\n", + " print(' - sum_of_costs = %i' %\n", + " (solver.Value(scaled_sum_of_costs_in_group[j]) // scaling))\n", + " print(' - average cost = %f' %\n", + " (solver.Value(averages[j]) * 1.0 / scaling))\n", + "\n", + "\n", + "tasks_and_workers_assignment_sat()\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/examples/transit_time.ipynb b/examples/notebook/examples/transit_time.ipynb new file mode 100644 index 0000000000..89edc41c02 --- /dev/null +++ b/examples/notebook/examples/transit_time.ipynb @@ -0,0 +1,237 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "#!/usr/bin/env python\n", + "# This Python file uses the following encoding: utf-8\n", + "# Copyright 2015 Tin Arm Engineering AB\n", + "# Copyright 2018 Google LLC\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"Display Transit Time\n", + " Distances are in meters and time in minutes.\n", + "\n", + " Manhattan average block: 750ft x 264ft -> 228m x 80m\n", + " src: https://nyti.ms/2GDoRIe \"NY Times: Know Your distance\"\n", + " here we use: 114m x 80m city block\n", + "\"\"\"\n", + "\n", + "from __future__ import print_function\n", + "from six.moves import xrange\n", + "from ortools.constraint_solver import pywrapcp\n", + "from ortools.constraint_solver import routing_enums_pb2\n", + "\n", + "\n", + "###########################\n", + "# Problem Data Definition #\n", + "###########################\n", + "class Vehicle():\n", + " \"\"\"Stores the property of a vehicle\"\"\"\n", + "\n", + " def __init__(self):\n", + " \"\"\"Initializes the vehicle properties\"\"\"\n", + " self._capacity = 15\n", + " # Travel speed: 5km/h to convert in m/min\n", + " self._speed = 5 * 60 / 3.6\n", + "\n", + " @property\n", + " def speed(self):\n", + " \"\"\"Gets the average travel speed of a vehicle\"\"\"\n", + " return self._speed\n", + "\n", + "\n", + "class CityBlock():\n", + " \"\"\"City block definition\"\"\"\n", + "\n", + " @property\n", + " def width(self):\n", + " \"\"\"Gets Block size West to East\"\"\"\n", + " return 228 / 2\n", + "\n", + " @property\n", + " def height(self):\n", + " \"\"\"Gets Block size North to South\"\"\"\n", + " return 80\n", + "\n", + "\n", + "class DataProblem():\n", + " \"\"\"Stores the data for the problem\"\"\"\n", + "\n", + " def __init__(self):\n", + " \"\"\"Initializes the data for the problem\"\"\"\n", + " self._vehicle = Vehicle()\n", + "\n", + " # Locations in block unit\n", + " locations = \\\n", + " [(4, 4), # depot\n", + " (2, 0), (8, 0), # row 0\n", + " (0, 1), (1, 1),\n", + " (5, 2), (7, 2),\n", + " (3, 3), (6, 3),\n", + " (5, 5), (8, 5),\n", + " (1, 6), (2, 6),\n", + " (3, 7), (6, 7),\n", + " (0, 8), (7, 8)]\n", + " # locations in meters using the city block dimension\n", + " city_block = CityBlock()\n", + " self._locations = [(loc[0] * city_block.width,\n", + " loc[1] * city_block.height) for loc in locations]\n", + "\n", + " self._depot = 0\n", + "\n", + " self._demands = \\\n", + " [0, # depot\n", + " 1, 1, # 1, 2\n", + " 2, 4, # 3, 4\n", + " 2, 4, # 5, 6\n", + " 8, 8, # 7, 8\n", + " 1, 2, # 9,10\n", + " 1, 2, # 11,12\n", + " 4, 4, # 13, 14\n", + " 8, 8] # 15, 16\n", + "\n", + " self._time_windows = \\\n", + " [(0, 0),\n", + " (75, 85), (75, 85), # 1, 2\n", + " (60, 70), (45, 55), # 3, 4\n", + " (0, 8), (50, 60), # 5, 6\n", + " (0, 10), (10, 20), # 7, 8\n", + " (0, 10), (75, 85), # 9, 10\n", + " (85, 95), (5, 15), # 11, 12\n", + " (15, 25), (10, 20), # 13, 14\n", + " (45, 55), (30, 40)] # 15, 16\n", + "\n", + " @property\n", + " def vehicle(self):\n", + " \"\"\"Gets a vehicle\"\"\"\n", + " return self._vehicle\n", + "\n", + " @property\n", + " def locations(self):\n", + " \"\"\"Gets locations\"\"\"\n", + " return self._locations\n", + "\n", + " @property\n", + " def num_locations(self):\n", + " \"\"\"Gets number of locations\"\"\"\n", + " return len(self.locations)\n", + "\n", + " @property\n", + " def depot(self):\n", + " \"\"\"Gets depot location index\"\"\"\n", + " return self._depot\n", + "\n", + " @property\n", + " def demands(self):\n", + " \"\"\"Gets demands at each location\"\"\"\n", + " return self._demands\n", + "\n", + " @property\n", + " def time_per_demand_unit(self):\n", + " \"\"\"Gets the time (in min) to load a demand\"\"\"\n", + " return 5 # 5 minutes/unit\n", + "\n", + " @property\n", + " def time_windows(self):\n", + " \"\"\"Gets (start time, end time) for each locations\"\"\"\n", + " return self._time_windows\n", + "\n", + "\n", + "#######################\n", + "# Problem Constraints #\n", + "#######################\n", + "def manhattan_distance(position_1, position_2):\n", + " \"\"\"Computes the Manhattan distance between two points\"\"\"\n", + " return (\n", + " abs(position_1[0] - position_2[0]) + abs(position_1[1] - position_2[1]))\n", + "\n", + "\n", + "class CreateTimeEvaluator(object):\n", + " \"\"\"Creates callback to get total times between locations.\"\"\"\n", + "\n", + " @staticmethod\n", + " def service_time(data, node):\n", + " \"\"\"Gets the service time for the specified location.\"\"\"\n", + " return data.demands[node] * data.time_per_demand_unit\n", + "\n", + " @staticmethod\n", + " def travel_time(data, from_node, to_node):\n", + " \"\"\"Gets the travel times between two locations.\"\"\"\n", + " if from_node == to_node:\n", + " travel_time = 0\n", + " else:\n", + " travel_time = manhattan_distance(data.locations[\n", + " from_node], data.locations[to_node]) / data.vehicle.speed\n", + " return travel_time\n", + "\n", + " def __init__(self, data):\n", + " \"\"\"Initializes the total time matrix.\"\"\"\n", + " self._total_time = {}\n", + " # precompute total time to have time callback in O(1)\n", + " for from_node in xrange(data.num_locations):\n", + " self._total_time[from_node] = {}\n", + " for to_node in xrange(data.num_locations):\n", + " if from_node == to_node:\n", + " self._total_time[from_node][to_node] = 0\n", + " else:\n", + " self._total_time[from_node][to_node] = int(\n", + " self.service_time(data, from_node) + self.travel_time(\n", + " data, from_node, to_node))\n", + "\n", + " def time_evaluator(self, from_node, to_node):\n", + " \"\"\"Returns the total time between the two nodes\"\"\"\n", + " return self._total_time[from_node][to_node]\n", + "\n", + "\n", + "def print_transit_time(route, time_evaluator):\n", + " \"\"\"Print transit time between nodes of a route\"\"\"\n", + " total_time = 0\n", + " for i, j in route:\n", + " total_time += time_evaluator(i, j)\n", + " print('{0} -> {1}: {2}min'.format(i, j, time_evaluator(i, j)))\n", + " print('Total time: {0}min\\n'.format(total_time))\n", + "\n", + "\n", + "########\n", + "# Main #\n", + "########\n", + "\"\"\"Entry point of the program\"\"\"\n", + "# Instantiate the data problem.\n", + "data = DataProblem()\n", + "\n", + "# Print Transit Time\n", + "time_evaluator = CreateTimeEvaluator(data).time_evaluator\n", + "print('Route 0:')\n", + "print_transit_time([[0, 5], [5, 8], [8, 6], [6, 2], [2, 0]], time_evaluator)\n", + "\n", + "print('Route 1:')\n", + "print_transit_time([[0, 9], [9, 14], [14, 16], [16, 10], [10, 0]],\n", + " time_evaluator)\n", + "\n", + "print('Route 2:')\n", + "print_transit_time([[0, 12], [12, 13], [13, 15], [15, 11], [11, 0]],\n", + " time_evaluator)\n", + "\n", + "print('Route 3:')\n", + "print_transit_time([[0, 7], [7, 4], [4, 3], [3, 1], [1, 0]], time_evaluator)\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/examples/tsp_sat.ipynb b/examples/notebook/examples/tsp_sat.ipynb new file mode 100644 index 0000000000..d287741feb --- /dev/null +++ b/examples/notebook/examples/tsp_sat.ipynb @@ -0,0 +1,117 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010-2018 Google LLC\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"Simple travelling salesman problem between cities.\"\"\"\n", + "\n", + "from __future__ import print_function\n", + "\n", + "from ortools.sat.python import cp_model\n", + "\n", + "\n", + "distance_matrix = [\n", + " [0, 10938, 4542, 2835, 29441, 2171, 1611, 9208, 9528, 11111, 16120, 22606, 22127, 20627, 21246, 23387, 16697, 33609, 26184, 24772, 22644, 20655, 30492, 23296, 32979, 18141, 19248, 17129, 17192, 15645, 12658, 11210, 12094, 13175, 18162, 4968, 12308, 10084, 13026, 15056],\n", + " [10938, 0, 6422, 9742, 18988, 12974, 11216, 19715, 19004, 18271, 25070, 31971, 31632, 30571, 31578, 33841, 27315, 43964, 36944, 35689, 33569, 31481, 41360, 33760, 43631, 28730, 29976, 27803, 28076, 26408, 23504, 22025, 22000, 13197, 14936, 15146, 23246, 20956, 23963, 25994],\n", + " [4542, 6422, 0, 3644, 25173, 6552, 5092, 13584, 13372, 13766, 19805, 26537, 26117, 24804, 25590, 27784, 21148, 37981, 30693, 29315, 27148, 25071, 34943, 27472, 37281, 22389, 23592, 21433, 21655, 20011, 17087, 15612, 15872, 11653, 15666, 8842, 16843, 14618, 17563, 19589],\n", + " [2835, 9742, 3644, 0, 28681, 3851, 4341, 11660, 12294, 13912, 18893, 25283, 24777, 23173, 23636, 25696, 18950, 35927, 28233, 26543, 24127, 21864, 31765, 24018, 33904, 19005, 20295, 18105, 18551, 16763, 13958, 12459, 12296, 10370, 15331, 5430, 14044, 12135, 14771, 16743],\n", + " [29441, 18988, 25173, 28681, 0, 31590, 29265, 37173, 35501, 32929, 40239, 47006, 46892, 46542, 48112, 50506, 44539, 60103, 54208, 53557, 51878, 50074, 59849, 52645, 62415, 47544, 48689, 46560, 46567, 45086, 42083, 40648, 40971, 29929, 28493, 34015, 41473, 38935, 42160, 44198],\n", + " [2171, 12974, 6552, 3851, 31590, 0, 3046, 7856, 8864, 11330, 15411, 21597, 21065, 19382, 19791, 21845, 15099, 32076, 24425, 22848, 20600, 18537, 28396, 21125, 30825, 15975, 17101, 14971, 15104, 13503, 10544, 9080, 9983, 13435, 18755, 2947, 10344, 8306, 11069, 13078],\n", + " [1611, 11216, 5092, 4341, 29265, 3046, 0, 8526, 8368, 9573, 14904, 21529, 21085, 19719, 20504, 22713, 16118, 32898, 25728, 24541, 22631, 20839, 30584, 23755, 33278, 18557, 19545, 17490, 17309, 15936, 12881, 11498, 12944, 14711, 19589, 5993, 12227, 9793, 12925, 14967],\n", + " [9208, 19715, 13584, 11660, 37173, 7856, 8526, 0, 3248, 7855, 8245, 13843, 13272, 11526, 12038, 14201, 7599, 24411, 17259, 16387, 15050, 13999, 23134, 17899, 26460, 12894, 13251, 11680, 10455, 9997, 7194, 6574, 10678, 20959, 26458, 8180, 5255, 2615, 5730, 7552],\n", + " [9528, 19004, 13372, 12294, 35501, 8864, 8368, 3248, 0, 4626, 6598, 13168, 12746, 11567, 12731, 15083, 9120, 25037, 18718, 18433, 17590, 16888, 25630, 20976, 29208, 16055, 16300, 14838, 13422, 13165, 10430, 9813, 13777, 22300, 27564, 10126, 8388, 5850, 8778, 10422],\n", + " [11111, 18271, 13766, 13912, 32929, 11330, 9573, 7855, 4626, 0, 7318, 14185, 14005, 13655, 15438, 17849, 12839, 27179, 21947, 22230, 21814, 21366, 29754, 25555, 33535, 20674, 20872, 19457, 17961, 17787, 15048, 14372, 18115, 24280, 29101, 13400, 13008, 10467, 13375, 14935],\n", + " [16120, 25070, 19805, 18893, 40239, 15411, 14904, 8245, 6598, 7318, 0, 6939, 6702, 6498, 8610, 10961, 7744, 19889, 15350, 16403, 16975, 17517, 24357, 22176, 28627, 18093, 17672, 16955, 14735, 15510, 13694, 13768, 18317, 28831, 34148, 16326, 11276, 9918, 11235, 11891],\n", + " [22606, 31971, 26537, 25283, 47006, 21597, 21529, 13843, 13168, 14185, 6939, 0, 793, 3401, 5562, 6839, 8923, 13433, 11264, 13775, 15853, 17629, 21684, 22315, 26411, 19539, 18517, 18636, 16024, 17632, 16948, 17587, 22131, 34799, 40296, 21953, 14739, 14568, 14366, 14002],\n", + " [22127, 31632, 26117, 24777, 46892, 21065, 21085, 13272, 12746, 14005, 6702, 793, 0, 2608, 4809, 6215, 8151, 13376, 10702, 13094, 15099, 16845, 21039, 21535, 25744, 18746, 17725, 17845, 15232, 16848, 16197, 16859, 21391, 34211, 39731, 21345, 14006, 13907, 13621, 13225],\n", + " [20627, 30571, 24804, 23173, 46542, 19382, 19719, 11526, 11567, 13655, 6498, 3401, 2608, 0, 2556, 4611, 5630, 13586, 9157, 11005, 12681, 14285, 19044, 18996, 23644, 16138, 15126, 15240, 12625, 14264, 13736, 14482, 18958, 32292, 37879, 19391, 11621, 11803, 11188, 10671],\n", + " [21246, 31578, 25590, 23636, 48112, 19791, 20504, 12038, 12731, 15438, 8610, 5562, 4809, 2556, 0, 2411, 4917, 12395, 6757, 8451, 10292, 12158, 16488, 16799, 21097, 14374, 13194, 13590, 10943, 12824, 12815, 13779, 18042, 32259, 37918, 19416, 10975, 11750, 10424, 9475],\n", + " [23387, 33841, 27784, 25696, 50506, 21845, 22713, 14201, 15083, 17849, 10961, 6839, 6215, 4611, 2411, 0, 6760, 10232, 4567, 7010, 9607, 12003, 14846, 16408, 19592, 14727, 13336, 14109, 11507, 13611, 14104, 15222, 19237, 34013, 39703, 21271, 12528, 13657, 11907, 10633],\n", + " [16697, 27315, 21148, 18950, 44539, 15099, 16118, 7599, 9120, 12839, 7744, 8923, 8151, 5630, 4917, 6760, 0, 16982, 9699, 9400, 9302, 9823, 16998, 14534, 21042, 10911, 10190, 9900, 7397, 8758, 8119, 8948, 13353, 27354, 33023, 14542, 6106, 6901, 5609, 5084],\n", + " [33609, 43964, 37981, 35927, 60103, 32076, 32898, 24411, 25037, 27179, 19889, 13433, 13376, 13586, 12395, 10232, 16982, 0, 8843, 12398, 16193, 19383, 16423, 22583, 20997, 22888, 21194, 22640, 20334, 22636, 23801, 25065, 28675, 44048, 49756, 31426, 22528, 23862, 21861, 20315],\n", + " [26184, 36944, 30693, 28233, 54208, 24425, 25728, 17259, 18718, 21947, 15350, 11264, 10702, 9157, 6757, 4567, 9699, 8843, 0, 3842, 7518, 10616, 10666, 14237, 15515, 14053, 12378, 13798, 11537, 13852, 15276, 16632, 19957, 35660, 41373, 23361, 14333, 16125, 13624, 11866],\n", + " [24772, 35689, 29315, 26543, 53557, 22848, 24541, 16387, 18433, 22230, 16403, 13775, 13094, 11005, 8451, 7010, 9400, 12398, 3842, 0, 3795, 7014, 8053, 10398, 12657, 10633, 8889, 10569, 8646, 10938, 12906, 14366, 17106, 33171, 38858, 21390, 12507, 14748, 11781, 9802],\n", + " [22644, 33569, 27148, 24127, 51878, 20600, 22631, 15050, 17590, 21814, 16975, 15853, 15099, 12681, 10292, 9607, 9302, 16193, 7518, 3795, 0, 3250, 8084, 6873, 11763, 6949, 5177, 7050, 5619, 7730, 10187, 11689, 13792, 30012, 35654, 18799, 10406, 12981, 9718, 7682],\n", + " [20655, 31481, 25071, 21864, 50074, 18537, 20839, 13999, 16888, 21366, 17517, 17629, 16845, 14285, 12158, 12003, 9823, 19383, 10616, 7014, 3250, 0, 9901, 4746, 12531, 3737, 1961, 4036, 3588, 5109, 7996, 9459, 10846, 27094, 32690, 16451, 8887, 11624, 8304, 6471],\n", + " [30492, 41360, 34943, 31765, 59849, 28396, 30584, 23134, 25630, 29754, 24357, 21684, 21039, 19044, 16488, 14846, 16998, 16423, 10666, 8053, 8084, 9901, 0, 9363, 4870, 13117, 11575, 13793, 13300, 15009, 17856, 19337, 20454, 36551, 42017, 26352, 18403, 21033, 17737, 15720],\n", + " [23296, 33760, 27472, 24018, 52645, 21125, 23755, 17899, 20976, 25555, 22176, 22315, 21535, 18996, 16799, 16408, 14534, 22583, 14237, 10398, 6873, 4746, 9363, 0, 10020, 5211, 4685, 6348, 7636, 8010, 11074, 12315, 11926, 27537, 32880, 18634, 12644, 15358, 12200, 10674],\n", + " [32979, 43631, 37281, 33904, 62415, 30825, 33278, 26460, 29208, 33535, 28627, 26411, 25744, 23644, 21097, 19592, 21042, 20997, 15515, 12657, 11763, 12531, 4870, 10020, 0, 14901, 13738, 15855, 16118, 17348, 20397, 21793, 21936, 37429, 42654, 28485, 21414, 24144, 20816, 18908],\n", + " [18141, 28730, 22389, 19005, 47544, 15975, 18557, 12894, 16055, 20674, 18093, 19539, 18746, 16138, 14374, 14727, 10911, 22888, 14053, 10633, 6949, 3737, 13117, 5211, 14901, 0, 1777, 1217, 3528, 2896, 5892, 7104, 7338, 23517, 29068, 13583, 7667, 10304, 7330, 6204],\n", + " [19248, 29976, 23592, 20295, 48689, 17101, 19545, 13251, 16300, 20872, 17672, 18517, 17725, 15126, 13194, 13336, 10190, 21194, 12378, 8889, 5177, 1961, 11575, 4685, 13738, 1777, 0, 2217, 2976, 3610, 6675, 8055, 8965, 25197, 30774, 14865, 8007, 10742, 7532, 6000],\n", + " [17129, 27803, 21433, 18105, 46560, 14971, 17490, 11680, 14838, 19457, 16955, 18636, 17845, 15240, 13590, 14109, 9900, 22640, 13798, 10569, 7050, 4036, 13793, 6348, 15855, 1217, 2217, 0, 2647, 1686, 4726, 6000, 6810, 23060, 28665, 12674, 6450, 9094, 6117, 5066],\n", + " [17192, 28076, 21655, 18551, 46567, 15104, 17309, 10455, 13422, 17961, 14735, 16024, 15232, 12625, 10943, 11507, 7397, 20334, 11537, 8646, 5619, 3588, 13300, 7636, 16118, 3528, 2976, 2647, 0, 2320, 4593, 6093, 8479, 24542, 30219, 13194, 5301, 8042, 4735, 3039],\n", + " [15645, 26408, 20011, 16763, 45086, 13503, 15936, 9997, 13165, 17787, 15510, 17632, 16848, 14264, 12824, 13611, 8758, 22636, 13852, 10938, 7730, 5109, 15009, 8010, 17348, 2896, 3610, 1686, 2320, 0, 3086, 4444, 6169, 22301, 27963, 11344, 4780, 7408, 4488, 3721],\n", + " [12658, 23504, 17087, 13958, 42083, 10544, 12881, 7194, 10430, 15048, 13694, 16948, 16197, 13736, 12815, 14104, 8119, 23801, 15276, 12906, 10187, 7996, 17856, 11074, 20397, 5892, 6675, 4726, 4593, 3086, 0, 1501, 5239, 20390, 26101, 8611, 2418, 4580, 2599, 3496],\n", + " [11210, 22025, 15612, 12459, 40648, 9080, 11498, 6574, 9813, 14372, 13768, 17587, 16859, 14482, 13779, 15222, 8948, 25065, 16632, 14366, 11689, 9459, 19337, 12315, 21793, 7104, 8055, 6000, 6093, 4444, 1501, 0, 4608, 19032, 24747, 7110, 2860, 4072, 3355, 4772],\n", + " [12094, 22000, 15872, 12296, 40971, 9983, 12944, 10678, 13777, 18115, 18317, 22131, 21391, 18958, 18042, 19237, 13353, 28675, 19957, 17106, 13792, 10846, 20454, 11926, 21936, 7338, 8965, 6810, 8479, 6169, 5239, 4608, 0, 16249, 21866, 7146, 7403, 8446, 7773, 8614],\n", + " [13175, 13197, 11653, 10370, 29929, 13435, 14711, 20959, 22300, 24280, 28831, 34799, 34211, 32292, 32259, 34013, 27354, 44048, 35660, 33171, 30012, 27094, 36551, 27537, 37429, 23517, 25197, 23060, 24542, 22301, 20390, 19032, 16249, 0, 5714, 12901, 21524, 20543, 22186, 23805],\n", + " [18162, 14936, 15666, 15331, 28493, 18755, 19589, 26458, 27564, 29101, 34148, 40296, 39731, 37879, 37918, 39703, 33023, 49756, 41373, 38858, 35654, 32690, 42017, 32880, 42654, 29068, 30774, 28665, 30219, 27963, 26101, 24747, 21866, 5714, 0, 18516, 27229, 26181, 27895, 29519],\n", + " [4968, 15146, 8842, 5430, 34015, 2947, 5993, 8180, 10126, 13400, 16326, 21953, 21345, 19391, 19416, 21271, 14542, 31426, 23361, 21390, 18799, 16451, 26352, 18634, 28485, 13583, 14865, 12674, 13194, 11344, 8611, 7110, 7146, 12901, 18516, 0, 9029, 7668, 9742, 11614],\n", + " [12308, 23246, 16843, 14044, 41473, 10344, 12227, 5255, 8388, 13008, 11276, 14739, 14006, 11621, 10975, 12528, 6106, 22528, 14333, 12507, 10406, 8887, 18403, 12644, 21414, 7667, 8007, 6450, 5301, 4780, 2418, 2860, 7403, 21524, 27229, 9029, 0, 2747, 726, 2749],\n", + " [10084, 20956, 14618, 12135, 38935, 8306, 9793, 2615, 5850, 10467, 9918, 14568, 13907, 11803, 11750, 13657, 6901, 23862, 16125, 14748, 12981, 11624, 21033, 15358, 24144, 10304, 10742, 9094, 8042, 7408, 4580, 4072, 8446, 20543, 26181, 7668, 2747, 0, 3330, 5313],\n", + " [13026, 23963, 17563, 14771, 42160, 11069, 12925, 5730, 8778, 13375, 11235, 14366, 13621, 11188, 10424, 11907, 5609, 21861, 13624, 11781, 9718, 8304, 17737, 12200, 20816, 7330, 7532, 6117, 4735, 4488, 2599, 3355, 7773, 22186, 27895, 9742, 726, 3330, 0, 2042],\n", + " [15056, 25994, 19589, 16743, 44198, 13078, 14967, 7552, 10422, 14935, 11891, 14002, 13225, 10671, 9475, 10633, 5084, 20315, 11866, 9802, 7682, 6471, 15720, 10674, 18908, 6204, 6000, 5066, 3039, 3721, 3496, 4772, 8614, 23805, 29519, 11614, 2749, 5313, 2042, 0],\n", + " ] # yapf: disable\n", + "\n", + "\n", + "\"\"\"Entry point of the program.\"\"\"\n", + "num_nodes = len(distance_matrix)\n", + "all_nodes = range(num_nodes)\n", + "print('Num nodes =', num_nodes)\n", + "\n", + "# Model.\n", + "model = cp_model.CpModel()\n", + "\n", + "obj_vars = []\n", + "obj_coeffs = []\n", + "\n", + "# Create the circuit constraint.\n", + "arcs = []\n", + "for i in all_nodes:\n", + " for j in all_nodes:\n", + " if i == j:\n", + " continue\n", + "\n", + " lit = model.NewBoolVar('%i follows %i' % (j, i))\n", + " arcs.append([i, j, lit])\n", + "\n", + " obj_vars.append(lit)\n", + " obj_coeffs.append(distance_matrix[i][j])\n", + "\n", + "model.AddCircuit(arcs)\n", + "\n", + "# Minimize weighted sum of arcs. Because this s\n", + "model.Minimize(\n", + " sum(obj_vars[i] * obj_coeffs[i] for i in range(len(obj_vars))))\n", + "\n", + "# Solve and print out the solution.\n", + "solver = cp_model.CpSolver()\n", + "solver.parameters.log_search_progress = True\n", + "# To benefit from the linearization of the circuit constraint.\n", + "solver.parameters.linearization_level = 2\n", + "\n", + "solver.Solve(model)\n", + "print(solver.ResponseStats())\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/examples/vendor_scheduling_sat.ipynb b/examples/notebook/examples/vendor_scheduling_sat.ipynb new file mode 100644 index 0000000000..c810ceb0a2 --- /dev/null +++ b/examples/notebook/examples/vendor_scheduling_sat.ipynb @@ -0,0 +1,155 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010-2018 Google LLC\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"Solves a simple shift scheduling problem.\"\"\"\n", + "\n", + "from __future__ import absolute_import\n", + "from __future__ import division\n", + "from __future__ import print_function\n", + "\n", + "from ortools.sat.python import cp_model\n", + "\n", + "\n", + "class SolutionPrinter(cp_model.CpSolverSolutionCallback):\n", + " \"\"\"Print intermediate solutions.\"\"\"\n", + "\n", + " def __init__(self, num_vendors, num_hours, possible_schedules,\n", + " selected_schedules, hours_stat, min_vendors):\n", + " cp_model.CpSolverSolutionCallback.__init__(self)\n", + " self.__solution_count = 0\n", + " self.__num_vendors = num_vendors\n", + " self.__num_hours = num_hours\n", + " self.__possible_schedules = possible_schedules\n", + " self.__selected_schedules = selected_schedules\n", + " self.__hours_stat = hours_stat\n", + " self.__min_vendors = min_vendors\n", + "\n", + " def on_solution_callback(self):\n", + " \"\"\"Called at each new solution.\"\"\"\n", + " self.__solution_count += 1\n", + " print('Solution %i: ', self.__solution_count)\n", + " print(' min vendors:', self.__min_vendors)\n", + " for i in range(self.__num_vendors):\n", + " print(' - vendor %i: ' % i, self.__possible_schedules[self.Value(\n", + " self.__selected_schedules[i])])\n", + " print()\n", + "\n", + " for j in range(self.__num_hours):\n", + " print(' - # workers on day%2i: ' % j, end=' ')\n", + " print(self.Value(self.__hours_stat[j]), end=' ')\n", + " print()\n", + " print()\n", + "\n", + " def solution_count(self):\n", + " \"\"\"Returns the number of solution found.\"\"\"\n", + " return self.__solution_count\n", + "\n", + "\n", + "\"\"\"Create the shift scheduling model and solve it.\"\"\"\n", + "# Create the model.\n", + "model = cp_model.CpModel()\n", + "\n", + "#\n", + "# data\n", + "#\n", + "num_vendors = 9\n", + "num_hours = 10\n", + "num_work_types = 1\n", + "\n", + "traffic = [100, 500, 100, 200, 320, 300, 200, 220, 300, 120]\n", + "max_traffic_per_vendor = 100\n", + "\n", + "# Last columns are :\n", + "# index_of_the_schedule, sum of worked hours (per work type).\n", + "# The index is useful for branching.\n", + "possible_schedules = [[1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0,\n", + " 8], [1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1,\n", + " 4], [0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 2,\n", + " 5], [0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 3, 4],\n", + " [1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 4,\n", + " 3], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 0]]\n", + "\n", + "num_possible_schedules = len(possible_schedules)\n", + "selected_schedules = []\n", + "vendors_stat = []\n", + "hours_stat = []\n", + "\n", + "# Auxiliary data\n", + "min_vendors = [t // max_traffic_per_vendor for t in traffic]\n", + "all_vendors = range(num_vendors)\n", + "all_hours = range(num_hours)\n", + "\n", + "#\n", + "# declare variables\n", + "#\n", + "x = {}\n", + "\n", + "for v in all_vendors:\n", + " tmp = []\n", + " for h in all_hours:\n", + " x[v, h] = model.NewIntVar(0, num_work_types, 'x[%i,%i]' % (v, h))\n", + " tmp.append(x[v, h])\n", + " selected_schedule = model.NewIntVar(0, num_possible_schedules - 1,\n", + " 's[%i]' % v)\n", + " hours = model.NewIntVar(0, num_hours, 'h[%i]' % v)\n", + " selected_schedules.append(selected_schedule)\n", + " vendors_stat.append(hours)\n", + " tmp.append(selected_schedule)\n", + " tmp.append(hours)\n", + "\n", + " model.AddAllowedAssignments(tmp, possible_schedules)\n", + "\n", + "#\n", + "# Statistics and constraints for each hour\n", + "#\n", + "for h in all_hours:\n", + " workers = model.NewIntVar(0, 1000, 'workers[%i]' % h)\n", + " model.Add(workers == sum(x[v, h] for v in all_vendors))\n", + " hours_stat.append(workers)\n", + " model.Add(workers * max_traffic_per_vendor >= traffic[h])\n", + "\n", + "#\n", + "# Redundant constraint: sort selected_schedules\n", + "#\n", + "for v in range(num_vendors - 1):\n", + " model.Add(selected_schedules[v] <= selected_schedules[v + 1])\n", + "\n", + "# Solve model.\n", + "solver = cp_model.CpSolver()\n", + "solution_printer = SolutionPrinter(num_vendors, num_hours,\n", + " possible_schedules, selected_schedules,\n", + " hours_stat, min_vendors)\n", + "status = solver.SearchForAllSolutions(model, solution_printer)\n", + "print('Status = %s' % solver.StatusName(status))\n", + "\n", + "print('Statistics')\n", + "print(' - conflicts : %i' % solver.NumConflicts())\n", + "print(' - branches : %i' % solver.NumBranches())\n", + "print(' - wall time : %f s' % solver.WallTime())\n", + "print(\n", + " ' - number of solutions found: %i' % solution_printer.solution_count())\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/examples/wedding_optimal_chart_sat.ipynb b/examples/notebook/examples/wedding_optimal_chart_sat.ipynb new file mode 100644 index 0000000000..450fb81b8c --- /dev/null +++ b/examples/notebook/examples/wedding_optimal_chart_sat.ipynb @@ -0,0 +1,226 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "#\n", + "# Copyright 2018 Google.\n", + "#\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"Finding an optimal wedding seating chart.\n", + "\n", + "From\n", + "Meghan L. Bellows and J. D. Luc Peterson\n", + "\"Finding an optimal seating chart for a wedding\"\n", + "http://www.improbable.com/news/2012/Optimal-seating-chart.pdf\n", + "http://www.improbable.com/2012/02/12/finding-an-optimal-seating-chart-for-a-wedding\n", + "\n", + "Every year, millions of brides (not to mention their mothers, future\n", + "mothers-in-law, and occasionally grooms) struggle with one of the\n", + "most daunting tasks during the wedding-planning process: the\n", + "seating chart. The guest responses are in, banquet hall is booked,\n", + "menu choices have been made. You think the hard parts are over,\n", + "but you have yet to embark upon the biggest headache of them all.\n", + "In order to make this process easier, we present a mathematical\n", + "formulation that models the seating chart problem. This model can\n", + "be solved to find the optimal arrangement of guests at tables.\n", + "At the very least, it can provide a starting point and hopefully\n", + "minimize stress and arguments.\n", + "\n", + "Adapted from\n", + "https://github.com/google/or-tools/blob/master/examples/csharp/wedding_optimal_chart.cs\n", + "\"\"\"\n", + "\n", + "from __future__ import print_function\n", + "import time\n", + "from ortools.sat.python import cp_model\n", + "\n", + "\n", + "class WeddingChartPrinter(cp_model.CpSolverSolutionCallback):\n", + " \"\"\"Print intermediate solutions.\"\"\"\n", + "\n", + " def __init__(self, seats, names, num_tables, num_guests):\n", + " cp_model.CpSolverSolutionCallback.__init__(self)\n", + " self.__solution_count = 0\n", + " self.__start_time = time.time()\n", + " self.__seats = seats\n", + " self.__names = names\n", + " self.__num_tables = num_tables\n", + " self.__num_guests = num_guests\n", + "\n", + " def on_solution_callback(self):\n", + " current_time = time.time()\n", + " objective = self.ObjectiveValue()\n", + " print(\"Solution %i, time = %f s, objective = %i\" %\n", + " (self.__solution_count, current_time - self.__start_time,\n", + " objective))\n", + " self.__solution_count += 1\n", + "\n", + " for t in range(self.__num_tables):\n", + " print(\"Table %d: \" % t)\n", + " for g in range(self.__num_guests):\n", + " if self.Value(self.__seats[(t, g)]):\n", + " print(\" \" + self.__names[g])\n", + "\n", + " def num_solutions(self):\n", + " return self.__solution_count\n", + "\n", + "\n", + "def BuildData():\n", + " #\n", + " # Data\n", + " #\n", + "\n", + " # Easy problem (from the paper)\n", + " # num_tables = 2\n", + " # table_capacity = 10\n", + " # min_known_neighbors = 1\n", + "\n", + " # Slightly harder problem (also from the paper)\n", + " num_tables = 5\n", + " table_capacity = 4\n", + " min_known_neighbors = 1\n", + "\n", + " # Connection matrix: who knows who, and how strong\n", + " # is the relation\n", + " C = [[1, 50, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0,\n", + " 0], [50, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0,\n", + " 0], [1, 1, 1, 50, 1, 1, 1, 1, 10, 0, 0, 0, 0, 0, 0, 0,\n", + " 0], [1, 1, 50, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0],\n", + " [1, 1, 1, 1, 1, 50, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0,\n", + " 0], [1, 1, 1, 1, 50, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0,\n", + " 0], [1, 1, 1, 1, 1, 1, 1, 50, 1, 0, 0, 0, 0, 0, 0, 0, 0],\n", + " [1, 1, 1, 1, 1, 1, 50, 1, 1, 0, 0, 0, 0, 0, 0, 0,\n", + " 0], [1, 1, 10, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0,\n", + " 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 50, 1, 1, 1, 1, 1, 1],\n", + " [0, 0, 0, 0, 0, 0, 0, 0, 0, 50, 1, 1, 1, 1, 1, 1,\n", + " 1], [0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1,\n", + " 1], [0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1], [\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1\n", + " ], [0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1], [\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1\n", + " ], [0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1]]\n", + "\n", + " # Names of the guests. B: Bride side, G: Groom side\n", + " names = [\n", + " \"Deb (B)\", \"John (B)\", \"Martha (B)\", \"Travis (B)\", \"Allan (B)\",\n", + " \"Lois (B)\", \"Jayne (B)\", \"Brad (B)\", \"Abby (B)\", \"Mary Helen (G)\",\n", + " \"Lee (G)\", \"Annika (G)\", \"Carl (G)\", \"Colin (G)\", \"Shirley (G)\",\n", + " \"DeAnn (G)\", \"Lori (G)\"\n", + " ]\n", + " return num_tables, table_capacity, min_known_neighbors, C, names\n", + "\n", + "\n", + "def solve_with_discrete_model():\n", + " num_tables, table_capacity, min_known_neighbors, C, names = BuildData()\n", + "\n", + " num_guests = len(C)\n", + "\n", + " all_tables = range(num_tables)\n", + " all_guests = range(num_guests)\n", + "\n", + " # Create the cp model.\n", + " model = cp_model.CpModel()\n", + "\n", + " #\n", + " # Decision variables\n", + " #\n", + " seats = {}\n", + " for t in all_tables:\n", + " for g in all_guests:\n", + " seats[(t, g)] = model.NewBoolVar(\"guest %i seats on table %i\" % (g,\n", + " t))\n", + "\n", + " colocated = {}\n", + " for g1 in range(num_guests - 1):\n", + " for g2 in range(g1 + 1, num_guests):\n", + " colocated[(g1, g2)] = model.NewBoolVar(\n", + " \"guest %i seats with guest %i\" % (g1, g2))\n", + "\n", + " same_table = {}\n", + " for g1 in range(num_guests - 1):\n", + " for g2 in range(g1 + 1, num_guests):\n", + " for t in all_tables:\n", + " same_table[(g1, g2, t)] = model.NewBoolVar(\n", + " \"guest %i seats with guest %i on table %i\" % (g1, g2, t))\n", + "\n", + " # Objective\n", + " model.Maximize(\n", + " sum(C[g1][g2] * colocated[g1, g2]\n", + " for g1 in range(num_guests - 1) for g2 in range(g1 + 1, num_guests)\n", + " if C[g1][g2] > 0))\n", + "\n", + " #\n", + " # Constraints\n", + " #\n", + "\n", + " # Everybody seats at one table.\n", + " for g in all_guests:\n", + " model.Add(sum(seats[(t, g)] for t in all_tables) == 1)\n", + "\n", + " # Tables have a max capacity.\n", + " for t in all_tables:\n", + " model.Add(sum(seats[(t, g)] for g in all_guests) <= table_capacity)\n", + "\n", + " # Link colocated with seats\n", + " for g1 in range(num_guests - 1):\n", + " for g2 in range(g1 + 1, num_guests):\n", + " for t in all_tables:\n", + " # Link same_table and seats.\n", + " model.AddBoolOr([\n", + " seats[(t, g1)].Not(), seats[(t, g2)].Not(),\n", + " same_table[(g1, g2, t)]\n", + " ])\n", + " model.AddImplication(same_table[(g1, g2, t)], seats[(t, g1)])\n", + " model.AddImplication(same_table[(g1, g2, t)], seats[(t, g2)])\n", + "\n", + " # Link colocated and same_table.\n", + " model.Add(\n", + " sum(same_table[(g1, g2, t)]\n", + " for t in all_tables) == colocated[(g1, g2)])\n", + "\n", + " # Min known neighbors rule.\n", + " for t in all_tables:\n", + " model.Add(\n", + " sum(same_table[(g1, g2, t)]\n", + " for g1 in range(num_guests - 1)\n", + " for g2 in range(g1 + 1, num_guests) for t in all_tables\n", + " if C[g1][g2] > 0) >= min_known_neighbors)\n", + "\n", + " # Symmetry breaking. First guest seats on the first table.\n", + " model.Add(seats[(0, 0)] == 1)\n", + "\n", + " ### Solve model.\n", + " solver = cp_model.CpSolver()\n", + " solution_printer = WeddingChartPrinter(seats, names, num_tables, num_guests)\n", + " solver.SolveWithSolutionCallback(model, solution_printer)\n", + "\n", + " print(\"Statistics\")\n", + " print(\" - conflicts : %i\" % solver.NumConflicts())\n", + " print(\" - branches : %i\" % solver.NumBranches())\n", + " print(\" - wall time : %f s\" % solver.WallTime())\n", + " print(\" - num solutions: %i\" % solution_printer.num_solutions())\n", + "\n", + "\n", + "solve_with_discrete_model()\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/examples/worker_schedule_sat.ipynb b/examples/notebook/examples/worker_schedule_sat.ipynb new file mode 100644 index 0000000000..48ff63b407 --- /dev/null +++ b/examples/notebook/examples/worker_schedule_sat.ipynb @@ -0,0 +1,172 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010-2018 Google LLC\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "from __future__ import print_function\n", + "\n", + "from ortools.sat.python import cp_model\n", + "\n", + "\n", + "def schedule():\n", + "\n", + " # Input data.\n", + " positions = [\n", + " 1, 2, 8, 10, 5, 3, 4, 3, 6, 6, 4, 5, 4, 3, 4, 4, 3, 4, 2, 1, 0, 0, 0, 0,\n", + " 1, 2, 9, 9, 4, 3, 4, 3, 5, 4, 5, 2, 5, 6, 6, 7, 4, 2, 1, 0, 0, 0, 0, 0,\n", + " 0, 2, 7, 6, 5, 2, 4, 4, 6, 6, 4, 5, 5, 5, 7, 5, 4, 4, 2, 3, 1, 0, 0, 0,\n", + " 1, 2, 9, 7, 2, 2, 4, 2, 4, 5, 3, 2, 6, 7, 5, 6, 4, 4, 2, 1, 0, 0, 0, 0,\n", + " 2, 2, 8, 8, 6, 3, 3, 3, 10, 9, 6, 3, 3, 4, 5, 4, 5, 4, 2, 1, 0, 0, 0, 0,\n", + " 1, 2, 9, 5, 5, 4, 5, 2, 5, 7, 5, 3, 4, 8, 4, 4, 2, 3, 1, 0, 0, 0, 0, 0,\n", + " 1, 2, 10, 5, 5, 4, 5, 2, 4, 6, 7, 4, 4, 5, 4, 4, 3, 3, 2, 1, 0, 0, 0, 0\n", + " ]\n", + "\n", + " possible_shifts = [[\n", + " 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 40\n", + " ], [\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 1, 40\n", + " ], [\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 2, 40\n", + " ], [\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1,\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1,\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1,\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1,\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1,\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 3, 40\n", + " ], [\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 4, 0\n", + " ], [\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 5, 16\n", + " ], [\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 6, 16\n", + " ], [\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1,\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1,\n", + " 7, 16\n", + " ], [\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1,\n", + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1,\n", + " 8, 40\n", + " ]]\n", + "\n", + " # Useful numbers.\n", + " num_slots = len(positions)\n", + " all_slots = range(num_slots)\n", + "\n", + " num_shifts = len(possible_shifts)\n", + " all_shifts = range(num_shifts)\n", + "\n", + " min_number_of_workers = [5 * x for x in positions]\n", + " num_workers = 300\n", + "\n", + " # Model the problem.\n", + " model = cp_model.CpModel()\n", + "\n", + " workers_per_shift = [\n", + " model.NewIntVar(0, num_workers, 'shift[%i]' % i) for i in all_shifts\n", + " ]\n", + "\n", + " # Satisfy min requirements.\n", + " for slot in all_slots:\n", + " model.Add(\n", + " sum(workers_per_shift[shift] * possible_shifts[shift][slot]\n", + " for shift in all_shifts) >= min_number_of_workers[slot])\n", + "\n", + " # Create the objective variable.\n", + " objective = model.NewIntVar(0, num_workers, 'objective')\n", + "\n", + " # Link the objective.\n", + " model.Add(sum(workers_per_shift) == objective)\n", + "\n", + " # Minimize.\n", + " model.Minimize(objective)\n", + "\n", + " solver = cp_model.CpSolver()\n", + " status = solver.Solve(model)\n", + "\n", + " if status == cp_model.OPTIMAL:\n", + " print('Objective value = %i' % solver.ObjectiveValue())\n", + "\n", + " print('Statistics')\n", + " print(' - conflicts : %i' % solver.NumConflicts())\n", + " print(' - branches : %i' % solver.NumBranches())\n", + " print(' - wall time : %f s' % solver.WallTime())\n", + "\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/examples/zebra_sat.ipynb b/examples/notebook/examples/zebra_sat.ipynb new file mode 100644 index 0000000000..12f6230a96 --- /dev/null +++ b/examples/notebook/examples/zebra_sat.ipynb @@ -0,0 +1,143 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010-2018 Google LLC\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"This is the zebra problem as invented by Lewis Caroll.\n", + "\n", + "There are five houses.\n", + "The Englishman lives in the red house.\n", + "The Spaniard owns the dog.\n", + "Coffee is drunk in the green house.\n", + "The Ukrainian drinks tea.\n", + "The green house is immediately to the right of the ivory house.\n", + "The Old Gold smoker owns snails.\n", + "Kools are smoked in the yellow house.\n", + "Milk is drunk in the middle house.\n", + "The Norwegian lives in the first house.\n", + "The man who smokes Chesterfields lives in the house next to the man\n", + " with the fox.\n", + "Kools are smoked in the house next to the house where the horse is kept.\n", + "The Lucky Strike smoker drinks orange juice.\n", + "The Japanese smokes Parliaments.\n", + "The Norwegian lives next to the blue house.\n", + "\n", + "Who owns a zebra and who drinks water?\n", + "\"\"\"\n", + "from __future__ import print_function\n", + "\n", + "from ortools.sat.python import cp_model\n", + "\n", + "\n", + "# pylint: disable=too-many-statements\n", + "def solve_zebra():\n", + " \"\"\"Solves the zebra problem.\"\"\"\n", + "\n", + " # Create the model.\n", + " model = cp_model.CpModel()\n", + "\n", + " red = model.NewIntVar(1, 5, 'red')\n", + " green = model.NewIntVar(1, 5, 'green')\n", + " yellow = model.NewIntVar(1, 5, 'yellow')\n", + " blue = model.NewIntVar(1, 5, 'blue')\n", + " ivory = model.NewIntVar(1, 5, 'ivory')\n", + "\n", + " englishman = model.NewIntVar(1, 5, 'englishman')\n", + " spaniard = model.NewIntVar(1, 5, 'spaniard')\n", + " japanese = model.NewIntVar(1, 5, 'japanese')\n", + " ukrainian = model.NewIntVar(1, 5, 'ukrainian')\n", + " norwegian = model.NewIntVar(1, 5, 'norwegian')\n", + "\n", + " dog = model.NewIntVar(1, 5, 'dog')\n", + " snails = model.NewIntVar(1, 5, 'snails')\n", + " fox = model.NewIntVar(1, 5, 'fox')\n", + " zebra = model.NewIntVar(1, 5, 'zebra')\n", + " horse = model.NewIntVar(1, 5, 'horse')\n", + "\n", + " tea = model.NewIntVar(1, 5, 'tea')\n", + " coffee = model.NewIntVar(1, 5, 'coffee')\n", + " water = model.NewIntVar(1, 5, 'water')\n", + " milk = model.NewIntVar(1, 5, 'milk')\n", + " fruit_juice = model.NewIntVar(1, 5, 'fruit juice')\n", + "\n", + " old_gold = model.NewIntVar(1, 5, 'old gold')\n", + " kools = model.NewIntVar(1, 5, 'kools')\n", + " chesterfields = model.NewIntVar(1, 5, 'chesterfields')\n", + " lucky_strike = model.NewIntVar(1, 5, 'lucky strike')\n", + " parliaments = model.NewIntVar(1, 5, 'parliaments')\n", + "\n", + " model.AddAllDifferent([red, green, yellow, blue, ivory])\n", + " model.AddAllDifferent(\n", + " [englishman, spaniard, japanese, ukrainian, norwegian])\n", + " model.AddAllDifferent([dog, snails, fox, zebra, horse])\n", + " model.AddAllDifferent([tea, coffee, water, milk, fruit_juice])\n", + " model.AddAllDifferent(\n", + " [parliaments, kools, chesterfields, lucky_strike, old_gold])\n", + "\n", + " model.Add(englishman == red)\n", + " model.Add(spaniard == dog)\n", + " model.Add(coffee == green)\n", + " model.Add(ukrainian == tea)\n", + " model.Add(green == ivory + 1)\n", + " model.Add(old_gold == snails)\n", + " model.Add(kools == yellow)\n", + " model.Add(milk == 3)\n", + " model.Add(norwegian == 1)\n", + "\n", + " diff_fox_chesterfields = model.NewIntVar(-4, 4, 'diff_fox_chesterfields')\n", + " model.Add(diff_fox_chesterfields == fox - chesterfields)\n", + " model.AddAbsEquality(1, diff_fox_chesterfields)\n", + "\n", + " diff_horse_kools = model.NewIntVar(-4, 4, 'diff_horse_kools')\n", + " model.Add(diff_horse_kools == horse - kools)\n", + " model.AddAbsEquality(1, diff_horse_kools)\n", + "\n", + " model.Add(lucky_strike == fruit_juice)\n", + " model.Add(japanese == parliaments)\n", + "\n", + " diff_norwegian_blue = model.NewIntVar(-4, 4, 'diff_norwegian_blue')\n", + " model.Add(diff_norwegian_blue == norwegian - blue)\n", + " model.AddAbsEquality(1, diff_norwegian_blue)\n", + "\n", + " # Solve and print out the solution.\n", + " solver = cp_model.CpSolver()\n", + " status = solver.Solve(model)\n", + "\n", + " if status == cp_model.FEASIBLE:\n", + " people = [englishman, spaniard, japanese, ukrainian, norwegian]\n", + " water_drinker = [\n", + " p for p in people if solver.Value(p) == solver.Value(water)\n", + " ][0]\n", + " zebra_owner = [\n", + " p for p in people if solver.Value(p) == solver.Value(zebra)\n", + " ][0]\n", + " print('The', water_drinker.Name(), 'drinks water.')\n", + " print('The', zebra_owner.Name(), 'owns the zebra.')\n", + " else:\n", + " print('No solutions to the zebra problem, this is unusual!')\n", + "\n", + "\n", + "solve_zebra()\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/flexible_job_shop_sat.ipynb b/examples/notebook/flexible_job_shop_sat.ipynb deleted file mode 100644 index 3ec1cd703b..0000000000 --- a/examples/notebook/flexible_job_shop_sat.ipynb +++ /dev/null @@ -1,187 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Copyright 2010-2017 Google\n", - "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", - "# you may not use this file except in compliance with the License.\n", - "# You may obtain a copy of the License at\n", - "#\n", - "# http://www.apache.org/licenses/LICENSE-2.0\n", - "#\n", - "# Unless required by applicable law or agreed to in writing, software\n", - "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", - "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", - "# See the License for the specific language governing permissions and\n", - "# limitations under the License.\n", - "\n", - "from __future__ import print_function\n", - "\n", - "from collections import defaultdict\n", - "from ortools.sat.python import cp_model\n", - "\n", - "\n", - "class SolutionPrinter(cp_model.CpSolverSolutionCallback):\n", - " \"\"\"Print intermediate solutions.\"\"\"\n", - "\n", - " def __init__(self):\n", - " cp_model.CpSolverSolutionCallback.__init__(self)\n", - " self.__solution_count = 0\n", - "\n", - " def OnSolutionCallback(self):\n", - " print('Solution %i, time = %f s, objective = %i' %\n", - " (self.__solution_count, self.WallTime(), self.ObjectiveValue()))\n", - " self.__solution_count += 1\n", - "\n", - "\n", - "# Data part.\n", - "jobs = [[[(3, 0), (1, 1), (5, 2)], [(2, 0), (4, 1), (6, 2)], [(2, 0), (3, 1),\n", - " (1, 2)]],\n", - " [[(2, 0), (3, 1), (4, 2)], [(1, 0), (5, 1), (4, 2)],\n", - " [(2, 0), (1, 1), (4, 2)]], [[(2, 0), (1, 1), (4, 2)],\n", - " [(2, 0), (3, 1), (4, 2)], [(3, 0), (1, 1),\n", - " (5, 2)]]]\n", - "\n", - "num_jobs = len(jobs)\n", - "all_jobs = range(num_jobs)\n", - "\n", - "num_machines = 3\n", - "all_machines = range(num_machines)\n", - "\n", - "# Model the flexible jobshop problem.\n", - "model = cp_model.CpModel()\n", - "\n", - "horizon = 0\n", - "for job in jobs:\n", - " for task in job:\n", - " max_task_duration = 0\n", - " for alternative in task:\n", - " max_task_duration = max(max_task_duration, alternative[0])\n", - " horizon += max_task_duration\n", - "\n", - "print('Horizon = %i' % horizon)\n", - "\n", - "# Global storage of variables.\n", - "intervals_per_resources = defaultdict(list)\n", - "starts = {} # indexed by (job_id, task_id).\n", - "presences = {} # indexed by (job_id, task_id, alt_id).\n", - "job_ends = []\n", - "\n", - "# Scan the jobs and create the relevant variables and intervals.\n", - "for job_id in all_jobs:\n", - " job = jobs[job_id]\n", - " num_tasks = len(job)\n", - " previous_end = None\n", - " for task_id in range(num_tasks):\n", - " task = job[task_id]\n", - "\n", - " min_duration = task[0][0]\n", - " max_duration = task[0][0]\n", - "\n", - " num_alternatives = len(task)\n", - " all_alternatives = range(num_alternatives)\n", - "\n", - " for alt_id in range(1, num_alternatives):\n", - " alt_duration = task[alt_id][0]\n", - " min_duration = min(min_duration, alt_duration)\n", - " max_duration = max(max_duration, alt_duration)\n", - "\n", - " # Create main interval for the task.\n", - " suffix_name = '_j%i_t%i' % (job_id, task_id)\n", - " start = model.NewIntVar(0, horizon, 'start' + suffix_name)\n", - " duration = model.NewIntVar(min_duration, max_duration,\n", - " 'duration' + suffix_name)\n", - " end = model.NewIntVar(0, horizon, 'end' + suffix_name)\n", - " interval = model.NewIntervalVar(start, duration, end,\n", - " 'interval' + suffix_name)\n", - "\n", - " # Store the start for the solution.\n", - " starts[(job_id, task_id)] = start\n", - "\n", - " # Add precedence with previous task in the same job.\n", - " if previous_end:\n", - " model.Add(start >= previous_end)\n", - " previous_end = end\n", - "\n", - " # Create alternative intervals.\n", - " if num_alternatives > 1:\n", - " l_presences = []\n", - " for alt_id in all_alternatives:\n", - " alt_suffix = '_j%i_t%i_a%i' % (job_id, task_id, alt_id)\n", - " l_presence = model.NewBoolVar('presence' + alt_suffix)\n", - " l_start = model.NewIntVar(0, horizon, 'start' + alt_suffix)\n", - " l_duration = task[alt_id][0]\n", - " l_end = model.NewIntVar(0, horizon, 'end' + alt_suffix)\n", - " l_interval = model.NewOptionalIntervalVar(\n", - " l_start, l_duration, l_end, l_presence, 'interval' + alt_suffix)\n", - " l_presences.append(l_presence)\n", - "\n", - " # Link the master variables with the local ones.\n", - " model.Add(start == l_start).OnlyEnforceIf(l_presence)\n", - " model.Add(duration == l_duration).OnlyEnforceIf(l_presence)\n", - " model.Add(end == l_end).OnlyEnforceIf(l_presence)\n", - "\n", - " # Add the local interval to the right machine.\n", - " intervals_per_resources[task[alt_id][1]].append(l_interval)\n", - "\n", - " # Store the presences for the solution.\n", - " presences[(job_id, task_id, alt_id)] = l_presence\n", - "\n", - " # Select exactly one presence variable.\n", - " model.Add(sum(l_presences) == 1)\n", - " else:\n", - " intervals_per_resources[task[0][1]].append(interval)\n", - " presences[(job_id, task_id, 0)] = model.NewConstant(1)\n", - "\n", - " job_ends.append(previous_end)\n", - "\n", - "# Create machines constraints.\n", - "for machine_id in all_machines:\n", - " intervals = intervals_per_resources[machine_id]\n", - " if len(intervals) > 1:\n", - " model.AddNoOverlap(intervals)\n", - "\n", - "# Makespan objective\n", - "makespan = model.NewIntVar(0, horizon, 'makespan')\n", - "model.AddMaxEquality(makespan, job_ends)\n", - "model.Minimize(makespan)\n", - "\n", - "# Solve model.\n", - "solver = cp_model.CpSolver()\n", - "solution_printer = SolutionPrinter()\n", - "status = solver.SolveWithSolutionCallback(model, solution_printer)\n", - "\n", - "# Print final solution.\n", - "for job_id in all_jobs:\n", - " print('Job %i:' % job_id)\n", - " for task_id in range(len(jobs[job_id])):\n", - " start_value = solver.Value(starts[(job_id, task_id)])\n", - " machine = -1\n", - " duration = -1\n", - " selected = -1\n", - " for alt_id in range(len(jobs[job_id][task_id])):\n", - " if solver.Value(presences[(job_id, task_id, alt_id)]):\n", - " duration = jobs[job_id][task_id][alt_id][0]\n", - " machine = jobs[job_id][task_id][alt_id][1]\n", - " selected = alt_id\n", - " print(' task_%i_%i starts at %i (alt %i, machine %i, duration %i)' %\n", - " (job_id, task_id, start_value, selected, machine, duration))\n", - "\n", - "print('Solve status: %s' % solver.StatusName(status))\n", - "print('Optimal objective value: %i' % solver.ObjectiveValue())\n", - "print('Statistics')\n", - "print(' - conflicts : %i' % solver.NumConflicts())\n", - "print(' - branches : %i' % solver.NumBranches())\n", - "print(' - wall time : %f s' % solver.WallTime())\n", - "\n" - ] - } - ], - "metadata": {}, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/examples/notebook/gate_scheduling_sat.ipynb b/examples/notebook/gate_scheduling_sat.ipynb deleted file mode 100644 index 4560388206..0000000000 --- a/examples/notebook/gate_scheduling_sat.ipynb +++ /dev/null @@ -1,145 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Copyright 2010-2017 Google\n", - "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", - "# you may not use this file except in compliance with the License.\n", - "# You may obtain a copy of the License at\n", - "#\n", - "# http://www.apache.org/licenses/LICENSE-2.0\n", - "#\n", - "# Unless required by applicable law or agreed to in writing, software\n", - "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", - "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", - "# See the License for the specific language governing permissions and\n", - "# limitations under the License.\n", - "\"\"\"Gate Scheduling problem.\n", - "\n", - "We have a set of jobs to perform (duration, width).\n", - "We have two parallel machines that can perform this job.\n", - "One machine can only perform one job at a time.\n", - "At any point in time, the sum of the width of the two active jobs does not\n", - "exceed a max_length.\n", - "\n", - "The objective is to minimize the max end time of all jobs.\n", - "\"\"\"\n", - "\n", - "from ortools.sat.python import cp_model\n", - "from ortools.sat.python import visualization\n", - "\n", - "\n", - "model = cp_model.CpModel()\n", - "\n", - "jobs = [[3, 3], [2, 5], [1, 3], [3, 7], [7, 3], [2, 2], [2, 2], [5, 5],\n", - " [10, 2], [4, 3], [2, 6], [1, 2], [6, 8], [4, 5], [3, 7]]\n", - "\n", - "max_length = 10\n", - "\n", - "horizon = sum(t[0] for t in jobs)\n", - "num_jobs = len(jobs)\n", - "all_jobs = range(num_jobs)\n", - "\n", - "intervals = []\n", - "intervals0 = []\n", - "intervals1 = []\n", - "performed = []\n", - "starts = []\n", - "ends = []\n", - "demands = []\n", - "\n", - "for i in all_jobs:\n", - " # Create main interval.\n", - " start = model.NewIntVar(0, horizon, 'start_%i' % i)\n", - " duration = jobs[i][0]\n", - " end = model.NewIntVar(0, horizon, 'end_%i' % i)\n", - " interval = model.NewIntervalVar(start, duration, end, 'interval_%i' % i)\n", - " starts.append(start)\n", - " intervals.append(interval)\n", - " ends.append(end)\n", - " demands.append(jobs[i][1])\n", - "\n", - " performed_on_m0 = model.NewBoolVar('perform_%i_on_m0' % i)\n", - " performed.append(performed_on_m0)\n", - "\n", - " # Create an optional copy of interval to be executed on machine 0.\n", - " start0 = model.NewIntVar(0, horizon, 'start_%i_on_m0' % i)\n", - " end0 = model.NewIntVar(0, horizon, 'end_%i_on_m0' % i)\n", - " interval0 = model.NewOptionalIntervalVar(\n", - " start0, duration, end0, performed_on_m0, 'interval_%i_on_m0' % i)\n", - " intervals0.append(interval0)\n", - "\n", - " # Create an optional copy of interval to be executed on machine 1.\n", - " start1 = model.NewIntVar(0, horizon, 'start_%i_on_m1' % i)\n", - " end1 = model.NewIntVar(0, horizon, 'end_%i_on_m1' % i)\n", - " interval1 = model.NewOptionalIntervalVar(start1, duration, end1,\n", - " performed_on_m0.Not(),\n", - " 'interval_%i_on_m1' % i)\n", - " intervals1.append(interval1)\n", - "\n", - " # We only propagate the constraint if the tasks is performed on the machine.\n", - " model.Add(start0 == start).OnlyEnforceIf(performed_on_m0)\n", - " model.Add(start1 == start).OnlyEnforceIf(performed_on_m0.Not())\n", - "\n", - "# Max Length constraint (modeled as a cumulative)\n", - "model.AddCumulative(intervals, demands, max_length)\n", - "\n", - "# Choose which machine to perform the jobs on.\n", - "model.AddNoOverlap(intervals0)\n", - "model.AddNoOverlap(intervals1)\n", - "\n", - "# Objective variable.\n", - "makespan = model.NewIntVar(0, horizon, 'makespan')\n", - "model.AddMaxEquality(makespan, ends)\n", - "model.Minimize(makespan)\n", - "\n", - "# Symmetry breaking.\n", - "model.Add(performed[0] == 0)\n", - "\n", - "# Solve model.\n", - "solver = cp_model.CpSolver()\n", - "solver.Solve(model)\n", - "\n", - "# Output solution.\n", - "if visualization.RunFromIPython():\n", - " output = visualization.SvgWrapper(solver.ObjectiveValue(), max_length, 40.0)\n", - " output.AddTitle('Makespan = %i' % solver.ObjectiveValue())\n", - " color_manager = visualization.ColorManager()\n", - " color_manager.SeedRandomColor(0)\n", - "\n", - " for i in all_jobs:\n", - " performed_machine = 1 - solver.Value(performed[i])\n", - " start = solver.Value(starts[i])\n", - " dx = jobs[i][0]\n", - " dy = jobs[i][1]\n", - " sy = performed_machine * (max_length - dy)\n", - " output.AddRectangle(start, sy, dx, dy, color_manager.RandomColor(),\n", - " 'black', 'j%i' % i)\n", - "\n", - " output.AddXScale()\n", - " output.AddYScale()\n", - " output.Display()\n", - "else:\n", - " print('Solution')\n", - " print(' - makespan = %i' % solver.ObjectiveValue())\n", - " for i in all_jobs:\n", - " performed_machine = 1 - solver.Value(performed[i])\n", - " start = solver.Value(starts[i])\n", - " print(' - Job %i starts at %i on machine %i' % (i, start,\n", - " performed_machine))\n", - " print('Statistics')\n", - " print(' - conflicts : %i' % solver.NumConflicts())\n", - " print(' - branches : %i' % solver.NumBranches())\n", - " print(' - wall time : %f ms' % solver.WallTime())\n", - "\n" - ] - } - ], - "metadata": {}, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/examples/notebook/graph/simple_max_flow_program.ipynb b/examples/notebook/graph/simple_max_flow_program.ipynb new file mode 100644 index 0000000000..57e30ba477 --- /dev/null +++ b/examples/notebook/graph/simple_max_flow_program.ipynb @@ -0,0 +1,71 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010-2018 Google LLC\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "# [START program]\n", + "\"\"\"From Taha 'Introduction to Operations Research', example 6.4-2.\"\"\"\n", + "# [START import]\n", + "from __future__ import print_function\n", + "from ortools.graph import pywrapgraph\n", + "# [END import]\n", + "\n", + "\n", + "\"\"\"MaxFlow simple interface example.\"\"\"\n", + "\n", + "# [START data]\n", + "# Define three parallel arrays: start_nodes, end_nodes, and the capacities\n", + "# between each pair. For instance, the arc from node 0 to node 1 has a\n", + "# capacity of 20.\n", + "\n", + "start_nodes = [0, 0, 0, 1, 1, 2, 2, 3, 3]\n", + "end_nodes = [1, 2, 3, 2, 4, 3, 4, 2, 4]\n", + "capacities = [20, 30, 10, 40, 30, 10, 20, 5, 20]\n", + "# [END data]\n", + "\n", + "# Instantiate a SimpleMaxFlow solver.\n", + "# [START constraints]\n", + "max_flow = pywrapgraph.SimpleMaxFlow()\n", + "# Add each arc.\n", + "for arc in zip(start_nodes, end_nodes, capacities):\n", + " max_flow.AddArcWithCapacity(arc[0], arc[1], arc[2])\n", + "# [END constraints]\n", + "\n", + "# [START solve]\n", + "# Find the maximum flow between node 0 and node 4.\n", + "if max_flow.Solve(0, 4) == max_flow.OPTIMAL:\n", + " print('Max flow:', max_flow.OptimalFlow())\n", + " print('')\n", + " print(' Arc Flow / Capacity')\n", + " for i in range(max_flow.NumArcs()):\n", + " print('%1s -> %1s %3s / %3s' %\n", + " (max_flow.Tail(i), max_flow.Head(i), max_flow.Flow(i),\n", + " max_flow.Capacity(i)))\n", + " print('Source side min-cut:', max_flow.GetSourceSideMinCut())\n", + " print('Sink side min-cut:', max_flow.GetSinkSideMinCut())\n", + "else:\n", + " print('There was an issue with the max flow input.')\n", + "# [END solve]\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/graph/simple_min_cost_flow_program.ipynb b/examples/notebook/graph/simple_min_cost_flow_program.ipynb new file mode 100644 index 0000000000..c97dcd7db1 --- /dev/null +++ b/examples/notebook/graph/simple_min_cost_flow_program.ipynb @@ -0,0 +1,83 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010-2018 Google LLC\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "# [START program]\n", + "\"\"\"From Bradley, H. and M., 'Applied Mathematical Programming', figure 8.1.\"\"\"\n", + "# [START import]\n", + "from __future__ import print_function\n", + "from ortools.graph import pywrapgraph\n", + "# [END import]\n", + "\n", + "\n", + "\"\"\"MinCostFlow simple interface example.\"\"\"\n", + "# [START data]\n", + "# Define four parallel arrays: sources, destinations, capacities,\n", + "# and unit costs between each pair. For instance, the arc from node 0\n", + "# to node 1 has a capacity of 15.\n", + "start_nodes = [0, 0, 1, 1, 1, 2, 2, 3, 4]\n", + "end_nodes = [1, 2, 2, 3, 4, 3, 4, 4, 2]\n", + "capacities = [15, 8, 20, 4, 10, 15, 4, 20, 5]\n", + "unit_costs = [4, 4, 2, 2, 6, 1, 3, 2, 3]\n", + "\n", + "# Define an array of supplies at each node.\n", + "supplies = [20, 0, 0, -5, -15]\n", + "# [END data]\n", + "\n", + "# [START constraints]\n", + "# Instantiate a SimpleMinCostFlow solver.\n", + "min_cost_flow = pywrapgraph.SimpleMinCostFlow()\n", + "\n", + "# Add each arc.\n", + "for arc in zip(start_nodes, end_nodes, capacities, unit_costs):\n", + " min_cost_flow.AddArcWithCapacityAndUnitCost(arc[0], arc[1], arc[2],\n", + " arc[3])\n", + "\n", + "# Add node supplies.\n", + "for count, supply in enumerate(supplies):\n", + " min_cost_flow.SetNodeSupply(count, supply)\n", + "# [END constraints]\n", + "\n", + "# [START solve]\n", + "# Find the min cost flow.\n", + "solve_status = min_cost_flow.Solve()\n", + "# [END solve]\n", + "\n", + "# [START print_solution]\n", + "if solve_status == min_cost_flow.OPTIMAL:\n", + " print('Minimum cost: ', min_cost_flow.OptimalCost())\n", + " print('')\n", + " print(' Arc Flow / Capacity Cost')\n", + " for i in range(min_cost_flow.NumArcs()):\n", + " cost = min_cost_flow.Flow(i) * min_cost_flow.UnitCost(i)\n", + " print('%1s -> %1s %3s / %3s %3s' %\n", + " (min_cost_flow.Tail(i), min_cost_flow.Head(i),\n", + " min_cost_flow.Flow(i), min_cost_flow.Capacity(i), cost))\n", + "else:\n", + " print('Solving the min cost flow problem failed. Solver status: ',\n", + " solve_status)\n", + "# [END print_solution]\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/hidato_sat.ipynb b/examples/notebook/hidato_sat.ipynb deleted file mode 100644 index 8f5acf2584..0000000000 --- a/examples/notebook/hidato_sat.ipynb +++ /dev/null @@ -1,198 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Copyright 2010-2017 Google\n", - "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", - "# you may not use this file except in compliance with the License.\n", - "# You may obtain a copy of the License at\n", - "#\n", - "# http://www.apache.org/licenses/LICENSE-2.0\n", - "#\n", - "# Unless required by applicable law or agreed to in writing, software\n", - "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", - "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", - "# See the License for the specific language governing permissions and\n", - "# limitations under the License.\n", - "\n", - "from ortools.sat.python import cp_model\n", - "from ortools.sat.python import visualization\n", - "\n", - "\n", - "def BuildPairs(rows, cols):\n", - " \"\"\"Build closeness pairs for consecutive numbers.\n", - "\n", - " Build set of allowed pairs such that two consecutive numbers touch\n", - " each other in the grid.\n", - "\n", - " Returns:\n", - " A list of pairs for allowed consecutive position of numbers.\n", - "\n", - " Args:\n", - " rows: the number of rows in the grid\n", - " cols: the number of columns in the grid\n", - " \"\"\"\n", - " return [\n", - " (x * cols + y, (x + dx) * cols + (y + dy)) for x in range(rows)\n", - " for y in range(cols) for dx in (-1, 0, 1)\n", - " for dy in (-1, 0, 1)\n", - " if (x + dx >= 0 and x + dx < rows and y + dy >= 0 and y + dy < cols and\n", - " (dx != 0 or dy != 0))\n", - " ]\n", - "\n", - "\n", - "def PrintSolution(positions, rows, cols):\n", - " \"\"\"Print a current solution.\"\"\"\n", - " # Create empty board.\n", - " board = []\n", - " for _ in range(rows):\n", - " board.append([0] * cols)\n", - " # Fill board with solution value.\n", - " for k in range(rows * cols):\n", - " position = positions[k]\n", - " board[position // cols][position % cols] = k + 1\n", - " # Print the board.\n", - " print('Solution')\n", - " PrintMatrix(board)\n", - "\n", - "\n", - "def PrintMatrix(game):\n", - " \"\"\"Pretty print of a matrix.\"\"\"\n", - " rows = len(game)\n", - " cols = len(game[0])\n", - " for i in range(rows):\n", - " line = ''\n", - " for j in range(cols):\n", - " if game[i][j] == 0:\n", - " line += ' .'\n", - " else:\n", - " line += '% 3s' % game[i][j]\n", - " print(line)\n", - "\n", - "\n", - "def BuildPuzzle(problem):\n", - " #\n", - " # models, a 0 indicates an open cell which number is not yet known.\n", - " #\n", - " #\n", - " puzzle = None\n", - " if problem == 1:\n", - " # Simple problem\n", - " puzzle = [[6, 0, 9], [0, 2, 8], [1, 0, 0]]\n", - "\n", - " elif problem == 2:\n", - " puzzle = [[0, 44, 41, 0, 0, 0, 0], [0, 43, 0, 28, 29, 0, 0],\n", - " [0, 1, 0, 0, 0, 33, 0], [0, 2, 25, 4, 34, 0, 36],\n", - " [49, 16, 0, 23, 0, 0, 0], [0, 19, 0, 0, 12, 7,\n", - " 0], [0, 0, 0, 14, 0, 0, 0]]\n", - "\n", - " elif problem == 3:\n", - " # Problems from the book:\n", - " # Gyora Bededek: \"Hidato: 2000 Pure Logic Puzzles\"\n", - " # Problem 1 (Practice)\n", - " puzzle = [[0, 0, 20, 0, 0], [0, 0, 0, 16, 18], [22, 0, 15, 0, 0],\n", - " [23, 0, 1, 14, 11], [0, 25, 0, 0, 12]]\n", - "\n", - " elif problem == 4:\n", - " # problem 2 (Practice)\n", - " puzzle = [[0, 0, 0, 0, 14], [0, 18, 12, 0, 0], [0, 0, 17, 4, 5],\n", - " [0, 0, 7, 0, 0], [9, 8, 25, 1, 0]]\n", - "\n", - " elif problem == 5:\n", - " # problem 3 (Beginner)\n", - " puzzle = [[0, 26, 0, 0, 0, 18], [0, 0, 27, 0, 0, 19], [31, 23, 0, 0, 14, 0],\n", - " [0, 33, 8, 0, 15, 1], [0, 0, 0, 5, 0, 0], [35, 36, 0, 10, 0, 0]]\n", - " elif problem == 6:\n", - " # Problem 15 (Intermediate)\n", - " puzzle = [[64, 0, 0, 0, 0, 0, 0, 0], [1, 63, 0, 59, 15, 57, 53, 0],\n", - " [0, 4, 0, 14, 0, 0, 0, 0], [3, 0, 11, 0, 20, 19, 0,\n", - " 50], [0, 0, 0, 0, 22, 0, 48, 40],\n", - " [9, 0, 0, 32, 23, 0, 0, 41], [27, 0, 0, 0, 36, 0, 46,\n", - " 0], [28, 30, 0, 35, 0, 0, 0, 0]]\n", - " return puzzle\n", - "\n", - "\n", - "def SolveHidato(puzzle, index):\n", - " \"\"\"Solve the given hidato table.\"\"\"\n", - " # Create the model.\n", - " model = cp_model.CpModel()\n", - "\n", - " r = len(puzzle)\n", - " c = len(puzzle[0])\n", - " if not visualization.RunFromIPython():\n", - " print('')\n", - " print('----- Solving problem %i -----' % index)\n", - " print('')\n", - " print(('Initial game (%i x %i)' % (r, c)))\n", - " PrintMatrix(puzzle)\n", - "\n", - " #\n", - " # declare variables\n", - " #\n", - " positions = [model.NewIntVar(0, r * c - 1, 'p[%i]' % i) for i in range(r * c)]\n", - "\n", - " #\n", - " # constraints\n", - " #\n", - " model.AddAllDifferent(positions)\n", - "\n", - " #\n", - " # Fill in the clues\n", - " #\n", - " for i in range(r):\n", - " for j in range(c):\n", - " if puzzle[i][j] > 0:\n", - " model.Add(positions[puzzle[i][j] - 1] == i * c + j)\n", - "\n", - " # Consecutive numbers much touch each other in the grid.\n", - " # We use an allowed assignment constraint to model it.\n", - " close_tuples = BuildPairs(r, c)\n", - " for k in range(0, r * c - 1):\n", - " model.AddAllowedAssignments([positions[k], positions[k + 1]], close_tuples)\n", - "\n", - " #\n", - " # solution and search\n", - " #\n", - "\n", - " solver = cp_model.CpSolver()\n", - " status = solver.Solve(model)\n", - "\n", - " if status == cp_model.FEASIBLE:\n", - " if visualization.RunFromIPython():\n", - " output = visualization.SvgWrapper(10, r, 40.0)\n", - " for i in range(len(positions)):\n", - " val = solver.Value(positions[i])\n", - " x = val % c\n", - " y = val // c\n", - " color = 'white' if puzzle[y][x] == 0 else 'lightgreen'\n", - " value = solver.Value(positions[i])\n", - " output.AddRectangle(x, r - y - 1, 1, 1, color, 'black', str(i + 1))\n", - "\n", - " output.AddTitle('Puzzle %i solved in %f s' % (index, solver.WallTime()))\n", - " output.Display()\n", - " else:\n", - " PrintSolution(\n", - " [solver.Value(x) for x in positions],\n", - " r,\n", - " c,\n", - " )\n", - " print('Statistics')\n", - " print(' - conflicts : %i' % solver.NumConflicts())\n", - " print(' - branches : %i' % solver.NumBranches())\n", - " print(' - wall time : %f ms' % solver.WallTime())\n", - "\n", - "\n", - "for i in range(1, 7):\n", - " SolveHidato(BuildPuzzle(i), i)\n", - "\n" - ] - } - ], - "metadata": {}, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/examples/notebook/jobshop_ft06_sat.ipynb b/examples/notebook/jobshop_ft06_sat.ipynb deleted file mode 100644 index f0031bf7d6..0000000000 --- a/examples/notebook/jobshop_ft06_sat.ipynb +++ /dev/null @@ -1,99 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Copyright 2010-2017 Google\n", - "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", - "# you may not use this file except in compliance with the License.\n", - "# You may obtain a copy of the License at\n", - "#\n", - "# http://www.apache.org/licenses/LICENSE-2.0\n", - "#\n", - "# Unless required by applicable law or agreed to in writing, software\n", - "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", - "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", - "# See the License for the specific language governing permissions and\n", - "# limitations under the License.\n", - "\n", - "import collections\n", - "from ortools.sat.python import cp_model\n", - "from ortools.sat.python import visualization\n", - "\n", - "\n", - "# Creates the solver.\n", - "model = cp_model.CpModel()\n", - "\n", - "machines_count = 6\n", - "jobs_count = 6\n", - "all_machines = range(0, machines_count)\n", - "all_jobs = range(0, jobs_count)\n", - "\n", - "durations = [[1, 3, 6, 7, 3, 6], [8, 5, 10, 10, 10, 4], [5, 4, 8, 9, 1, 7],\n", - " [5, 5, 5, 3, 8, 9], [9, 3, 5, 4, 3, 1], [3, 3, 9, 10, 4, 1]]\n", - "\n", - "machines = [[2, 0, 1, 3, 5, 4], [1, 2, 4, 5, 0, 3], [2, 3, 5, 0, 1, 4],\n", - " [1, 0, 2, 3, 4, 5], [2, 1, 4, 5, 0, 3], [1, 3, 5, 0, 4, 2]]\n", - "\n", - "# Computes horizon dynamically.\n", - "horizon = sum([sum(durations[i]) for i in all_jobs])\n", - "\n", - "Task = collections.namedtuple('Task', 'start end interval')\n", - "\n", - "# Creates jobs.\n", - "all_tasks = {}\n", - "for i in all_jobs:\n", - " for j in all_machines:\n", - " start_var = model.NewIntVar(0, horizon, 'start_%i_%i' % (i, j))\n", - " duration = durations[i][j]\n", - " end_var = model.NewIntVar(0, horizon, 'end_%i_%i' % (i, j))\n", - " interval_var = model.NewIntervalVar(start_var, duration, end_var,\n", - " 'interval_%i_%i' % (i, j))\n", - " all_tasks[(i, j)] = Task(\n", - " start=start_var, end=end_var, interval=interval_var)\n", - "\n", - "# Create disjuctive constraints.\n", - "machine_to_jobs = {}\n", - "for i in all_machines:\n", - " machines_jobs = []\n", - " for j in all_jobs:\n", - " for k in all_machines:\n", - " if machines[j][k] == i:\n", - " machines_jobs.append(all_tasks[(j, k)].interval)\n", - " machine_to_jobs[i] = machines_jobs\n", - " model.AddNoOverlap(machines_jobs)\n", - "\n", - "# Precedences inside a job.\n", - "for i in all_jobs:\n", - " for j in range(0, machines_count - 1):\n", - " model.Add(all_tasks[(i, j + 1)].start >= all_tasks[(i, j)].end)\n", - "\n", - "# Makespan objective.\n", - "obj_var = model.NewIntVar(0, horizon, 'makespan')\n", - "model.AddMaxEquality(\n", - " obj_var, [all_tasks[(i, machines_count - 1)].end for i in all_jobs])\n", - "model.Minimize(obj_var)\n", - "\n", - "# Solve model.\n", - "solver = cp_model.CpSolver()\n", - "response = solver.Solve(model)\n", - "\n", - "# Output solution.\n", - "if visualization.RunFromIPython():\n", - " starts = [[solver.Value(all_tasks[(i, j)][0])\n", - " for j in all_machines]\n", - " for i in all_jobs]\n", - " visualization.DisplayJobshop(starts, durations, machines, 'FT06')\n", - "else:\n", - " print('Optimal makespan: %i' % solver.ObjectiveValue())\n", - "\n" - ] - } - ], - "metadata": {}, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/examples/notebook/linear_solver/integer_programming_example.ipynb b/examples/notebook/linear_solver/integer_programming_example.ipynb new file mode 100644 index 0000000000..5b4a70701f --- /dev/null +++ b/examples/notebook/linear_solver/integer_programming_example.ipynb @@ -0,0 +1,94 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010-2018 Google LLC\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"Small example to illustrate solving a MIP problem.\"\"\"\n", + "# [START program]\n", + "from __future__ import print_function\n", + "# [START import]\n", + "from ortools.linear_solver import pywraplp\n", + "# [END import]\n", + "\n", + "\n", + "def IntegerProgrammingExample():\n", + " \"\"\"Integer programming sample.\"\"\"\n", + " # [START solver]\n", + " # Create the mip solver with the CBC backend.\n", + " solver = pywraplp.Solver('IntegerProgrammingExample',\n", + " pywraplp.Solver.CBC_MIXED_INTEGER_PROGRAMMING)\n", + " # [END solver]\n", + "\n", + " # [START variables]\n", + " # x, y, and z are non-negative integer variables.\n", + " x = solver.IntVar(0.0, solver.infinity(), 'x')\n", + " y = solver.IntVar(0.0, solver.infinity(), 'y')\n", + " z = solver.IntVar(0.0, solver.infinity(), 'z')\n", + " # [END variables]\n", + "\n", + " # [START constraints]\n", + " # 2*x + 7*y + 3*z <= 50\n", + " constraint0 = solver.Constraint(-solver.infinity(), 50)\n", + " constraint0.SetCoefficient(x, 2)\n", + " constraint0.SetCoefficient(y, 7)\n", + " constraint0.SetCoefficient(z, 3)\n", + "\n", + " # 3*x - 5*y + 7*z <= 45\n", + " constraint1 = solver.Constraint(-solver.infinity(), 45)\n", + " constraint1.SetCoefficient(x, 3)\n", + " constraint1.SetCoefficient(y, -5)\n", + " constraint1.SetCoefficient(z, 7)\n", + "\n", + " # 5*x + 2*y - 6*z <= 37\n", + " constraint2 = solver.Constraint(-solver.infinity(), 37)\n", + " constraint2.SetCoefficient(x, 5)\n", + " constraint2.SetCoefficient(y, 2)\n", + " constraint2.SetCoefficient(z, -6)\n", + " # [END constraints]\n", + "\n", + " # [START objective]\n", + " # Maximize 2*x + 2*y + 3*z\n", + " objective = solver.Objective()\n", + " objective.SetCoefficient(x, 2)\n", + " objective.SetCoefficient(y, 2)\n", + " objective.SetCoefficient(z, 3)\n", + " objective.SetMaximization()\n", + " # [END objective]\n", + "\n", + " # Solve the problem and print the solution.\n", + " # [START print_solution]\n", + " solver.Solve()\n", + " # Print the objective value of the solution.\n", + " print('Maximum objective function value = %d' % solver.Objective().Value())\n", + " print()\n", + " # Print the value of each variable in the solution.\n", + " for variable in [x, y, z]:\n", + " print('%s = %d' % (variable.name(), variable.solution_value()))\n", + " # [END print_solution]\n", + "\n", + "\n", + "IntegerProgrammingExample()\n", + "# [END program]\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/linear_solver/linear_programming_example.ipynb b/examples/notebook/linear_solver/linear_programming_example.ipynb new file mode 100644 index 0000000000..b48ca56219 --- /dev/null +++ b/examples/notebook/linear_solver/linear_programming_example.ipynb @@ -0,0 +1,95 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010-2018 Google LLC\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"Linear optimization example.\"\"\"\n", + "# [START program]\n", + "from __future__ import print_function\n", + "# [START import]\n", + "from ortools.linear_solver import pywraplp\n", + "\n", + "# [END import]\n", + "\n", + "\n", + "def LinearProgrammingExample():\n", + " \"\"\"Linear programming sample.\"\"\"\n", + " # Instantiate a Glop solver, naming it LinearExample.\n", + " # [START solver]\n", + " solver = pywraplp.Solver('LinearProgrammingExample',\n", + " pywraplp.Solver.GLOP_LINEAR_PROGRAMMING)\n", + " # [END solver]\n", + "\n", + " # Create the two variables and let them take on any non-negative value.\n", + " # [START variables]\n", + " x = solver.NumVar(0, solver.infinity(), 'x')\n", + " y = solver.NumVar(0, solver.infinity(), 'y')\n", + " # [END variables]\n", + "\n", + " # [START constraints]\n", + " # Constraint 0: x + 2y <= 14.\n", + " constraint0 = solver.Constraint(-solver.infinity(), 14)\n", + " constraint0.SetCoefficient(x, 1)\n", + " constraint0.SetCoefficient(y, 2)\n", + "\n", + " # Constraint 1: 3x - y >= 0.\n", + " constraint1 = solver.Constraint(0, solver.infinity())\n", + " constraint1.SetCoefficient(x, 3)\n", + " constraint1.SetCoefficient(y, -1)\n", + "\n", + " # Constraint 2: x - y <= 2.\n", + " constraint2 = solver.Constraint(-solver.infinity(), 2)\n", + " constraint2.SetCoefficient(x, 1)\n", + " constraint2.SetCoefficient(y, -1)\n", + " # [END constraints]\n", + "\n", + " # [START objective]\n", + " # Objective function: 3x + 4y.\n", + " objective = solver.Objective()\n", + " objective.SetCoefficient(x, 3)\n", + " objective.SetCoefficient(y, 4)\n", + " objective.SetMaximization()\n", + " # [END objective]\n", + "\n", + " # Solve the system.\n", + " # [START solve]\n", + " solver.Solve()\n", + " # [END solve]\n", + " # [START print_solution]\n", + " opt_solution = 3 * x.solution_value() + 4 * y.solution_value()\n", + " print('Number of variables =', solver.NumVariables())\n", + " print('Number of constraints =', solver.NumConstraints())\n", + " # The value of each variable in the solution.\n", + " print('Solution:')\n", + " print('x = ', x.solution_value())\n", + " print('y = ', y.solution_value())\n", + " # The objective value of the solution.\n", + " print('Optimal objective value =', opt_solution)\n", + " # [END print_solution]\n", + "\n", + "\n", + "LinearProgrammingExample()\n", + "# [END program]\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/linear_solver/mip_var_array.ipynb b/examples/notebook/linear_solver/mip_var_array.ipynb new file mode 100644 index 0000000000..c81b0f93f6 --- /dev/null +++ b/examples/notebook/linear_solver/mip_var_array.ipynb @@ -0,0 +1,114 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010-2018 Google LLC\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"MIP example that uses a variable array.\"\"\"\n", + "# [START program]\n", + "# [START import]\n", + "from __future__ import print_function\n", + "from ortools.linear_solver import pywraplp\n", + "\n", + "# [END import]\n", + "\n", + "\n", + "# [START data_model]\n", + "def create_data_model():\n", + " \"\"\"Stores the data for the problem.\"\"\"\n", + " data = {}\n", + " data['constraint_coeffs'] = [\n", + " [5, 7, 9, 2, 1],\n", + " [18, 4, -9, 10, 12],\n", + " [4, 7, 3, 8, 5],\n", + " [5, 13, 16, 3, -7],\n", + " ]\n", + " data['bounds'] = [250, 285, 211, 315]\n", + " data['obj_coeffs'] = [7, 8, 2, 9, 6]\n", + " data['num_vars'] = 5\n", + " data['num_constraints'] = 4\n", + " return data\n", + "\n", + "\n", + "# [END data_model]\n", + "\n", + "\n", + "# [START data]\n", + "data = create_data_model()\n", + "# [END data]\n", + "\n", + "# [START solver]\n", + "# Create the mip solver with the CBC backend.\n", + "solver = pywraplp.Solver('simple_mip_program',\n", + " pywraplp.Solver.CBC_MIXED_INTEGER_PROGRAMMING)\n", + "# [END solver]\n", + "\n", + "# [START variables]\n", + "infinity = solver.infinity()\n", + "x = {}\n", + "for j in range(data['num_vars']):\n", + " x[j] = solver.IntVar(0, infinity, 'x[%i]' % j)\n", + "print('Number of variables =', solver.NumVariables())\n", + "# [END variables]\n", + "\n", + "# [START constraints]\n", + "for i in range(data['num_constraints']):\n", + " constraint = solver.RowConstraint(0, data['bounds'][i], '')\n", + " for j in range(data['num_vars']):\n", + " constraint.SetCoefficient(x[j], data['constraint_coeffs'][i][j])\n", + "print('Number of constraints =', solver.NumConstraints())\n", + "# In Python, you can also set the constraints as follows.\n", + "# for i in range(data['num_constraints']):\n", + "# constraint_expr = \\\n", + "# [data['constraint_coeffs'][i][j] * x[j] for j in range(data['num_vars'])]\n", + "# solver.Add(sum(constraint_expr) <= data['bounds'][i])\n", + "# [END constraints]\n", + "\n", + "# [START objective]\n", + "objective = solver.Objective()\n", + "for j in range(data['num_vars']):\n", + " objective.SetCoefficient(x[j], data['obj_coeffs'][j])\n", + "objective.SetMaximization()\n", + "# In Python, you can also set the objective as follows.\n", + "# obj_expr = [data['obj_coeffs'][j] * x[j] for j in range(data['num_vars'])]\n", + "# solver.Maximize(solver.Sum(obj_expr))\n", + "# [END objective]\n", + "\n", + "# [START solve]\n", + "status = solver.Solve()\n", + "# [END solve]\n", + "\n", + "# [START print_solution]\n", + "if status == pywraplp.Solver.OPTIMAL:\n", + " print('Objective value =', solver.Objective().Value())\n", + " for j in range(data['num_vars']):\n", + " print(x[j].name(), ' = ', x[j].solution_value())\n", + " print()\n", + " print('Problem solved in %f milliseconds' % solver.wall_time())\n", + " print('Problem solved in %d iterations' % solver.iterations())\n", + " print('Problem solved in %d branch-and-bound nodes' % solver.nodes())\n", + "else:\n", + " print('The problem does not have an optimal solution.')\n", + "# [END print_solution]\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/linear_solver/simple_lp_program.ipynb b/examples/notebook/linear_solver/simple_lp_program.ipynb new file mode 100644 index 0000000000..4d40cb4a71 --- /dev/null +++ b/examples/notebook/linear_solver/simple_lp_program.ipynb @@ -0,0 +1,77 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010-2018 Google LLC\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"Minimal example to call the GLOP solver.\"\"\"\n", + "# [START program]\n", + "# [START import]\n", + "from __future__ import print_function\n", + "from ortools.linear_solver import pywraplp\n", + "# [END import]\n", + "\n", + "\n", + "# [START solver]\n", + "# Create the linear solver with the GLOP backend.\n", + "solver = pywraplp.Solver('simple_lp_program',\n", + " pywraplp.Solver.GLOP_LINEAR_PROGRAMMING)\n", + "# [END solver]\n", + "\n", + "# [START variables]\n", + "# Create the variables x and y.\n", + "x = solver.NumVar(0, 1, 'x')\n", + "y = solver.NumVar(0, 2, 'y')\n", + "\n", + "print('Number of variables =', solver.NumVariables())\n", + "# [END variables]\n", + "\n", + "# [START constraints]\n", + "# Create a linear constraint, 0 <= x + y <= 2.\n", + "ct = solver.Constraint(0, 2, 'ct')\n", + "ct.SetCoefficient(x, 1)\n", + "ct.SetCoefficient(y, 1)\n", + "\n", + "print('Number of constraints =', solver.NumConstraints())\n", + "# [END constraints]\n", + "\n", + "# [START objective]\n", + "# Create the objective function, 3 * x + y.\n", + "objective = solver.Objective()\n", + "objective.SetCoefficient(x, 3)\n", + "objective.SetCoefficient(y, 1)\n", + "objective.SetMaximization()\n", + "# [END objective]\n", + "\n", + "# [START solve]\n", + "solver.Solve()\n", + "# [END solve]\n", + "\n", + "# [START print_solution]\n", + "print('Solution:')\n", + "print('Objective value =', objective.Value())\n", + "print('x =', x.solution_value())\n", + "print('y =', y.solution_value())\n", + "# [END print_solution]\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/linear_solver/simple_mip_program.ipynb b/examples/notebook/linear_solver/simple_mip_program.ipynb new file mode 100644 index 0000000000..5743c207e7 --- /dev/null +++ b/examples/notebook/linear_solver/simple_mip_program.ipynb @@ -0,0 +1,86 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010-2018 Google LLC\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"Integer programming examples that show how to use the APIs.\"\"\"\n", + "# [START program]\n", + "# [START import]\n", + "from __future__ import print_function\n", + "from ortools.linear_solver import pywraplp\n", + "# [END import]\n", + "\n", + "\n", + "# [START solver]\n", + "# Create the mip solver with the CBC backend.\n", + "solver = pywraplp.Solver('simple_mip_program',\n", + " pywraplp.Solver.CBC_MIXED_INTEGER_PROGRAMMING)\n", + "# [END solver]\n", + "\n", + "# [START variables]\n", + "infinity = solver.infinity()\n", + "# x and y are integer non-negative variables.\n", + "x = solver.IntVar(0.0, infinity, 'x')\n", + "y = solver.IntVar(0.0, infinity, 'y')\n", + "\n", + "print('Number of variables =', solver.NumVariables())\n", + "# [END variables]\n", + "\n", + "# [START constraints]\n", + "# x + 7 * y <= 17.5.\n", + "solver.Add(x + 7 * y <= 17.5)\n", + "\n", + "# x <= 3.5.\n", + "solver.Add(x <= 3.5)\n", + "\n", + "print('Number of constraints =', solver.NumConstraints())\n", + "# [END constraints]\n", + "\n", + "# [START objective]\n", + "# Maximize x + 10 * y.\n", + "solver.Maximize(x + 10 * y)\n", + "# [END objective]\n", + "\n", + "# [START solve]\n", + "status = solver.Solve()\n", + "# [END solve]\n", + "\n", + "# [START print_solution]\n", + "if status == pywraplp.Solver.OPTIMAL:\n", + " print('Solution:')\n", + " print('Objective value =', solver.Objective().Value())\n", + " print('x =', x.solution_value())\n", + " print('y =', y.solution_value())\n", + "else:\n", + " print('The problem does not have an optimal solution.')\n", + "# [END print_solution]\n", + "\n", + "# [START advanced]\n", + "print('\\nAdvanced usage:')\n", + "print('Problem solved in %f milliseconds' % solver.wall_time())\n", + "print('Problem solved in %d iterations' % solver.iterations())\n", + "print('Problem solved in %d branch-and-bound nodes' % solver.nodes())\n", + "# [END advanced]\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/nurses_sat.ipynb b/examples/notebook/nurses_sat.ipynb deleted file mode 100644 index b1f72e3e49..0000000000 --- a/examples/notebook/nurses_sat.ipynb +++ /dev/null @@ -1,142 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Copyright 2010-2017 Google\n", - "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", - "# you may not use this file except in compliance with the License.\n", - "# You may obtain a copy of the License at\n", - "#\n", - "# http://www.apache.org/licenses/LICENSE-2.0\n", - "#\n", - "# Unless required by applicable law or agreed to in writing, software\n", - "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", - "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", - "# See the License for the specific language governing permissions and\n", - "# limitations under the License.\n", - "\n", - "from __future__ import print_function\n", - "import sys\n", - "from ortools.sat.python import cp_model\n", - "\n", - "\n", - "class NursesPartialSolutionPrinter(cp_model.CpSolverSolutionCallback):\n", - " \"\"\"Print intermediate solutions.\"\"\"\n", - "\n", - " def __init__(self, shifts, num_nurses, num_days, num_shifts, sols):\n", - " cp_model.CpSolverSolutionCallback.__init__(self)\n", - " self.__shifts = shifts\n", - " self.__num_nurses = num_nurses\n", - " self.__num_days = num_days\n", - " self.__num_shifts = num_shifts\n", - " self.__solutions = set(sols)\n", - " self.__solution_count = 0\n", - "\n", - " def OnSolutionCallback(self):\n", - " self.__solution_count += 1\n", - " if self.__solution_count in self.__solutions:\n", - " print('Solution #%i' % self.__solution_count)\n", - " for d in range(self.__num_days):\n", - " print('Day #%i' % d)\n", - " for n in range(self.__num_nurses):\n", - " for s in range(self.__num_shifts):\n", - " if self.Value(self.__shifts[(n, d, s)]):\n", - " print(' Nurse #%i is working shift #%i' % (n, s))\n", - " print()\n", - "\n", - " def SolutionCount(self):\n", - " return self.__solution_count\n", - "\n", - "\n", - "# Data.\n", - "num_nurses = 4\n", - "num_shifts = 4 # Nurse assigned to shift 0 means not working that day.\n", - "num_days = 7\n", - "all_nurses = range(num_nurses)\n", - "all_shifts = range(num_shifts)\n", - "all_working_shifts = range(1, num_shifts)\n", - "all_days = range(num_days)\n", - "\n", - "# Creates the model.\n", - "model = cp_model.CpModel()\n", - "\n", - "# Creates shift variables.\n", - "# shifts[(n, d, s)]: nurse 'n' works shift 's' on day 'd'.\n", - "shifts = {}\n", - "for n in all_nurses:\n", - " for d in all_days:\n", - " for s in all_shifts:\n", - " shifts[(n, d, s)] = model.NewBoolVar('shift_n%id%is%i' % (n, d, s))\n", - "\n", - "# Makes assignments different on each day, that is each shift is assigned at\n", - "# most one nurse. As we have the same number of nurses and shifts, then each\n", - "# day, each shift is assigned to exactly one nurse.\n", - "for d in all_days:\n", - " for s in all_shifts:\n", - " model.Add(sum(shifts[(n, d, s)] for n in all_nurses) == 1)\n", - "\n", - "# Nurses do 1 shift per day.\n", - "for n in all_nurses:\n", - " for d in all_days:\n", - " model.Add(sum(shifts[(n, d, s)] for s in all_shifts) == 1)\n", - "\n", - "# Each nurse works 5 or 6 days in a week.\n", - "# That is each nurse works shift 0 at most 2 times.\n", - "for n in all_nurses:\n", - " model.AddSumConstraint([shifts[(n, d, 0)] for d in all_days], 1, 2)\n", - "\n", - "# works_shift[(n, s)] is 1 if nurse n works shift s at least one day in\n", - "# the week.\n", - "works_shift = {}\n", - "for n in all_nurses:\n", - " for s in all_shifts:\n", - " works_shift[(n, s)] = model.NewBoolVar('works_shift_n%is%i' % (n, s))\n", - " model.AddMaxEquality(works_shift[(n, s)],\n", - " [shifts[(n, d, s)] for d in all_days])\n", - "\n", - "# For each shift, at most 2 nurses are assigned to that shift during the week.\n", - "for s in all_working_shifts:\n", - " model.Add(sum(works_shift[(n, s)] for n in all_nurses) <= 2)\n", - "\n", - "# If a nurse works shifts 2 or 3 on, she must also work that shift the\n", - "# previous day or the following day.\n", - "# This means that on a given day and shift, either she does not work that\n", - "# shift on that day, or she works that shift on the day before, or the day\n", - "# after.\n", - "for n in all_nurses:\n", - " for s in [2, 3]:\n", - " for d in all_days:\n", - " yesterday = (d - 1) % num_days\n", - " tomorrow = (d + 1) % num_days\n", - " model.AddBoolOr([\n", - " shifts[(n, yesterday, s)], shifts[(n, d, s)].Not(),\n", - " shifts[(n, tomorrow, s)]\n", - " ])\n", - "\n", - "# Creates the solver and solve.\n", - "solver = cp_model.CpSolver()\n", - "# Display a few solutions picked at random.\n", - "a_few_solutions = [859, 2034, 5091, 7003]\n", - "solution_printer = NursesPartialSolutionPrinter(shifts, num_nurses, num_days,\n", - " num_shifts, a_few_solutions)\n", - "status = solver.SearchForAllSolutions(model, solution_printer)\n", - "\n", - "# Statistics.\n", - "print()\n", - "print('Statistics')\n", - "print(' - conflicts : %i' % solver.NumConflicts())\n", - "print(' - branches : %i' % solver.NumBranches())\n", - "print(' - wall time : %f ms' % solver.WallTime())\n", - "print(' - solutions found : %i' % solution_printer.SolutionCount())\n", - "\n" - ] - } - ], - "metadata": {}, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/examples/notebook/rcpsp_sat.ipynb b/examples/notebook/rcpsp_sat.ipynb deleted file mode 100644 index 5f21b530fc..0000000000 --- a/examples/notebook/rcpsp_sat.ipynb +++ /dev/null @@ -1,286 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Copyright 2010-2017 Google\n", - "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", - "# you may not use this file except in compliance with the License.\n", - "# You may obtain a copy of the License at\n", - "#\n", - "# http://www.apache.org/licenses/LICENSE-2.0\n", - "#\n", - "# Unless required by applicable law or agreed to in writing, software\n", - "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", - "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", - "# See the License for the specific language governing permissions and\n", - "# limitations under the License.\n", - "\n", - "from __future__ import print_function\n", - "\n", - "import argparse\n", - "from collections import defaultdict\n", - "from ortools.sat.python import cp_model\n", - "from ortools.data import rcpsp_pb2\n", - "from ortools.data import pywraprcpsp\n", - "import time\n", - "\n", - "parser = argparse.ArgumentParser()\n", - "\n", - "parser.add_argument(\n", - " '--input', default='', help='Input file to parse and solve.')\n", - "parser.add_argument(\n", - " '--output_proto',\n", - " default='',\n", - " help='Output file to write the cp_model proto to.')\n", - "\n", - "\n", - "class SolutionPrinter(cp_model.CpSolverSolutionCallback):\n", - " \"\"\"Print intermediate solutions.\"\"\"\n", - "\n", - " def __init__(self):\n", - " cp_model.CpSolverSolutionCallback.__init__(self)\n", - " self.__solution_count = 0\n", - " self.__start_time = time.time()\n", - "\n", - " def OnSolutionCallback(self):\n", - " current_time = time.time()\n", - " objective = self.ObjectiveValue()\n", - " print('Solution %i, time = %f s, objective = %i' %\n", - " (self.__solution_count, current_time - self.__start_time, objective))\n", - " self.__solution_count += 1\n", - "\n", - "\n", - "def SolveRcpsp(problem, proto_file):\n", - " # Determine problem type.\n", - " problem_type = ('Resource investment'\n", - " if problem.is_resource_investment else 'RCPSP')\n", - "\n", - " if problem.is_rcpsp_max:\n", - " problem_type += '/Max delay'\n", - " if problem.is_consumer_producer:\n", - " print('Solving %s with %i reservoir resources and %i tasks' %\n", - " (problem_type, len(problem.resources), len(problem.tasks)))\n", - " else:\n", - " print('Solving %s with %i resources and %i tasks' %\n", - " (problem_type, len(problem.resources), len(problem.tasks)))\n", - "\n", - " # Create the model.\n", - " model = cp_model.CpModel()\n", - "\n", - " num_tasks = len(problem.tasks)\n", - " num_resources = len(problem.resources)\n", - "\n", - " all_tasks = range(num_tasks)\n", - " all_active_tasks = range(1, num_tasks - 1)\n", - " all_resources = range(num_resources)\n", - "\n", - " horizon = problem.deadline if problem.deadline != -1 else problem.horizon\n", - " if horizon == -1: # Naive computation.\n", - " horizon = sum(max(r.duration for r in t.recipes) for t in problem.tasks)\n", - " if problem.is_rcpsp_max:\n", - " for t in problem.tasks:\n", - " for sd in t.successor_delays:\n", - " for rd in sd.recipe_delays:\n", - " for d in rd.min_delays:\n", - " horizon += abs(d)\n", - " print(' - horizon = %i' % horizon)\n", - "\n", - " # Containers used to build resources.\n", - " intervals_per_resource = defaultdict(list)\n", - " demands_per_resource = defaultdict(list)\n", - " presences_per_resource = defaultdict(list)\n", - " starts_per_resource = defaultdict(list)\n", - "\n", - " # Starts and ends for master interval variables.\n", - " task_starts = {}\n", - " task_ends = {}\n", - "\n", - " # Containers for per-recipe per task variables.\n", - " alternatives_per_task = defaultdict(list)\n", - " presences_per_task = defaultdict(list)\n", - " starts_per_task = defaultdict(list)\n", - " ends_per_task = defaultdict(list)\n", - "\n", - " # Create tasks.\n", - " for t in all_active_tasks:\n", - " task = problem.tasks[t]\n", - "\n", - " if len(task.recipes) == 1:\n", - " # Create interval.\n", - " recipe = task.recipes[0]\n", - " task_starts[t] = model.NewIntVar(0, horizon, 'start_of_task_%i' % t)\n", - " task_ends[t] = model.NewIntVar(0, horizon, 'end_of_task_%i' % t)\n", - " interval = model.NewIntervalVar(task_starts[t], recipe.duration,\n", - " task_ends[t], 'interval_%i' % t)\n", - "\n", - " # Store for later.\n", - " alternatives_per_task[t].append(interval)\n", - " starts_per_task[t].append(task_starts[t])\n", - " ends_per_task[t].append(task_ends[t])\n", - " presences_per_task[t].append(1)\n", - "\n", - " # Register for resources.\n", - " for i in range(len(recipe.demands)):\n", - " demand = recipe.demands[i]\n", - " res = recipe.resources[i]\n", - " demands_per_resource[res].append(demand)\n", - " if problem.resources[res].renewable:\n", - " intervals_per_resource[res].append(interval)\n", - " else:\n", - " starts_per_resource[res].append(task_starts[t])\n", - " presences_per_resource[res].append(1)\n", - " else:\n", - " all_recipes = range(len(task.recipes))\n", - "\n", - " # Compute duration range.\n", - " min_size = min(recipe.duration for recipe in task.recipes)\n", - " max_size = max(recipe.duration for recipe in task.recipes)\n", - "\n", - " # Create one optional interval per recipe.\n", - " for r in all_recipes:\n", - " recipe = task.recipes[r]\n", - " is_present = model.NewBoolVar('is_present_%i_r%i' % (t, r))\n", - " start = model.NewIntVar(0, horizon, 'start_%i_r%i' % (t, r))\n", - " end = model.NewIntVar(0, horizon, 'end_%i_r%i' % (t, r))\n", - " interval = model.NewOptionalIntervalVar(\n", - " start, recipe.duration, end, is_present, 'interval_%i_r%i' % (t, r))\n", - "\n", - " # Store variables.\n", - " alternatives_per_task[t].append(interval)\n", - " starts_per_task[t].append(start)\n", - " ends_per_task[t].append(end)\n", - " presences_per_task[t].append(is_present)\n", - "\n", - " # Register intervals in resources.\n", - " for i in range(len(recipe.demands)):\n", - " demand = recipe.demands[i]\n", - " res = recipe.resources[i]\n", - " demands_per_resource[res].append(demand)\n", - " if problem.resources[res].renewable:\n", - " intervals_per_resource[res].append(interval)\n", - " else:\n", - " starts_per_resource[res].append(start)\n", - " presences_per_resource[res].append(is_present)\n", - "\n", - " # Create the master interval for the task.\n", - " task_starts[t] = model.NewIntVar(0, horizon, 'start_of_task_%i' % t)\n", - " task_ends[t] = model.NewIntVar(0, horizon, 'end_of_task_%i' % t)\n", - " duration = model.NewIntVar(min_size, max_size, 'duration_of_task_%i' % t)\n", - " interval = model.NewIntervalVar(task_starts[t], duration, task_ends[t],\n", - " 'interval_%i' % t)\n", - "\n", - " # Link with optional per-recipe copies.\n", - " for r in all_recipes:\n", - " p = presences_per_task[t][r]\n", - " model.Add(task_starts[t] == starts_per_task[t][r]).OnlyEnforceIf(p)\n", - " model.Add(task_ends[t] == ends_per_task[t][r]).OnlyEnforceIf(p)\n", - " model.Add(duration == task.recipes[r].duration).OnlyEnforceIf(p)\n", - " model.Add(sum(presences_per_task[t]) == 1)\n", - "\n", - " # Create makespan variable\n", - " makespan = model.NewIntVar(0, horizon, 'makespan')\n", - "\n", - " # Add precedences.\n", - " if problem.is_rcpsp_max:\n", - " for t in all_active_tasks:\n", - " task = problem.tasks[t]\n", - " num_modes = len(task.recipes)\n", - " num_successors = len(task.successors)\n", - "\n", - " for s in range(num_successors):\n", - " n = task.successors[s]\n", - " delay_matrix = task.successor_delays[s]\n", - " num_other_modes = len(problem.tasks[n].recipes)\n", - " for m1 in range(num_modes):\n", - " s1 = starts_per_task[t][m1]\n", - " if n == num_tasks - 1:\n", - " delay = delay_matrix.recipe_delays[m1].min_delays[0]\n", - " model.Add(s1 + delay <= makespan)\n", - " else:\n", - " for m2 in range(num_other_modes):\n", - " delay = delay_matrix.recipe_delays[m1].min_delays[m2]\n", - " s2 = starts_per_task[n][m2]\n", - " model.Add(s1 + delay <= s2)\n", - " else: # Normal dependencies (task ends before the start of successors).\n", - " for t in all_active_tasks:\n", - " for n in problem.tasks[t].successors:\n", - " if n == num_tasks - 1:\n", - " model.Add(task_ends[t] <= makespan)\n", - " else:\n", - " model.Add(task_ends[t] <= task_starts[n])\n", - "\n", - " # Containers for resource investment problems.\n", - " capacities = []\n", - " max_cost = 0\n", - "\n", - " # Create resources.\n", - " for r in all_resources:\n", - " resource = problem.resources[r]\n", - " c = resource.max_capacity\n", - " if c == -1:\n", - " c = sum(demands_per_resource[r])\n", - "\n", - " if problem.is_resource_investment:\n", - " capacity = model.NewIntVar(0, c, 'capacity_of_%i' % r)\n", - " model.AddCumulative(intervals_per_resource[r], demands_per_resource[r],\n", - " capacity)\n", - " capacities.append(capacity)\n", - " max_cost += c * resource.unit_cost\n", - " elif resource.renewable:\n", - " if intervals_per_resource[r]:\n", - " model.AddCumulative(intervals_per_resource[r], demands_per_resource[r],\n", - " c)\n", - " elif presences_per_resource[r]: # Non empty non renewable resource.\n", - " if problem.is_consumer_producer:\n", - " model.AddReservoirConstraint(\n", - " starts_per_resource[r], demands_per_resource[r],\n", - " resource.min_capacity, resource.max_capacity)\n", - " else:\n", - " model.Add(\n", - " sum(presences_per_resource[r][i] * demands_per_resource[r][i]\n", - " for i in range(len(presences_per_resource[r]))) <= c)\n", - "\n", - " # Objective.\n", - " if problem.is_resource_investment:\n", - " objective = model.NewIntVar(0, max_cost, 'capacity_costs')\n", - " model.Add(objective == sum(problem.resources[i].unit_cost * capacities[i]\n", - " for i in range(len(capacities))))\n", - " else:\n", - " objective = makespan\n", - "\n", - " model.Minimize(objective)\n", - "\n", - " if proto_file:\n", - " print('Writing proto to %s' % proto_file)\n", - " text_file = open(proto_file, 'w')\n", - " text_file.write(str(model))\n", - " text_file.close()\n", - "\n", - " # Solve model.\n", - " solver = cp_model.CpSolver()\n", - " solution_printer = SolutionPrinter()\n", - " status = solver.SolveWithSolutionCallback(model, solution_printer)\n", - " print('Solve status: %s' % solver.StatusName(status))\n", - " print('Optimal objective value: %i' % solver.ObjectiveValue())\n", - " print('Statistics')\n", - " print(' - conflicts : %i' % solver.NumConflicts())\n", - " print(' - branches : %i' % solver.NumBranches())\n", - " print(' - wall time : %f s' % solver.WallTime())\n", - "\n", - "\n", - "parser = pywraprcpsp.RcpspParser()\n", - "parser.ParseFile(args.input)\n", - "problem = parser.Problem()\n", - "SolveRcpsp(problem, args.output_proto)\n", - "\n" - ] - } - ], - "metadata": {}, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/examples/notebook/sat/binpacking_problem_sat.ipynb b/examples/notebook/sat/binpacking_problem_sat.ipynb new file mode 100644 index 0000000000..6f35313b59 --- /dev/null +++ b/examples/notebook/sat/binpacking_problem_sat.ipynb @@ -0,0 +1,95 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010-2018 Google LLC\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"Solves a binpacking problem using the CP-SAT solver.\"\"\"\n", + "\n", + "from __future__ import print_function\n", + "\n", + "from ortools.sat.python import cp_model\n", + "\n", + "\n", + "def BinpackingProblemSat():\n", + " \"\"\"Solves a bin-packing problem using the CP-SAT solver.\"\"\"\n", + " # Data.\n", + " bin_capacity = 100\n", + " slack_capacity = 20\n", + " num_bins = 5\n", + " all_bins = range(num_bins)\n", + "\n", + " items = [(20, 6), (15, 6), (30, 4), (45, 3)]\n", + " num_items = len(items)\n", + " all_items = range(num_items)\n", + "\n", + " # Model.\n", + " model = cp_model.CpModel()\n", + "\n", + " # Main variables.\n", + " x = {}\n", + " for i in all_items:\n", + " num_copies = items[i][1]\n", + " for b in all_bins:\n", + " x[(i, b)] = model.NewIntVar(0, num_copies, 'x_%i_%i' % (i, b))\n", + "\n", + " # Load variables.\n", + " load = [model.NewIntVar(0, bin_capacity, 'load_%i' % b) for b in all_bins]\n", + "\n", + " # Slack variables.\n", + " slacks = [model.NewBoolVar('slack_%i' % b) for b in all_bins]\n", + "\n", + " # Links load and x.\n", + " for b in all_bins:\n", + " model.Add(load[b] == sum(x[(i, b)] * items[i][0] for i in all_items))\n", + "\n", + " # Place all items.\n", + " for i in all_items:\n", + " model.Add(sum(x[(i, b)] for b in all_bins) == items[i][1])\n", + "\n", + " # Links load and slack through an equivalence relation.\n", + " safe_capacity = bin_capacity - slack_capacity\n", + " for b in all_bins:\n", + " # slack[b] => load[b] <= safe_capacity.\n", + " model.Add(load[b] <= safe_capacity).OnlyEnforceIf(slacks[b])\n", + " # not(slack[b]) => load[b] > safe_capacity.\n", + " model.Add(load[b] > safe_capacity).OnlyEnforceIf(slacks[b].Not())\n", + "\n", + " # Maximize sum of slacks.\n", + " model.Maximize(sum(slacks))\n", + "\n", + " # Solves and prints out the solution.\n", + " solver = cp_model.CpSolver()\n", + " status = solver.Solve(model)\n", + " print('Solve status: %s' % solver.StatusName(status))\n", + " if status == cp_model.OPTIMAL:\n", + " print('Optimal objective value: %i' % solver.ObjectiveValue())\n", + " print('Statistics')\n", + " print(' - conflicts : %i' % solver.NumConflicts())\n", + " print(' - branches : %i' % solver.NumBranches())\n", + " print(' - wall time : %f s' % solver.WallTime())\n", + "\n", + "\n", + "BinpackingProblemSat()\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/sat/bool_or_sample_sat.ipynb b/examples/notebook/sat/bool_or_sample_sat.ipynb new file mode 100644 index 0000000000..a8e8d69c3e --- /dev/null +++ b/examples/notebook/sat/bool_or_sample_sat.ipynb @@ -0,0 +1,47 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010-2018 Google LLC\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"Code sample to demonstrates a simple Boolean constraint.\"\"\"\n", + "\n", + "from __future__ import absolute_import\n", + "from __future__ import division\n", + "from __future__ import print_function\n", + "\n", + "from ortools.sat.python import cp_model\n", + "\n", + "\n", + "def BoolOrSampleSat():\n", + " model = cp_model.CpModel()\n", + "\n", + " x = model.NewBoolVar('x')\n", + " y = model.NewBoolVar('y')\n", + "\n", + " model.AddBoolOr([x, y.Not()])\n", + "\n", + "\n", + "BoolOrSampleSat()\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/sat/boolean_product_sample_sat.ipynb b/examples/notebook/sat/boolean_product_sample_sat.ipynb new file mode 100644 index 0000000000..4a2f341e84 --- /dev/null +++ b/examples/notebook/sat/boolean_product_sample_sat.ipynb @@ -0,0 +1,61 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010-2018 Google LLC\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"Code sample that encodes the product of two Boolean variables.\"\"\"\n", + "\n", + "from __future__ import absolute_import\n", + "from __future__ import division\n", + "from __future__ import print_function\n", + "\n", + "from ortools.sat.python import cp_model\n", + "\n", + "\n", + "def BooleanProductSampleSat():\n", + " \"\"\"Encoding of the product of two Boolean variables.\n", + "\n", + " p == x * y, which is the same as p <=> x and y\n", + " \"\"\"\n", + " model = cp_model.CpModel()\n", + " x = model.NewBoolVar('x')\n", + " y = model.NewBoolVar('y')\n", + " p = model.NewBoolVar('p')\n", + "\n", + " # x and y implies p, rewrite as not(x and y) or p\n", + " model.AddBoolOr([x.Not(), y.Not(), p])\n", + "\n", + " # p implies x and y, expanded into two implication\n", + " model.AddImplication(p, x)\n", + " model.AddImplication(p, y)\n", + "\n", + " # Create a solver and solve.\n", + " solver = cp_model.CpSolver()\n", + " solution_printer = cp_model.VarArraySolutionPrinter([x, y, p])\n", + " solver.SearchForAllSolutions(model, solution_printer)\n", + "\n", + "\n", + "BooleanProductSampleSat()\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/sat/channeling_sample_sat.ipynb b/examples/notebook/sat/channeling_sample_sat.ipynb new file mode 100644 index 0000000000..9d1bdf6924 --- /dev/null +++ b/examples/notebook/sat/channeling_sample_sat.ipynb @@ -0,0 +1,94 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010-2018 Google LLC\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"Link integer constraints together.\"\"\"\n", + "\n", + "from __future__ import absolute_import\n", + "from __future__ import division\n", + "from __future__ import print_function\n", + "\n", + "from ortools.sat.python import cp_model\n", + "\n", + "\n", + "class VarArraySolutionPrinter(cp_model.CpSolverSolutionCallback):\n", + " \"\"\"Print intermediate solutions.\"\"\"\n", + "\n", + " def __init__(self, variables):\n", + " cp_model.CpSolverSolutionCallback.__init__(self)\n", + " self.__variables = variables\n", + " self.__solution_count = 0\n", + "\n", + " def on_solution_callback(self):\n", + " self.__solution_count += 1\n", + " for v in self.__variables:\n", + " print('%s=%i' % (v, self.Value(v)), end=' ')\n", + " print()\n", + "\n", + " def solution_count(self):\n", + " return self.__solution_count\n", + "\n", + "\n", + "def ChannelingSampleSat():\n", + " \"\"\"Demonstrates how to link integer constraints together.\"\"\"\n", + "\n", + " # Create the CP-SAT model.\n", + " model = cp_model.CpModel()\n", + "\n", + " # Declare our two primary variables.\n", + " x = model.NewIntVar(0, 10, 'x')\n", + " y = model.NewIntVar(0, 10, 'y')\n", + "\n", + " # Declare our intermediate boolean variable.\n", + " b = model.NewBoolVar('b')\n", + "\n", + " # Implement b == (x >= 5).\n", + " model.Add(x >= 5).OnlyEnforceIf(b)\n", + " model.Add(x < 5).OnlyEnforceIf(b.Not())\n", + "\n", + " # Create our two half-reified constraints.\n", + " # First, b implies (y == 10 - x).\n", + " model.Add(y == 10 - x).OnlyEnforceIf(b)\n", + " # Second, not(b) implies y == 0.\n", + " model.Add(y == 0).OnlyEnforceIf(b.Not())\n", + "\n", + " # Search for x values in increasing order.\n", + " model.AddDecisionStrategy([x], cp_model.CHOOSE_FIRST,\n", + " cp_model.SELECT_MIN_VALUE)\n", + "\n", + " # Create a solver and solve with a fixed search.\n", + " solver = cp_model.CpSolver()\n", + "\n", + " # Force the solver to follow the decision strategy exactly.\n", + " solver.parameters.search_branching = cp_model.FIXED_SEARCH\n", + "\n", + " # Search and print out all solutions.\n", + " solution_printer = VarArraySolutionPrinter([x, y, b])\n", + " solver.SearchForAllSolutions(model, solution_printer)\n", + "\n", + "\n", + "ChannelingSampleSat()\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/sat/cp_is_fun_sat.ipynb b/examples/notebook/sat/cp_is_fun_sat.ipynb new file mode 100644 index 0000000000..404bde4b00 --- /dev/null +++ b/examples/notebook/sat/cp_is_fun_sat.ipynb @@ -0,0 +1,113 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010-2018 Google LLC\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "# [START program]\n", + "\"\"\"Cryptarithmetic puzzle.\n", + "\n", + "First attempt to solve equation CP + IS + FUN = TRUE\n", + "where each letter represents a unique digit.\n", + "\n", + "This problem has 72 different solutions in base 10.\n", + "\"\"\"\n", + "\n", + "# [START program]\n", + "from __future__ import print_function\n", + "\n", + "from ortools.sat.python import cp_model\n", + "\n", + "\n", + "# [START solution_printing]\n", + "class VarArraySolutionPrinter(cp_model.CpSolverSolutionCallback):\n", + " \"\"\"Print intermediate solutions.\"\"\"\n", + "\n", + " def __init__(self, variables):\n", + " cp_model.CpSolverSolutionCallback.__init__(self)\n", + " self.__variables = variables\n", + " self.__solution_count = 0\n", + "\n", + " def on_solution_callback(self):\n", + " self.__solution_count += 1\n", + " for v in self.__variables:\n", + " print('%s=%i' % (v, self.Value(v)), end=' ')\n", + " print()\n", + "\n", + " def solution_count(self):\n", + " return self.__solution_count\n", + " # [END solution_printing]\n", + "\n", + "\n", + "def CPIsFunSat():\n", + " \"\"\"Solve the CP+IS+FUN==TRUE cryptarithm.\"\"\"\n", + " # Constraint programming engine\n", + " model = cp_model.CpModel()\n", + "\n", + " # [START variables]\n", + " base = 10\n", + "\n", + " c = model.NewIntVar(1, base - 1, 'C')\n", + " p = model.NewIntVar(0, base - 1, 'P')\n", + " i = model.NewIntVar(1, base - 1, 'I')\n", + " s = model.NewIntVar(0, base - 1, 'S')\n", + " f = model.NewIntVar(1, base - 1, 'F')\n", + " u = model.NewIntVar(0, base - 1, 'U')\n", + " n = model.NewIntVar(0, base - 1, 'N')\n", + " t = model.NewIntVar(1, base - 1, 'T')\n", + " r = model.NewIntVar(0, base - 1, 'R')\n", + " e = model.NewIntVar(0, base - 1, 'E')\n", + "\n", + " # We need to group variables in a list to use the constraint AllDifferent.\n", + " letters = [c, p, i, s, f, u, n, t, r, e]\n", + "\n", + " # Verify that we have enough digits.\n", + " assert base >= len(letters)\n", + " # [END variables]\n", + "\n", + " # [START constraints]\n", + " # Define constraints.\n", + " model.AddAllDifferent(letters)\n", + "\n", + " # CP + IS + FUN = TRUE\n", + " model.Add(c * base + p + i * base + s + f * base * base + u * base +\n", + " n == t * base * base * base + r * base * base + u * base + e)\n", + " # [END constraints]\n", + "\n", + " # [START solve]\n", + " ### Solve model.\n", + " solver = cp_model.CpSolver()\n", + " solution_printer = VarArraySolutionPrinter(letters)\n", + " status = solver.SearchForAllSolutions(model, solution_printer)\n", + " # [END solve]\n", + "\n", + " print()\n", + " print('Statistics')\n", + " print(' - status : %s' % solver.StatusName(status))\n", + " print(' - conflicts : %i' % solver.NumConflicts())\n", + " print(' - branches : %i' % solver.NumBranches())\n", + " print(' - wall time : %f s' % solver.WallTime())\n", + " print(' - solutions found : %i' % solution_printer.solution_count())\n", + "\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/sat/earliness_tardiness_cost_sample_sat.ipynb b/examples/notebook/sat/earliness_tardiness_cost_sample_sat.ipynb new file mode 100644 index 0000000000..3ca6988a1a --- /dev/null +++ b/examples/notebook/sat/earliness_tardiness_cost_sample_sat.ipynb @@ -0,0 +1,108 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010-2018 Google LLC\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"Encodes an convex piecewise linear function.\"\"\"\n", + "\n", + "from __future__ import absolute_import\n", + "from __future__ import division\n", + "from __future__ import print_function\n", + "\n", + "from ortools.sat.python import cp_model\n", + "\n", + "\n", + "class VarArraySolutionPrinter(cp_model.CpSolverSolutionCallback):\n", + " \"\"\"Print intermediate solutions.\"\"\"\n", + "\n", + " def __init__(self, variables):\n", + " cp_model.CpSolverSolutionCallback.__init__(self)\n", + " self.__variables = variables\n", + " self.__solution_count = 0\n", + "\n", + " def on_solution_callback(self):\n", + " self.__solution_count += 1\n", + " for v in self.__variables:\n", + " print('%s=%i' % (v, self.Value(v)), end=' ')\n", + " print()\n", + "\n", + " def solution_count(self):\n", + " return self.__solution_count\n", + "\n", + "\n", + "def earliness_tardiness_cost_sample_sat():\n", + " \"\"\"Encode the piecewise linear expression.\"\"\"\n", + "\n", + " earliness_date = 5 # ed.\n", + " earliness_cost = 8\n", + " lateness_date = 15 # ld.\n", + " lateness_cost = 12\n", + "\n", + " # Model.\n", + " model = cp_model.CpModel()\n", + "\n", + " # Declare our primary variable.\n", + " x = model.NewIntVar(0, 20, 'x')\n", + "\n", + " # Create the expression variable and implement the piecewise linear function.\n", + " #\n", + " # \\ /\n", + " # \\______/\n", + " # ed ld\n", + " #\n", + " large_constant = 1000\n", + " expr = model.NewIntVar(0, large_constant, 'expr')\n", + "\n", + " # First segment.\n", + " s1 = model.NewIntVar(-large_constant, large_constant, 's1')\n", + " model.Add(s1 == earliness_cost * (earliness_date - x))\n", + "\n", + " # Second segment.\n", + " s2 = 0\n", + "\n", + " # Third segment.\n", + " s3 = model.NewIntVar(-large_constant, large_constant, 's3')\n", + " model.Add(s3 == lateness_cost * (x - lateness_date))\n", + "\n", + " # Link together expr and x through s1, s2, and s3.\n", + " model.AddMaxEquality(expr, [s1, s2, s3])\n", + "\n", + " # Search for x values in increasing order.\n", + " model.AddDecisionStrategy([x], cp_model.CHOOSE_FIRST,\n", + " cp_model.SELECT_MIN_VALUE)\n", + "\n", + " # Create a solver and solve with a fixed search.\n", + " solver = cp_model.CpSolver()\n", + "\n", + " # Force the solver to follow the decision strategy exactly.\n", + " solver.parameters.search_branching = cp_model.FIXED_SEARCH\n", + "\n", + " # Search and print out all solutions.\n", + " solution_printer = VarArraySolutionPrinter([x, expr])\n", + " solver.SearchForAllSolutions(model, solution_printer)\n", + "\n", + "\n", + "earliness_tardiness_cost_sample_sat()\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/sat/interval_sample_sat.ipynb b/examples/notebook/sat/interval_sample_sat.ipynb new file mode 100644 index 0000000000..07418a3fed --- /dev/null +++ b/examples/notebook/sat/interval_sample_sat.ipynb @@ -0,0 +1,52 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010-2018 Google LLC\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"Code sample to demonstrates how to build an interval.\"\"\"\n", + "\n", + "from __future__ import absolute_import\n", + "from __future__ import division\n", + "from __future__ import print_function\n", + "\n", + "from ortools.sat.python import cp_model\n", + "\n", + "\n", + "def IntervalSampleSat():\n", + " model = cp_model.CpModel()\n", + "\n", + " horizon = 100\n", + " start_var = model.NewIntVar(0, horizon, 'start')\n", + " duration = 10 # Python cp/sat code accept integer variables or constants.\n", + " end_var = model.NewIntVar(0, horizon, 'end')\n", + " interval_var = model.NewIntervalVar(start_var, duration, end_var,\n", + " 'interval')\n", + "\n", + " print('start = %s, duration = %i, end = %s, interval = %s' %\n", + " (start_var, duration, end_var, interval_var))\n", + "\n", + "\n", + "IntervalSampleSat()\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/sat/literal_sample_sat.ipynb b/examples/notebook/sat/literal_sample_sat.ipynb new file mode 100644 index 0000000000..c2b0b94974 --- /dev/null +++ b/examples/notebook/sat/literal_sample_sat.ipynb @@ -0,0 +1,46 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010-2018 Google LLC\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"Code sample to demonstrate Boolean variable and literals.\"\"\"\n", + "\n", + "from __future__ import absolute_import\n", + "from __future__ import division\n", + "from __future__ import print_function\n", + "\n", + "from ortools.sat.python import cp_model\n", + "\n", + "\n", + "def LiteralSampleSat():\n", + " model = cp_model.CpModel()\n", + " x = model.NewBoolVar('x')\n", + " not_x = x.Not()\n", + " print(x)\n", + " print(not_x)\n", + "\n", + "\n", + "LiteralSampleSat()\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/sat/minimal_jobshop_sat.ipynb b/examples/notebook/sat/minimal_jobshop_sat.ipynb new file mode 100644 index 0000000000..da3d8c2e2f --- /dev/null +++ b/examples/notebook/sat/minimal_jobshop_sat.ipynb @@ -0,0 +1,159 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010-2018 Google LLC\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"Minimal jobshop example.\"\"\"\n", + "\n", + "# [START program]\n", + "from __future__ import print_function\n", + "\n", + "import collections\n", + "\n", + "# [START model]\n", + "# Import Python wrapper for or-tools CP-SAT solver.\n", + "from ortools.sat.python import cp_model\n", + "\n", + "\n", + "def MinimalJobshopSat():\n", + " \"\"\"Minimal jobshop problem.\"\"\"\n", + " # Create the model.\n", + " model = cp_model.CpModel()\n", + " # [END model]\n", + "\n", + " # [START data]\n", + " jobs_data = [ # task = (machine_id, processing_time).\n", + " [(0, 3), (1, 2), (2, 2)], # Job0\n", + " [(0, 2), (2, 1), (1, 4)], # Job1\n", + " [(1, 4), (2, 3)] # Job2\n", + " ]\n", + "\n", + " machines_count = 1 + max(task[0] for job in jobs_data for task in job)\n", + " all_machines = range(machines_count)\n", + " # [END data]\n", + "\n", + " # Computes horizon dynamically as the sum of all durations.\n", + " horizon = sum(task[1] for job in jobs_data for task in job)\n", + "\n", + " # [START variables]\n", + " # Named tuple to store information about created variables.\n", + " task_type = collections.namedtuple('task_type', 'start end interval')\n", + " # Named tuple to manipulate solution information.\n", + " assigned_task_type = collections.namedtuple('assigned_task_type',\n", + " 'start job index duration')\n", + "\n", + " # Creates job intervals and add to the corresponding machine lists.\n", + " all_tasks = {}\n", + " machine_to_intervals = collections.defaultdict(list)\n", + "\n", + " for job_id, job in enumerate(jobs_data):\n", + " for task_id, task in enumerate(job):\n", + " machine = task[0]\n", + " duration = task[1]\n", + " suffix = '_%i_%i' % (job_id, task_id)\n", + " start_var = model.NewIntVar(0, horizon, 'start' + suffix)\n", + " end_var = model.NewIntVar(0, horizon, 'end' + suffix)\n", + " interval_var = model.NewIntervalVar(start_var, duration, end_var,\n", + " 'interval' + suffix)\n", + " all_tasks[job_id, task_id] = task_type(\n", + " start=start_var, end=end_var, interval=interval_var)\n", + " machine_to_intervals[machine].append(interval_var)\n", + " # [END variables]\n", + "\n", + " # [START constraints]\n", + " # Create and add disjunctive constraints.\n", + " for machine in all_machines:\n", + " model.AddNoOverlap(machine_to_intervals[machine])\n", + "\n", + " # Precedences inside a job.\n", + " for job_id, job in enumerate(jobs_data):\n", + " for task_id in range(len(job) - 1):\n", + " model.Add(all_tasks[job_id, task_id +\n", + " 1].start >= all_tasks[job_id, task_id].end)\n", + " # [END constraints]\n", + "\n", + " # [START objective]\n", + " # Makespan objective.\n", + " obj_var = model.NewIntVar(0, horizon, 'makespan')\n", + " model.AddMaxEquality(obj_var, [\n", + " all_tasks[job_id, len(job) - 1].end\n", + " for job_id, job in enumerate(jobs_data)\n", + " ])\n", + " model.Minimize(obj_var)\n", + " # [END objective]\n", + "\n", + " # [START solver]\n", + " # Solve model.\n", + " solver = cp_model.CpSolver()\n", + " status = solver.Solve(model)\n", + " # [END solver]\n", + "\n", + " if status == cp_model.OPTIMAL:\n", + " # [START solution_printing]\n", + " # Create one list of assigned tasks per machine.\n", + " assigned_jobs = collections.defaultdict(list)\n", + " for job_id, job in enumerate(jobs_data):\n", + " for task_id, task in enumerate(job):\n", + " machine = task[0]\n", + " assigned_jobs[machine].append(\n", + " assigned_task_type(\n", + " start=solver.Value(all_tasks[job_id, task_id].start),\n", + " job=job_id,\n", + " index=task_id,\n", + " duration=task[1]))\n", + "\n", + " # Create per machine output lines.\n", + " output = ''\n", + " for machine in all_machines:\n", + " # Sort by starting time.\n", + " assigned_jobs[machine].sort()\n", + " sol_line_tasks = 'Machine ' + str(machine) + ': '\n", + " sol_line = ' '\n", + "\n", + " for assigned_task in assigned_jobs[machine]:\n", + " name = 'job_%i_%i' % (assigned_task.job, assigned_task.index)\n", + " # Add spaces to output to align columns.\n", + " sol_line_tasks += '%-10s' % name\n", + "\n", + " start = assigned_task.start\n", + " duration = assigned_task.duration\n", + " sol_tmp = '[%i,%i]' % (start, start + duration)\n", + " # Add spaces to output to align columns.\n", + " sol_line += '%-10s' % sol_tmp\n", + "\n", + " sol_line += '\\n'\n", + " sol_line_tasks += '\\n'\n", + " output += sol_line_tasks\n", + " output += sol_line\n", + "\n", + " # Finally print the solution found.\n", + " print('Optimal Schedule Length: %i' % solver.ObjectiveValue())\n", + " print(output)\n", + " # [END solution_printing]\n", + "\n", + "\n", + "MinimalJobshopSat()\n", + "# [END program]\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/sat/multiple_knapsack_sat.ipynb b/examples/notebook/sat/multiple_knapsack_sat.ipynb new file mode 100644 index 0000000000..16cd917f86 --- /dev/null +++ b/examples/notebook/sat/multiple_knapsack_sat.ipynb @@ -0,0 +1,139 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010-2018 Google LLC\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "# [START program]\n", + "\"\"\"Solves a multiple knapsack problem using the CP-SAT solver.\"\"\"\n", + "\n", + "# [START import]\n", + "from __future__ import absolute_import\n", + "from __future__ import division\n", + "from __future__ import print_function\n", + "\n", + "from ortools.sat.python import cp_model\n", + "\n", + "# [END import]\n", + "\n", + "\n", + "# [START data_model]\n", + "def create_data_model():\n", + " \"\"\"Create the data for the example.\"\"\"\n", + " data = {}\n", + " weights = [48, 30, 42, 36, 36, 48, 42, 42, 36, 24, 30, 30, 42, 36, 36]\n", + " values = [10, 30, 25, 50, 35, 30, 15, 40, 30, 35, 45, 10, 20, 30, 25]\n", + " data['num_items'] = len(weights)\n", + " data['all_items'] = range(data['num_items'])\n", + " data['weights'] = weights\n", + " data['values'] = values\n", + " data['bin_capacities'] = [100, 100, 100, 100, 100]\n", + " data['num_bins'] = len(data['bin_capacities'])\n", + " data['all_bins'] = range(data['num_bins'])\n", + " return data\n", + "\n", + "\n", + "# [END data_model]\n", + "\n", + "\n", + "# [START solution_printer]\n", + "def print_solutions(data, solver, x):\n", + " \"\"\"Display the solution.\"\"\"\n", + " total_weight = 0\n", + " total_value = 0\n", + " for b in data['all_bins']:\n", + " print('Bin', b, '\\n')\n", + " bin_weight = 0\n", + " bin_value = 0\n", + " for idx, val in enumerate(data['weights']):\n", + " if solver.Value(x[(idx, b)]) > 0:\n", + " print('Item', idx, '- Weight:', val, ' Value:',\n", + " data['values'][idx])\n", + " bin_weight += val\n", + " bin_value += data['values'][idx]\n", + " print('Packed bin weight:', bin_weight)\n", + " print('Packed bin value:', bin_value, '\\n')\n", + " total_weight += bin_weight\n", + " total_value += bin_value\n", + " print('Total packed weight:', total_weight)\n", + " print('Total packed value:', total_value)\n", + "\n", + "\n", + "# [END solution_printer]\n", + "\n", + "\n", + "# [START data]\n", + "data = create_data_model()\n", + "# [END data]\n", + "\n", + "# [START model]\n", + "model = cp_model.CpModel()\n", + "# [END model]\n", + "\n", + "# Main variables.\n", + "# [START variables]\n", + "x = {}\n", + "for idx in data['all_items']:\n", + " for b in data['all_bins']:\n", + " x[(idx, b)] = model.NewIntVar(0, 1, 'x_%i_%i' % (idx, b))\n", + "max_value = sum(data['values'])\n", + "# value[b] is the value of bin b when packed.\n", + "value = [\n", + " model.NewIntVar(0, max_value, 'value_%i' % b) for b in data['all_bins']\n", + "]\n", + "for b in data['all_bins']:\n", + " model.Add(value[b] == sum(\n", + " x[(i, b)] * data['values'][i] for i in data['all_items']))\n", + "# [END variables]\n", + "\n", + "# [START constraints]\n", + "# Each item can be in at most one bin.\n", + "for idx in data['all_items']:\n", + " model.Add(sum(x[idx, b] for b in data['all_bins']) <= 1)\n", + "\n", + "# The amount packed in each bin cannot exceed its capacity.\n", + "for b in data['all_bins']:\n", + " model.Add(\n", + " sum(x[(i, b)] * data['weights'][i]\n", + " for i in data['all_items']) <= data['bin_capacities'][b])\n", + "# [END constraints]\n", + "\n", + "# [START objective]\n", + "# Maximize total value of packed items.\n", + "model.Maximize(sum(value))\n", + "# [END objective]\n", + "\n", + "# [START solver]\n", + "solver = cp_model.CpSolver()\n", + "# [END solver]\n", + "\n", + "# [START solve]\n", + "status = solver.Solve(model)\n", + "# [END solve]\n", + "\n", + "# [START print_solution]\n", + "if status == cp_model.OPTIMAL:\n", + " print_solutions(data, solver, x)\n", + "# [END solutions_printer]\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/sat/no_overlap_sample_sat.ipynb b/examples/notebook/sat/no_overlap_sample_sat.ipynb new file mode 100644 index 0000000000..0582f7f7c9 --- /dev/null +++ b/examples/notebook/sat/no_overlap_sample_sat.ipynb @@ -0,0 +1,88 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010-2018 Google LLC\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"Code sample to demonstrate how to build a NoOverlap constraint.\"\"\"\n", + "\n", + "from __future__ import absolute_import\n", + "from __future__ import division\n", + "from __future__ import print_function\n", + "\n", + "from ortools.sat.python import cp_model\n", + "\n", + "\n", + "def NoOverlapSampleSat():\n", + " \"\"\"No overlap sample with fixed activities.\"\"\"\n", + " model = cp_model.CpModel()\n", + " horizon = 21 # 3 weeks.\n", + "\n", + " # Task 0, duration 2.\n", + " start_0 = model.NewIntVar(0, horizon, 'start_0')\n", + " duration_0 = 2 # Python cp/sat code accepts integer variables or constants.\n", + " end_0 = model.NewIntVar(0, horizon, 'end_0')\n", + " task_0 = model.NewIntervalVar(start_0, duration_0, end_0, 'task_0')\n", + " # Task 1, duration 4.\n", + " start_1 = model.NewIntVar(0, horizon, 'start_1')\n", + " duration_1 = 4 # Python cp/sat code accepts integer variables or constants.\n", + " end_1 = model.NewIntVar(0, horizon, 'end_1')\n", + " task_1 = model.NewIntervalVar(start_1, duration_1, end_1, 'task_1')\n", + "\n", + " # Task 2, duration 3.\n", + " start_2 = model.NewIntVar(0, horizon, 'start_2')\n", + " duration_2 = 3 # Python cp/sat code accepts integer variables or constants.\n", + " end_2 = model.NewIntVar(0, horizon, 'end_2')\n", + " task_2 = model.NewIntervalVar(start_2, duration_2, end_2, 'task_2')\n", + "\n", + " # Weekends.\n", + " weekend_0 = model.NewIntervalVar(5, 2, 7, 'weekend_0')\n", + " weekend_1 = model.NewIntervalVar(12, 2, 14, 'weekend_1')\n", + " weekend_2 = model.NewIntervalVar(19, 2, 21, 'weekend_2')\n", + "\n", + " # No Overlap constraint.\n", + " model.AddNoOverlap(\n", + " [task_0, task_1, task_2, weekend_0, weekend_1, weekend_2])\n", + "\n", + " # Makespan objective.\n", + " obj = model.NewIntVar(0, horizon, 'makespan')\n", + " model.AddMaxEquality(obj, [end_0, end_1, end_2])\n", + " model.Minimize(obj)\n", + "\n", + " # Solve model.\n", + " solver = cp_model.CpSolver()\n", + " status = solver.Solve(model)\n", + "\n", + " if status == cp_model.OPTIMAL:\n", + " # Print out makespan and the start times for all tasks.\n", + " print('Optimal Schedule Length: %i' % solver.ObjectiveValue())\n", + " print('Task 0 starts at %i' % solver.Value(start_0))\n", + " print('Task 1 starts at %i' % solver.Value(start_1))\n", + " print('Task 2 starts at %i' % solver.Value(start_2))\n", + " else:\n", + " print('Solver exited with nonoptimal status: %i' % status)\n", + "\n", + "\n", + "NoOverlapSampleSat()\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/sat/nurses_sat.ipynb b/examples/notebook/sat/nurses_sat.ipynb new file mode 100644 index 0000000000..9e79a4330a --- /dev/null +++ b/examples/notebook/sat/nurses_sat.ipynb @@ -0,0 +1,147 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010-2018 Google LLC\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"Example of a simple nurse scheduling problem.\"\"\"\n", + "\n", + "# [START program]\n", + "# [START import]\n", + "from __future__ import print_function\n", + "from ortools.sat.python import cp_model\n", + "\n", + "# [END import]\n", + "\n", + "\n", + "# [START solution_printer]\n", + "class NursesPartialSolutionPrinter(cp_model.CpSolverSolutionCallback):\n", + " \"\"\"Print intermediate solutions.\"\"\"\n", + "\n", + " def __init__(self, shifts, num_nurses, num_days, num_shifts, sols):\n", + " cp_model.CpSolverSolutionCallback.__init__(self)\n", + " self._shifts = shifts\n", + " self._num_nurses = num_nurses\n", + " self._num_days = num_days\n", + " self._num_shifts = num_shifts\n", + " self._solutions = set(sols)\n", + " self._solution_count = 0\n", + "\n", + " def on_solution_callback(self):\n", + " if self._solution_count in self._solutions:\n", + " print('Solution %i' % self._solution_count)\n", + " for d in range(self._num_days):\n", + " print('Day %i' % d)\n", + " for n in range(self._num_nurses):\n", + " is_working = False\n", + " for s in range(self._num_shifts):\n", + " if self.Value(self._shifts[(n, d, s)]):\n", + " is_working = True\n", + " print(' Nurse %i works shift %i' % (n, s))\n", + " if not is_working:\n", + " print(' Nurse {} does not work'.format(n))\n", + " print()\n", + " self._solution_count += 1\n", + "\n", + " def solution_count(self):\n", + " return self._solution_count\n", + "\n", + "\n", + "# [END solution_printer]\n", + "\n", + "\n", + "# Data.\n", + "# [START data]\n", + "num_nurses = 4\n", + "num_shifts = 3\n", + "num_days = 3\n", + "all_nurses = range(num_nurses)\n", + "all_shifts = range(num_shifts)\n", + "all_days = range(num_days)\n", + "# [END data]\n", + "# Creates the model.\n", + "# [START model]\n", + "model = cp_model.CpModel()\n", + "# [END model]\n", + "\n", + "# Creates shift variables.\n", + "# shifts[(n, d, s)]: nurse 'n' works shift 's' on day 'd'.\n", + "# [START variables]\n", + "shifts = {}\n", + "for n in all_nurses:\n", + " for d in all_days:\n", + " for s in all_shifts:\n", + " shifts[(n, d,\n", + " s)] = model.NewBoolVar('shift_n%id%is%i' % (n, d, s))\n", + "# [END variables]\n", + "\n", + "# Each shift is assigned to exactly one nurse in the schedule period.\n", + "# [START exactly_one_nurse]\n", + "for d in all_days:\n", + " for s in all_shifts:\n", + " model.Add(sum(shifts[(n, d, s)] for n in all_nurses) == 1)\n", + "# [END exactly_one_nurse]\n", + "\n", + "# Each nurse works at most one shift per day.\n", + "# [START at_most_one_shift]\n", + "for n in all_nurses:\n", + " for d in all_days:\n", + " model.Add(sum(shifts[(n, d, s)] for s in all_shifts) <= 1)\n", + "# [END at_most_one_shift]\n", + "\n", + "# [START assign_nurses_evenly]\n", + "# min_shifts_per_nurse is the largest integer such that every nurse\n", + "# can be assigned at least that many shifts. If the number of nurses doesn't\n", + "# divide the total number of shifts over the schedule period,\n", + "# some nurses have to work one more shift, for a total of\n", + "# min_shifts_per_nurse + 1.\n", + "min_shifts_per_nurse = (num_shifts * num_days) // num_nurses\n", + "max_shifts_per_nurse = min_shifts_per_nurse + 1\n", + "for n in all_nurses:\n", + " num_shifts_worked = sum(\n", + " shifts[(n, d, s)] for d in all_days for s in all_shifts)\n", + " model.Add(min_shifts_per_nurse <= num_shifts_worked)\n", + " model.Add(num_shifts_worked <= max_shifts_per_nurse)\n", + "# [END assign_nurses_evenly]\n", + "\n", + "# Creates the solver and solve.\n", + "# [START solve]\n", + "solver = cp_model.CpSolver()\n", + "solver.parameters.linearization_level = 0\n", + "# Display the first five solutions.\n", + "a_few_solutions = range(5)\n", + "solution_printer = NursesPartialSolutionPrinter(shifts, num_nurses,\n", + " num_days, num_shifts,\n", + " a_few_solutions)\n", + "solver.SearchForAllSolutions(model, solution_printer)\n", + "# [END solve]\n", + "\n", + "# Statistics.\n", + "print()\n", + "print('Statistics')\n", + "print(' - conflicts : %i' % solver.NumConflicts())\n", + "print(' - branches : %i' % solver.NumBranches())\n", + "print(' - wall time : %f s' % solver.WallTime())\n", + "print(' - solutions found : %i' % solution_printer.solution_count())\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/sat/optional_interval_sample_sat.ipynb b/examples/notebook/sat/optional_interval_sample_sat.ipynb new file mode 100644 index 0000000000..cc91604819 --- /dev/null +++ b/examples/notebook/sat/optional_interval_sample_sat.ipynb @@ -0,0 +1,54 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010-2018 Google LLC\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"Code sample to demonstrates how to build an optional interval.\"\"\"\n", + "\n", + "from __future__ import absolute_import\n", + "from __future__ import division\n", + "from __future__ import print_function\n", + "\n", + "from ortools.sat.python import cp_model\n", + "\n", + "\n", + "def OptionalIntervalSampleSat():\n", + " \"\"\"Build an optional interval.\"\"\"\n", + " model = cp_model.CpModel()\n", + "\n", + " horizon = 100\n", + " start_var = model.NewIntVar(0, horizon, 'start')\n", + " duration = 10 # Python cp/sat code accept integer variables or constants.\n", + " end_var = model.NewIntVar(0, horizon, 'end')\n", + " presence_var = model.NewBoolVar('presence')\n", + " interval_var = model.NewOptionalIntervalVar(start_var, duration, end_var,\n", + " presence_var, 'interval')\n", + "\n", + " print('start = %s, duration = %i, end = %s, presence = %s, interval = %s' %\n", + " (start_var, duration, end_var, presence_var, interval_var))\n", + "\n", + "\n", + "OptionalIntervalSampleSat()\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/sat/rabbits_and_pheasants_sat.ipynb b/examples/notebook/sat/rabbits_and_pheasants_sat.ipynb new file mode 100644 index 0000000000..df3f56f376 --- /dev/null +++ b/examples/notebook/sat/rabbits_and_pheasants_sat.ipynb @@ -0,0 +1,59 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010-2018 Google LLC\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"Rabbits and Pheasants quizz.\"\"\"\n", + "\n", + "from __future__ import absolute_import\n", + "from __future__ import division\n", + "from __future__ import print_function\n", + "\n", + "from ortools.sat.python import cp_model\n", + "\n", + "\n", + "def RabbitsAndPheasantsSat():\n", + " \"\"\"Solves the rabbits + pheasants problem.\"\"\"\n", + " model = cp_model.CpModel()\n", + "\n", + " r = model.NewIntVar(0, 100, 'r')\n", + " p = model.NewIntVar(0, 100, 'p')\n", + "\n", + " # 20 heads.\n", + " model.Add(r + p == 20)\n", + " # 56 legs.\n", + " model.Add(4 * r + 2 * p == 56)\n", + "\n", + " # Solves and prints out the solution.\n", + " solver = cp_model.CpSolver()\n", + " status = solver.Solve(model)\n", + "\n", + " if status == cp_model.FEASIBLE:\n", + " print('%i rabbits and %i pheasants' %\n", + " (solver.Value(r), solver.Value(p)))\n", + "\n", + "\n", + "RabbitsAndPheasantsSat()\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/sat/ranking_sample_sat.ipynb b/examples/notebook/sat/ranking_sample_sat.ipynb new file mode 100644 index 0000000000..6d433d7831 --- /dev/null +++ b/examples/notebook/sat/ranking_sample_sat.ipynb @@ -0,0 +1,173 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010-2018 Google LLC\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"Code sample to demonstrates how to rank intervals.\"\"\"\n", + "\n", + "from __future__ import print_function\n", + "\n", + "from ortools.sat.python import cp_model\n", + "\n", + "\n", + "def RankTasks(model, starts, presences, ranks):\n", + " \"\"\"This method adds constraints and variables to links tasks and ranks.\n", + "\n", + " This method assumes that all starts are disjoint, meaning that all tasks have\n", + " a strictly positive duration, and they appear in the same NoOverlap\n", + " constraint.\n", + "\n", + " Args:\n", + " model: The CpModel to add the constraints to.\n", + " starts: The array of starts variables of all tasks.\n", + " presences: The array of presence variables of all tasks.\n", + " ranks: The array of rank variables of all tasks.\n", + " \"\"\"\n", + "\n", + " num_tasks = len(starts)\n", + " all_tasks = range(num_tasks)\n", + "\n", + " # Creates precedence variables between pairs of intervals.\n", + " precedences = {}\n", + " for i in all_tasks:\n", + " for j in all_tasks:\n", + " if i == j:\n", + " precedences[(i, j)] = presences[i]\n", + " else:\n", + " prec = model.NewBoolVar('%i before %i' % (i, j))\n", + " precedences[(i, j)] = prec\n", + " model.Add(starts[i] < starts[j]).OnlyEnforceIf(prec)\n", + "\n", + " # Treats optional intervals.\n", + " for i in range(num_tasks - 1):\n", + " for j in range(i + 1, num_tasks):\n", + " tmp_array = [precedences[(i, j)], precedences[(j, i)]]\n", + " if presences[i] != 1:\n", + " tmp_array.append(presences[i].Not())\n", + " # Makes sure that if i is not performed, all precedences are false.\n", + " model.AddImplication(presences[i].Not(),\n", + " precedences[(i, j)].Not())\n", + " model.AddImplication(presences[i].Not(),\n", + " precedences[(j, i)].Not())\n", + " if presences[j] != 1:\n", + " tmp_array.append(presences[j].Not())\n", + " # Makes sure that if j is not performed, all precedences are false.\n", + " model.AddImplication(presences[j].Not(),\n", + " precedences[(i, j)].Not())\n", + " model.AddImplication(presences[j].Not(),\n", + " precedences[(j, i)].Not())\n", + " # The following bool_or will enforce that for any two intervals:\n", + " # i precedes j or j precedes i or at least one interval is not\n", + " # performed.\n", + " model.AddBoolOr(tmp_array)\n", + " # Redundant constraint: it propagates early that at most one precedence\n", + " # is true.\n", + " model.AddImplication(precedences[(i, j)], precedences[(j, i)].Not())\n", + " model.AddImplication(precedences[(j, i)], precedences[(i, j)].Not())\n", + "\n", + " # Links precedences and ranks.\n", + " for i in all_tasks:\n", + " model.Add(ranks[i] == sum(precedences[(j, i)] for j in all_tasks) - 1)\n", + "\n", + "\n", + "def RankingSampleSat():\n", + " \"\"\"Ranks tasks in a NoOverlap constraint.\"\"\"\n", + "\n", + " model = cp_model.CpModel()\n", + " horizon = 100\n", + " num_tasks = 4\n", + " all_tasks = range(num_tasks)\n", + "\n", + " starts = []\n", + " ends = []\n", + " intervals = []\n", + " presences = []\n", + " ranks = []\n", + "\n", + " # Creates intervals, half of them are optional.\n", + " for t in all_tasks:\n", + " start = model.NewIntVar(0, horizon, 'start_%i' % t)\n", + " duration = t + 1\n", + " end = model.NewIntVar(0, horizon, 'end_%i' % t)\n", + " if t < num_tasks // 2:\n", + " interval = model.NewIntervalVar(start, duration, end,\n", + " 'interval_%i' % t)\n", + " presence = True\n", + " else:\n", + " presence = model.NewBoolVar('presence_%i' % t)\n", + " interval = model.NewOptionalIntervalVar(start, duration, end,\n", + " presence,\n", + " 'o_interval_%i' % t)\n", + " starts.append(start)\n", + " ends.append(end)\n", + " intervals.append(interval)\n", + " presences.append(presence)\n", + "\n", + " # Ranks = -1 if and only if the tasks is not performed.\n", + " ranks.append(model.NewIntVar(-1, num_tasks - 1, 'rank_%i' % t))\n", + "\n", + " # Adds NoOverlap constraint.\n", + " model.AddNoOverlap(intervals)\n", + "\n", + " # Adds ranking constraint.\n", + " RankTasks(model, starts, presences, ranks)\n", + "\n", + " # Adds a constraint on ranks.\n", + " model.Add(ranks[0] < ranks[1])\n", + "\n", + " # Creates makespan variable.\n", + " makespan = model.NewIntVar(0, horizon, 'makespan')\n", + " for t in all_tasks:\n", + " if presences[t] == 1:\n", + " model.Add(ends[t] <= makespan)\n", + " else:\n", + " model.Add(ends[t] <= makespan).OnlyEnforceIf(presences[t])\n", + "\n", + " # Minimizes makespan - fixed gain per tasks performed.\n", + " # As the fixed cost is less that the duration of the last interval,\n", + " # the solver will not perform the last interval.\n", + " model.Minimize(2 * makespan - 7 * sum(presences[t] for t in all_tasks))\n", + "\n", + " # Solves the model model.\n", + " solver = cp_model.CpSolver()\n", + " status = solver.Solve(model)\n", + "\n", + " if status == cp_model.OPTIMAL:\n", + " # Prints out the makespan and the start times and ranks of all tasks.\n", + " print('Optimal cost: %i' % solver.ObjectiveValue())\n", + " print('Makespan: %i' % solver.Value(makespan))\n", + " for t in all_tasks:\n", + " if solver.Value(presences[t]):\n", + " print('Task %i starts at %i with rank %i' %\n", + " (t, solver.Value(starts[t]), solver.Value(ranks[t])))\n", + " else:\n", + " print('Task %i in not performed and ranked at %i' %\n", + " (t, solver.Value(ranks[t])))\n", + " else:\n", + " print('Solver exited with nonoptimal status: %i' % status)\n", + "\n", + "\n", + "RankingSampleSat()\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/sat/reified_sample_sat.ipynb b/examples/notebook/sat/reified_sample_sat.ipynb new file mode 100644 index 0000000000..c9b43cbcb6 --- /dev/null +++ b/examples/notebook/sat/reified_sample_sat.ipynb @@ -0,0 +1,58 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010-2018 Google LLC\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"Simple model with a reified constraint.\"\"\"\n", + "\n", + "from __future__ import absolute_import\n", + "from __future__ import division\n", + "from __future__ import print_function\n", + "\n", + "from ortools.sat.python import cp_model\n", + "\n", + "\n", + "def ReifiedSampleSat():\n", + " \"\"\"Showcase creating a reified constraint.\"\"\"\n", + " model = cp_model.CpModel()\n", + "\n", + " x = model.NewBoolVar('x')\n", + " y = model.NewBoolVar('y')\n", + " b = model.NewBoolVar('b')\n", + "\n", + " # First version using a half-reified bool and.\n", + " model.AddBoolAnd([x, y.Not()]).OnlyEnforceIf(b)\n", + "\n", + " # Second version using implications.\n", + " model.AddImplication(b, x)\n", + " model.AddImplication(b, y.Not())\n", + "\n", + " # Third version using bool or.\n", + " model.AddBoolOr([b.Not(), x])\n", + " model.AddBoolOr([b.Not(), y.Not()])\n", + "\n", + "\n", + "ReifiedSampleSat()\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/sat/schedule_requests_sat.ipynb b/examples/notebook/sat/schedule_requests_sat.ipynb new file mode 100644 index 0000000000..1f47f2ed36 --- /dev/null +++ b/examples/notebook/sat/schedule_requests_sat.ipynb @@ -0,0 +1,130 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010-2018 Google LLC\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"Nurse scheduling problem with shift requests.\"\"\"\n", + "\n", + "# [START program]\n", + "# [START import]\n", + "from __future__ import print_function\n", + "from ortools.sat.python import cp_model\n", + "# [END import]\n", + "\n", + "\n", + "# This program tries to find an optimal assignment of nurses to shifts\n", + "# (3 shifts per day, for 7 days), subject to some constraints (see below).\n", + "# Each nurse can request to be assigned to specific shifts.\n", + "# The optimal assignment maximizes the number of fulfilled shift requests.\n", + "# [START data]\n", + "num_nurses = 5\n", + "num_shifts = 3\n", + "num_days = 7\n", + "all_nurses = range(num_nurses)\n", + "all_shifts = range(num_shifts)\n", + "all_days = range(num_days)\n", + "shift_requests = [[[0, 0, 1], [0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 1],\n", + " [0, 1, 0], [0, 0, 1]],\n", + " [[0, 0, 0], [0, 0, 0], [0, 1, 0], [0, 1, 0], [1, 0, 0],\n", + " [0, 0, 0], [0, 0, 1]],\n", + " [[0, 1, 0], [0, 1, 0], [0, 0, 0], [1, 0, 0], [0, 0, 0],\n", + " [0, 1, 0], [0, 0, 0]],\n", + " [[0, 0, 1], [0, 0, 0], [1, 0, 0], [0, 1, 0], [0, 0, 0],\n", + " [1, 0, 0], [0, 0, 0]],\n", + " [[0, 0, 0], [0, 0, 1], [0, 1, 0], [0, 0, 0], [1, 0, 0],\n", + " [0, 1, 0], [0, 0, 0]]]\n", + "# [END data]\n", + "# Creates the model.\n", + "# [START model]\n", + "model = cp_model.CpModel()\n", + "# [END model]\n", + "\n", + "# Creates shift variables.\n", + "# shifts[(n, d, s)]: nurse 'n' works shift 's' on day 'd'.\n", + "# [START variables]\n", + "shifts = {}\n", + "for n in all_nurses:\n", + " for d in all_days:\n", + " for s in all_shifts:\n", + " shifts[(n, d,\n", + " s)] = model.NewBoolVar('shift_n%id%is%i' % (n, d, s))\n", + "# [END variables]\n", + "\n", + "# Each shift is assigned to exactly one nurse in .\n", + "# [START exactly_one_nurse]\n", + "for d in all_days:\n", + " for s in all_shifts:\n", + " model.Add(sum(shifts[(n, d, s)] for n in all_nurses) == 1)\n", + "# [END exactly_one_nurse]\n", + "\n", + "# Each nurse works at most one shift per day.\n", + "# [START at_most_one_shift]\n", + "for n in all_nurses:\n", + " for d in all_days:\n", + " model.Add(sum(shifts[(n, d, s)] for s in all_shifts) <= 1)\n", + "# [END at_most_one_shift]\n", + "\n", + "# [START assign_nurses_evenly]\n", + "# min_shifts_assigned is the largest integer such that every nurse can be\n", + "# assigned at least that number of shifts.\n", + "min_shifts_per_nurse = (num_shifts * num_days) // num_nurses\n", + "max_shifts_per_nurse = min_shifts_per_nurse + 1\n", + "for n in all_nurses:\n", + " num_shifts_worked = sum(\n", + " shifts[(n, d, s)] for d in all_days for s in all_shifts)\n", + " model.Add(min_shifts_per_nurse <= num_shifts_worked)\n", + " model.Add(num_shifts_worked <= max_shifts_per_nurse)\n", + "# [END assign_nurses_evenly]\n", + "\n", + "# [START objective]\n", + "model.Maximize(\n", + " sum(shift_requests[n][d][s] * shifts[(n, d, s)] for n in all_nurses\n", + " for d in all_days for s in all_shifts))\n", + "# [END objective]\n", + "# Creates the solver and solve.\n", + "# [START solve]\n", + "solver = cp_model.CpSolver()\n", + "solver.Solve(model)\n", + "for d in all_days:\n", + " print('Day', d)\n", + " for n in all_nurses:\n", + " for s in all_shifts:\n", + " if solver.Value(shifts[(n, d, s)]) == 1:\n", + " if shift_requests[n][d][s] == 1:\n", + " print('Nurse', n, 'works shift', s, '(requested).')\n", + " else:\n", + " print('Nurse', n, 'works shift', s, '(not requested).')\n", + " print()\n", + "# [END solve]\n", + "\n", + "# Statistics.\n", + "# [START print_solution]\n", + "print()\n", + "print('Statistics')\n", + "print(' - Number of shift requests met = %i' % solver.ObjectiveValue(),\n", + " '(out of', num_nurses * min_shifts_per_nurse, ')')\n", + "print(' - wall time : %f s' % solver.WallTime())\n", + "# [END print_solution]\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/sat/scheduling_with_calendar_sample_sat.ipynb b/examples/notebook/sat/scheduling_with_calendar_sample_sat.ipynb new file mode 100644 index 0000000000..c2fbdb253e --- /dev/null +++ b/examples/notebook/sat/scheduling_with_calendar_sample_sat.ipynb @@ -0,0 +1,98 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010-2018 Google LLC\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"Code sample to demonstrate how an interval can span across a break.\"\"\"\n", + "\n", + "from __future__ import absolute_import\n", + "from __future__ import division\n", + "from __future__ import print_function\n", + "\n", + "from ortools.sat.python import cp_model\n", + "\n", + "\n", + "class VarArraySolutionPrinter(cp_model.CpSolverSolutionCallback):\n", + " \"\"\"Print intermediate solutions.\"\"\"\n", + "\n", + " def __init__(self, variables):\n", + " cp_model.CpSolverSolutionCallback.__init__(self)\n", + " self.__variables = variables\n", + " self.__solution_count = 0\n", + "\n", + " def on_solution_callback(self):\n", + " self.__solution_count += 1\n", + " for v in self.__variables:\n", + " print('%s=%i' % (v, self.Value(v)), end=' ')\n", + " print()\n", + "\n", + " def solution_count(self):\n", + " return self.__solution_count\n", + "\n", + "\n", + "def SchedulingWithCalendarSampleSat():\n", + " \"\"\"Interval spanning across a lunch break.\"\"\"\n", + " model = cp_model.CpModel()\n", + "\n", + " # The data is the following:\n", + " # Work starts at 8h, ends at 18h, with a lunch break between 13h and 14h.\n", + " # We need to schedule a task that needs 3 hours of processing time.\n", + " # Total duration can be 3 or 4 (if it spans the lunch break).\n", + " #\n", + " # Because the duration is at least 3 hours, work cannot start after 15h.\n", + " # Because of the break, work cannot start at 13h.\n", + "\n", + " start = model.NewIntVarFromDomain(\n", + " cp_model.Domain.FromIntervals([(8, 12), (14, 15)]), 'start')\n", + " duration = model.NewIntVar(3, 4, 'duration')\n", + " end = model.NewIntVar(8, 18, 'end')\n", + " unused_interval = model.NewIntervalVar(start, duration, end, 'interval')\n", + "\n", + " # We have 2 states (spanning across lunch or not)\n", + " across = model.NewBoolVar('across')\n", + " non_spanning_hours = cp_model.Domain.FromValues([8, 9, 10, 14, 15])\n", + " model.AddLinearExpressionInDomain(start, non_spanning_hours).OnlyEnforceIf(\n", + " across.Not())\n", + " model.AddLinearConstraint(start, 11, 12).OnlyEnforceIf(across)\n", + " model.Add(duration == 3).OnlyEnforceIf(across.Not())\n", + " model.Add(duration == 4).OnlyEnforceIf(across)\n", + "\n", + " # Search for x values in increasing order.\n", + " model.AddDecisionStrategy([start], cp_model.CHOOSE_FIRST,\n", + " cp_model.SELECT_MIN_VALUE)\n", + "\n", + " # Create a solver and solve with a fixed search.\n", + " solver = cp_model.CpSolver()\n", + "\n", + " # Force the solver to follow the decision strategy exactly.\n", + " solver.parameters.search_branching = cp_model.FIXED_SEARCH\n", + "\n", + " # Search and print all solutions.\n", + " solution_printer = VarArraySolutionPrinter([start, duration, across])\n", + " solver.SearchForAllSolutions(model, solution_printer)\n", + "\n", + "\n", + "SchedulingWithCalendarSampleSat()\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/sat/search_for_all_solutions_sample_sat.ipynb b/examples/notebook/sat/search_for_all_solutions_sample_sat.ipynb new file mode 100644 index 0000000000..4bc9c24bb4 --- /dev/null +++ b/examples/notebook/sat/search_for_all_solutions_sample_sat.ipynb @@ -0,0 +1,91 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010-2018 Google LLC\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"Code sample that solves a model and displays all solutions.\"\"\"\n", + "\n", + "# [START program]\n", + "from __future__ import absolute_import\n", + "from __future__ import division\n", + "from __future__ import print_function\n", + "\n", + "from ortools.sat.python import cp_model\n", + "\n", + "\n", + "# [START print_solution]\n", + "class VarArraySolutionPrinter(cp_model.CpSolverSolutionCallback):\n", + " \"\"\"Print intermediate solutions.\"\"\"\n", + "\n", + " def __init__(self, variables):\n", + " cp_model.CpSolverSolutionCallback.__init__(self)\n", + " self.__variables = variables\n", + " self.__solution_count = 0\n", + "\n", + " def on_solution_callback(self):\n", + " self.__solution_count += 1\n", + " for v in self.__variables:\n", + " print('%s=%i' % (v, self.Value(v)), end=' ')\n", + " print()\n", + "\n", + " def solution_count(self):\n", + " return self.__solution_count\n", + " # [END print_solution]\n", + "\n", + "\n", + "def SearchForAllSolutionsSampleSat():\n", + " \"\"\"Showcases calling the solver to search for all solutions.\"\"\"\n", + " # Creates the model.\n", + " # [START model]\n", + " model = cp_model.CpModel()\n", + " # [END model]\n", + "\n", + " # Creates the variables.\n", + " # [START variables]\n", + " num_vals = 3\n", + " x = model.NewIntVar(0, num_vals - 1, 'x')\n", + " y = model.NewIntVar(0, num_vals - 1, 'y')\n", + " z = model.NewIntVar(0, num_vals - 1, 'z')\n", + " # [END variables]\n", + "\n", + " # Create the constraints.\n", + " # [START constraints]\n", + " model.Add(x != y)\n", + " # [END constraints]\n", + "\n", + " # Create a solver and solve.\n", + " # [START solve]\n", + " solver = cp_model.CpSolver()\n", + " solution_printer = VarArraySolutionPrinter([x, y, z])\n", + " status = solver.SearchForAllSolutions(model, solution_printer)\n", + " # [END solve]\n", + "\n", + " print('Status = %s' % solver.StatusName(status))\n", + " print('Number of solutions found: %i' % solution_printer.solution_count())\n", + "\n", + "\n", + "SearchForAllSolutionsSampleSat()\n", + "# [END program]\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/sat/simple_sat_program.ipynb b/examples/notebook/sat/simple_sat_program.ipynb new file mode 100644 index 0000000000..b889b030a9 --- /dev/null +++ b/examples/notebook/sat/simple_sat_program.ipynb @@ -0,0 +1,72 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010-2018 Google LLC\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"Simple solve.\"\"\"\n", + "\n", + "# [START program]\n", + "from __future__ import absolute_import\n", + "from __future__ import division\n", + "from __future__ import print_function\n", + "\n", + "from ortools.sat.python import cp_model\n", + "\n", + "\n", + "def SimpleSatProgram():\n", + " \"\"\"Minimal CP-SAT example to showcase calling the solver.\"\"\"\n", + " # Creates the model.\n", + " # [START model]\n", + " model = cp_model.CpModel()\n", + " # [END model]\n", + "\n", + " # Creates the variables.\n", + " # [START variables]\n", + " num_vals = 3\n", + " x = model.NewIntVar(0, num_vals - 1, 'x')\n", + " y = model.NewIntVar(0, num_vals - 1, 'y')\n", + " z = model.NewIntVar(0, num_vals - 1, 'z')\n", + " # [END variables]\n", + "\n", + " # Creates the constraints.\n", + " # [START constraints]\n", + " model.Add(x != y)\n", + " # [END constraints]\n", + "\n", + " # Creates a solver and solves the model.\n", + " # [START solve]\n", + " solver = cp_model.CpSolver()\n", + " status = solver.Solve(model)\n", + " # [END solve]\n", + "\n", + " if status == cp_model.FEASIBLE:\n", + " print('x = %i' % solver.Value(x))\n", + " print('y = %i' % solver.Value(y))\n", + " print('z = %i' % solver.Value(z))\n", + "\n", + "\n", + "SimpleSatProgram()\n", + "# [END program]\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/sat/solution_hinting_sample_sat.ipynb b/examples/notebook/sat/solution_hinting_sample_sat.ipynb new file mode 100644 index 0000000000..ed15a4a4c6 --- /dev/null +++ b/examples/notebook/sat/solution_hinting_sample_sat.ipynb @@ -0,0 +1,79 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010-2018 Google LLC\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"Code sample that solves a model using solution hinting.\"\"\"\n", + "\n", + "# [START program]\n", + "from __future__ import absolute_import\n", + "from __future__ import division\n", + "from __future__ import print_function\n", + "\n", + "from ortools.sat.python import cp_model\n", + "\n", + "\n", + "def SolutionHintingSampleSat():\n", + " \"\"\"Showcases solution hinting.\"\"\"\n", + " # Creates the model.\n", + " # [START model]\n", + " model = cp_model.CpModel()\n", + " # [END model]\n", + "\n", + " # Creates the variables.\n", + " # [START variables]\n", + " num_vals = 3\n", + " x = model.NewIntVar(0, num_vals - 1, 'x')\n", + " y = model.NewIntVar(0, num_vals - 1, 'y')\n", + " z = model.NewIntVar(0, num_vals - 1, 'z')\n", + " # [END variables]\n", + "\n", + " # Creates the constraints.\n", + " # [START constraints]\n", + " model.Add(x != y)\n", + " # [END constraints]\n", + "\n", + " # [START objective]\n", + " model.Maximize(x + 2 * y + 3 * z)\n", + " # [END objective]\n", + "\n", + " # Solution hinting: x <- 1, y <- 2\n", + " model.AddHint(x, 1)\n", + " model.AddHint(y, 2)\n", + "\n", + " # Creates a solver and solves.\n", + " # [START solve]\n", + " solver = cp_model.CpSolver()\n", + " solution_printer = cp_model.VarArrayAndObjectiveSolutionPrinter([x, y, z])\n", + " status = solver.SolveWithSolutionCallback(model, solution_printer)\n", + " # [END solve]\n", + "\n", + " print('Status = %s' % solver.StatusName(status))\n", + " print('Number of solutions found: %i' % solution_printer.solution_count())\n", + "\n", + "\n", + "SolutionHintingSampleSat()\n", + "# [END program]\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/sat/solve_and_print_intermediate_solutions_sample_sat.ipynb b/examples/notebook/sat/solve_and_print_intermediate_solutions_sample_sat.ipynb new file mode 100644 index 0000000000..4219176f76 --- /dev/null +++ b/examples/notebook/sat/solve_and_print_intermediate_solutions_sample_sat.ipynb @@ -0,0 +1,98 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010-2018 Google LLC\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"Solves an optimization problem and displays all intermediate solutions.\"\"\"\n", + "\n", + "# [START program]\n", + "from __future__ import absolute_import\n", + "from __future__ import division\n", + "from __future__ import print_function\n", + "\n", + "from ortools.sat.python import cp_model\n", + "\n", + "\n", + "# You need to subclass the cp_model.CpSolverSolutionCallback class.\n", + "# [START print_solution]\n", + "class VarArrayAndObjectiveSolutionPrinter(cp_model.CpSolverSolutionCallback):\n", + " \"\"\"Print intermediate solutions.\"\"\"\n", + "\n", + " def __init__(self, variables):\n", + " cp_model.CpSolverSolutionCallback.__init__(self)\n", + " self.__variables = variables\n", + " self.__solution_count = 0\n", + "\n", + " def on_solution_callback(self):\n", + " print('Solution %i' % self.__solution_count)\n", + " print(' objective value = %i' % self.ObjectiveValue())\n", + " for v in self.__variables:\n", + " print(' %s = %i' % (v, self.Value(v)), end=' ')\n", + " print()\n", + " self.__solution_count += 1\n", + "\n", + " def solution_count(self):\n", + " return self.__solution_count\n", + " # [END print_solution]\n", + "\n", + "\n", + "def SolveAndPrintIntermediateSolutionsSampleSat():\n", + " \"\"\"Showcases printing intermediate solutions found during search.\"\"\"\n", + " # Creates the model.\n", + " # [START model]\n", + " model = cp_model.CpModel()\n", + " # [END model]\n", + "\n", + " # Creates the variables.\n", + " # [START variables]\n", + " num_vals = 3\n", + " x = model.NewIntVar(0, num_vals - 1, 'x')\n", + " y = model.NewIntVar(0, num_vals - 1, 'y')\n", + " z = model.NewIntVar(0, num_vals - 1, 'z')\n", + " # [END variables]\n", + "\n", + " # Creates the constraints.\n", + " # [START constraints]\n", + " model.Add(x != y)\n", + " # [END constraints]\n", + "\n", + " # [START objective]\n", + " model.Maximize(x + 2 * y + 3 * z)\n", + " # [END objective]\n", + "\n", + " # Creates a solver and solves.\n", + " # [START solve]\n", + " solver = cp_model.CpSolver()\n", + " solution_printer = VarArrayAndObjectiveSolutionPrinter([x, y, z])\n", + " status = solver.SolveWithSolutionCallback(model, solution_printer)\n", + " # [END solve]\n", + "\n", + " print('Status = %s' % solver.StatusName(status))\n", + " print('Number of solutions found: %i' % solution_printer.solution_count())\n", + "\n", + "\n", + "SolveAndPrintIntermediateSolutionsSampleSat()\n", + "# [END program]\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/sat/solve_with_time_limit_sample_sat.ipynb b/examples/notebook/sat/solve_with_time_limit_sample_sat.ipynb new file mode 100644 index 0000000000..7e4acf4e63 --- /dev/null +++ b/examples/notebook/sat/solve_with_time_limit_sample_sat.ipynb @@ -0,0 +1,64 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010-2018 Google LLC\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"Solves a problem with a time limit.\"\"\"\n", + "\n", + "from __future__ import absolute_import\n", + "from __future__ import division\n", + "from __future__ import print_function\n", + "\n", + "from ortools.sat.python import cp_model\n", + "\n", + "\n", + "def SolveWithTimeLimitSampleSat():\n", + " \"\"\"Minimal CP-SAT example to showcase calling the solver.\"\"\"\n", + " # Creates the model.\n", + " model = cp_model.CpModel()\n", + " # Creates the variables.\n", + " num_vals = 3\n", + " x = model.NewIntVar(0, num_vals - 1, 'x')\n", + " y = model.NewIntVar(0, num_vals - 1, 'y')\n", + " z = model.NewIntVar(0, num_vals - 1, 'z')\n", + " # Adds an all-different constraint.\n", + " model.Add(x != y)\n", + "\n", + " # Creates a solver and solves the model.\n", + " solver = cp_model.CpSolver()\n", + "\n", + " # Sets a time limit of 10 seconds.\n", + " solver.parameters.max_time_in_seconds = 10.0\n", + "\n", + " status = solver.Solve(model)\n", + "\n", + " if status == cp_model.FEASIBLE:\n", + " print('x = %i' % solver.Value(x))\n", + " print('y = %i' % solver.Value(y))\n", + " print('z = %i' % solver.Value(z))\n", + "\n", + "\n", + "SolveWithTimeLimitSampleSat()\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/sat/step_function_sample_sat.ipynb b/examples/notebook/sat/step_function_sample_sat.ipynb new file mode 100644 index 0000000000..00527b38f4 --- /dev/null +++ b/examples/notebook/sat/step_function_sample_sat.ipynb @@ -0,0 +1,112 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010-2018 Google LLC\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"Implements a step function.\"\"\"\n", + "\n", + "from __future__ import absolute_import\n", + "from __future__ import division\n", + "from __future__ import print_function\n", + "\n", + "from ortools.sat.python import cp_model\n", + "\n", + "\n", + "class VarArraySolutionPrinter(cp_model.CpSolverSolutionCallback):\n", + " \"\"\"Print intermediate solutions.\"\"\"\n", + "\n", + " def __init__(self, variables):\n", + " cp_model.CpSolverSolutionCallback.__init__(self)\n", + " self.__variables = variables\n", + " self.__solution_count = 0\n", + "\n", + " def on_solution_callback(self):\n", + " self.__solution_count += 1\n", + " for v in self.__variables:\n", + " print('%s=%i' % (v, self.Value(v)), end=' ')\n", + " print()\n", + "\n", + " def solution_count(self):\n", + " return self.__solution_count\n", + "\n", + "\n", + "def step_function_sample_sat():\n", + " \"\"\"Encode the step function.\"\"\"\n", + "\n", + " # Model.\n", + " model = cp_model.CpModel()\n", + "\n", + " # Declare our primary variable.\n", + " x = model.NewIntVar(0, 20, 'x')\n", + "\n", + " # Create the expression variable and implement the step function\n", + " # Note it is not defined for x == 2.\n", + " #\n", + " # - 3\n", + " # -- -- --------- 2\n", + " # 1\n", + " # -- --- 0\n", + " # 0 ================ 20\n", + " #\n", + " expr = model.NewIntVar(0, 3, 'expr')\n", + "\n", + " # expr == 0 on [5, 6] U [8, 10]\n", + " b0 = model.NewBoolVar('b0')\n", + " model.AddLinearExpressionInDomain(\n", + " x, cp_model.Domain.FromIntervals([(5, 6), (8, 10)])).OnlyEnforceIf(b0)\n", + " model.Add(expr == 0).OnlyEnforceIf(b0)\n", + "\n", + " # expr == 2 on [0, 1] U [3, 4] U [11, 20]\n", + " b2 = model.NewBoolVar('b2')\n", + " model.AddLinearExpressionInDomain(\n", + " x, cp_model.Domain.FromIntervals([(0, 1), (3, 4),\n", + " (11, 20)])).OnlyEnforceIf(b2)\n", + " model.Add(expr == 2).OnlyEnforceIf(b2)\n", + "\n", + " # expr == 3 when x == 7\n", + " b3 = model.NewBoolVar('b3')\n", + " model.Add(x == 7).OnlyEnforceIf(b3)\n", + " model.Add(expr == 3).OnlyEnforceIf(b3)\n", + "\n", + " # At least one bi is true. (we could use a sum == 1).\n", + " model.AddBoolOr([b0, b2, b3])\n", + "\n", + " # Search for x values in increasing order.\n", + " model.AddDecisionStrategy([x], cp_model.CHOOSE_FIRST,\n", + " cp_model.SELECT_MIN_VALUE)\n", + "\n", + " # Create a solver and solve with a fixed search.\n", + " solver = cp_model.CpSolver()\n", + "\n", + " # Force the solver to follow the decision strategy exactly.\n", + " solver.parameters.search_branching = cp_model.FIXED_SEARCH\n", + "\n", + " # Search and print out all solutions.\n", + " solution_printer = VarArraySolutionPrinter([x, expr])\n", + " solver.SearchForAllSolutions(model, solution_printer)\n", + "\n", + "\n", + "step_function_sample_sat()\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/sat/stop_after_n_solutions_sample_sat.ipynb b/examples/notebook/sat/stop_after_n_solutions_sample_sat.ipynb new file mode 100644 index 0000000000..c0e34a4932 --- /dev/null +++ b/examples/notebook/sat/stop_after_n_solutions_sample_sat.ipynb @@ -0,0 +1,79 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2010-2018 Google LLC\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\"\"\"Code sample that solves a model and displays a small number of solutions.\"\"\"\n", + "\n", + "from __future__ import absolute_import\n", + "from __future__ import division\n", + "from __future__ import print_function\n", + "\n", + "from ortools.sat.python import cp_model\n", + "\n", + "\n", + "class VarArraySolutionPrinterWithLimit(cp_model.CpSolverSolutionCallback):\n", + " \"\"\"Print intermediate solutions.\"\"\"\n", + "\n", + " def __init__(self, variables, limit):\n", + " cp_model.CpSolverSolutionCallback.__init__(self)\n", + " self.__variables = variables\n", + " self.__solution_count = 0\n", + " self.__solution_limit = limit\n", + "\n", + " def on_solution_callback(self):\n", + " self.__solution_count += 1\n", + " for v in self.__variables:\n", + " print('%s=%i' % (v, self.Value(v)), end=' ')\n", + " print()\n", + " if self.__solution_count >= self.__solution_limit:\n", + " print('Stop search after %i solutions' % self.__solution_limit)\n", + " self.StopSearch()\n", + "\n", + " def solution_count(self):\n", + " return self.__solution_count\n", + "\n", + "\n", + "def StopAfterNSolutionsSampleSat():\n", + " \"\"\"Showcases calling the solver to search for small number of solutions.\"\"\"\n", + " # Creates the model.\n", + " model = cp_model.CpModel()\n", + " # Creates the variables.\n", + " num_vals = 3\n", + " x = model.NewIntVar(0, num_vals - 1, 'x')\n", + " y = model.NewIntVar(0, num_vals - 1, 'y')\n", + " z = model.NewIntVar(0, num_vals - 1, 'z')\n", + "\n", + " # Create a solver and solve.\n", + " solver = cp_model.CpSolver()\n", + " solution_printer = VarArraySolutionPrinterWithLimit([x, y, z], 5)\n", + " status = solver.SearchForAllSolutions(model, solution_printer)\n", + " print('Status = %s' % solver.StatusName(status))\n", + " print('Number of solutions found: %i' % solution_printer.solution_count())\n", + " assert solution_printer.solution_count() == 5\n", + "\n", + "\n", + "StopAfterNSolutionsSampleSat()\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/notebook/steel_mill_slab_sat.ipynb b/examples/notebook/steel_mill_slab_sat.ipynb deleted file mode 100644 index 329390d0e5..0000000000 --- a/examples/notebook/steel_mill_slab_sat.ipynb +++ /dev/null @@ -1,788 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Copyright 2010-2017 Google\n", - "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", - "# you may not use this file except in compliance with the License.\n", - "# You may obtain a copy of the License at\n", - "#\n", - "# http://www.apache.org/licenses/LICENSE-2.0\n", - "#\n", - "# Unless required by applicable law or agreed to in writing, software\n", - "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", - "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", - "# See the License for the specific language governing permissions and\n", - "# limitations under the License.\n", - "\n", - "from __future__ import print_function\n", - "import argparse\n", - "import time\n", - "from ortools.sat.python import cp_model\n", - "from ortools.linear_solver import pywraplp\n", - "\n", - "PARSER = argparse.ArgumentParser()\n", - "\n", - "PARSER.add_argument(\n", - " '--problem', default=2, type=int, help='Problem id to solve.')\n", - "PARSER.add_argument(\n", - " '--break_symmetries',\n", - " default=True,\n", - " type=bool,\n", - " help='Break symmetries between equivalent orders.')\n", - "PARSER.add_argument(\n", - " '--solver',\n", - " default='sat_table',\n", - " help='Method used to solve: sat, sat_table, sat_column, mip_column.')\n", - "PARSER.add_argument(\n", - " '--output_proto',\n", - " default='',\n", - " help='Output file to write the cp_model proto to.')\n", - "\n", - "def BuildProblem(problem_id):\n", - " \"\"\"Build problem data.\"\"\"\n", - " if problem_id == 0:\n", - " capacities = [\n", - " 0, 12, 14, 17, 18, 19, 20, 23, 24, 25, 26, 27, 28, 29, 30, 32, 35, 39,\n", - " 42, 43, 44\n", - " ]\n", - " num_colors = 88\n", - " num_slabs = 111\n", - " orders = [\n", - " (4, 1), # (size, color)\n", - " (22, 2),\n", - " (9, 3),\n", - " (5, 4),\n", - " (8, 5),\n", - " (3, 6),\n", - " (3, 4),\n", - " (4, 7),\n", - " (7, 4),\n", - " (7, 8),\n", - " (3, 6),\n", - " (2, 6),\n", - " (2, 4),\n", - " (8, 9),\n", - " (5, 10),\n", - " (7, 11),\n", - " (4, 7),\n", - " (7, 11),\n", - " (5, 10),\n", - " (7, 11),\n", - " (8, 9),\n", - " (3, 1),\n", - " (25, 12),\n", - " (14, 13),\n", - " (3, 6),\n", - " (22, 14),\n", - " (19, 15),\n", - " (19, 15),\n", - " (22, 16),\n", - " (22, 17),\n", - " (22, 18),\n", - " (20, 19),\n", - " (22, 20),\n", - " (5, 21),\n", - " (4, 22),\n", - " (10, 23),\n", - " (26, 24),\n", - " (17, 25),\n", - " (20, 26),\n", - " (16, 27),\n", - " (10, 28),\n", - " (19, 29),\n", - " (10, 30),\n", - " (10, 31),\n", - " (23, 32),\n", - " (22, 33),\n", - " (26, 34),\n", - " (27, 35),\n", - " (22, 36),\n", - " (27, 37),\n", - " (22, 38),\n", - " (22, 39),\n", - " (13, 40),\n", - " (14, 41),\n", - " (16, 27),\n", - " (26, 34),\n", - " (26, 42),\n", - " (27, 35),\n", - " (22, 36),\n", - " (20, 43),\n", - " (26, 24),\n", - " (22, 44),\n", - " (13, 45),\n", - " (19, 46),\n", - " (20, 47),\n", - " (16, 48),\n", - " (15, 49),\n", - " (17, 50),\n", - " (10, 28),\n", - " (20, 51),\n", - " (5, 52),\n", - " (26, 24),\n", - " (19, 53),\n", - " (15, 54),\n", - " (10, 55),\n", - " (10, 56),\n", - " (13, 57),\n", - " (13, 58),\n", - " (13, 59),\n", - " (12, 60),\n", - " (12, 61),\n", - " (18, 62),\n", - " (10, 63),\n", - " (18, 64),\n", - " (16, 65),\n", - " (20, 66),\n", - " (12, 67),\n", - " (6, 68),\n", - " (6, 68),\n", - " (15, 69),\n", - " (15, 70),\n", - " (15, 70),\n", - " (21, 71),\n", - " (30, 72),\n", - " (30, 73),\n", - " (30, 74),\n", - " (30, 75),\n", - " (23, 76),\n", - " (15, 77),\n", - " (15, 78),\n", - " (27, 79),\n", - " (27, 80),\n", - " (27, 81),\n", - " (27, 82),\n", - " (27, 83),\n", - " (27, 84),\n", - " (27, 79),\n", - " (27, 85),\n", - " (27, 86),\n", - " (10, 87),\n", - " (3, 88)\n", - " ]\n", - " elif problem_id == 1:\n", - " capacities = [0, 17, 44]\n", - " num_colors = 23\n", - " num_slabs = 30\n", - " orders = [\n", - " (4, 1), # (size, color)\n", - " (22, 2),\n", - " (9, 3),\n", - " (5, 4),\n", - " (8, 5),\n", - " (3, 6),\n", - " (3, 4),\n", - " (4, 7),\n", - " (7, 4),\n", - " (7, 8),\n", - " (3, 6),\n", - " (2, 6),\n", - " (2, 4),\n", - " (8, 9),\n", - " (5, 10),\n", - " (7, 11),\n", - " (4, 7),\n", - " (7, 11),\n", - " (5, 10),\n", - " (7, 11),\n", - " (8, 9),\n", - " (3, 1),\n", - " (25, 12),\n", - " (14, 13),\n", - " (3, 6),\n", - " (22, 14),\n", - " (19, 15),\n", - " (19, 15),\n", - " (22, 16),\n", - " (22, 17),\n", - " (22, 18),\n", - " (20, 19),\n", - " (22, 20),\n", - " (5, 21),\n", - " (4, 22),\n", - " (10, 23)\n", - " ]\n", - " elif problem_id == 2:\n", - " capacities = [0, 17, 44]\n", - " num_colors = 15\n", - " num_slabs = 20\n", - " orders = [\n", - " (4, 1), # (size, color)\n", - " (22, 2),\n", - " (9, 3),\n", - " (5, 4),\n", - " (8, 5),\n", - " (3, 6),\n", - " (3, 4),\n", - " (4, 7),\n", - " (7, 4),\n", - " (7, 8),\n", - " (3, 6),\n", - " (2, 6),\n", - " (2, 4),\n", - " (8, 9),\n", - " (5, 10),\n", - " (7, 11),\n", - " (4, 7),\n", - " (7, 11),\n", - " (5, 10),\n", - " (7, 11),\n", - " (8, 9),\n", - " (3, 1),\n", - " (25, 12),\n", - " (14, 13),\n", - " (3, 6),\n", - " (22, 14),\n", - " (19, 15),\n", - " (19, 15)\n", - " ]\n", - "\n", - " elif problem_id == 3:\n", - " capacities = [0, 17, 44]\n", - " num_colors = 8\n", - " num_slabs = 10\n", - " orders = [\n", - " (4, 1), # (size, color)\n", - " (22, 2),\n", - " (9, 3),\n", - " (5, 4),\n", - " (8, 5),\n", - " (3, 6),\n", - " (3, 4),\n", - " (4, 7),\n", - " (7, 4),\n", - " (7, 8),\n", - " (3, 6)\n", - " ]\n", - "\n", - " return (num_slabs, capacities, num_colors, orders)\n", - "\n", - "class SteelMillSlabSolutionPrinter(cp_model.CpSolverSolutionCallback):\n", - " \"\"\"Print intermediate solutions.\"\"\"\n", - "\n", - " def __init__(self, orders, assign, load, loss):\n", - " cp_model.CpSolverSolutionCallback.__init__(self)\n", - " self.__orders = orders\n", - " self.__assign = assign\n", - " self.__load = load\n", - " self.__loss = loss\n", - " self.__solution_count = 0\n", - " self.__all_orders = range(len(orders))\n", - " self.__all_slabs = range(len(assign[0]))\n", - " self.__start_time = time.time()\n", - "\n", - " def OnSolutionCallback(self):\n", - " current_time = time.time()\n", - " objective = sum(self.Value(l) for l in self.__loss)\n", - " print('Solution %i, time = %f s, objective = %i' %\n", - " (self.__solution_count, current_time - self.__start_time, objective))\n", - " self.__solution_count += 1\n", - " orders_in_slab = [[\n", - " o for o in self.__all_orders if self.Value(self.__assign[o][s])\n", - " ] for s in self.__all_slabs]\n", - " for s in self.__all_slabs:\n", - " if orders_in_slab[s]:\n", - " line = ' - slab %i, load = %i, loss = %i, orders = [' % (\n", - " s, self.Value(self.__load[s]), self.Value(self.__loss[s]))\n", - " for o in orders_in_slab[s]:\n", - " line += '#%i(w%i, c%i) ' % (o, self.__orders[o][0],\n", - " self.__orders[o][1])\n", - " line += ']'\n", - " print(line)\n", - "\n", - "class SolutionPrinterWithObjective(cp_model.CpSolverSolutionCallback):\n", - " \"\"\"Print intermediate solutions.\"\"\"\n", - "\n", - " def __init__(self):\n", - " cp_model.CpSolverSolutionCallback.__init__(self)\n", - " self.__solution_count = 0\n", - " self.__start_time = time.time()\n", - "\n", - " def OnSolutionCallback(self):\n", - " current_time = time.time()\n", - " print('Solution %i, time = %f s, objective = %i' %\n", - " (self.__solution_count, current_time - self.__start_time,\n", - " self.ObjectiveValue()))\n", - " self.__solution_count += 1\n", - "\n", - "def SteelMillSlab(problem, break_symmetries, output_proto):\n", - " \"\"\"Solves the Steel Mill Slab Problem.\"\"\"\n", - " ### Load problem.\n", - " (num_slabs, capacities, num_colors, orders) = BuildProblem(problem)\n", - "\n", - " num_orders = len(orders)\n", - " num_capacities = len(capacities)\n", - " all_slabs = range(num_slabs)\n", - " all_colors = range(num_colors)\n", - " all_orders = range(len(orders))\n", - " print('Solving steel mill with %i orders, %i slabs, and %i capacities' %\n", - " (num_orders, num_slabs, num_capacities - 1))\n", - "\n", - " # Compute auxilliary data.\n", - " widths = [x[0] for x in orders]\n", - " colors = [x[1] for x in orders]\n", - " max_capacity = max(capacities)\n", - " loss_array = [\n", - " min(x for x in capacities if x >= c) - c for c in range(max_capacity + 1)\n", - " ]\n", - " max_loss = max(loss_array)\n", - " orders_per_color = [\n", - " [o for o in all_orders if colors[o] == c + 1] for c in all_colors\n", - " ]\n", - " unique_color_orders = [\n", - " o for o in all_orders if len(orders_per_color[colors[o] - 1]) == 1\n", - " ]\n", - "\n", - " ### Model problem.\n", - "\n", - " # Create the model and the decision variables.\n", - " model = cp_model.CpModel()\n", - " assign = [[\n", - " model.NewBoolVar('assign_%i_to_slab_%i' % (o, s)) for s in all_slabs\n", - " ] for o in all_orders]\n", - " loads = [\n", - " model.NewIntVar(0, max_capacity, 'load_of_slab_%i' % s) for s in all_slabs\n", - " ]\n", - " color_is_in_slab = [[\n", - " model.NewBoolVar('color_%i_in_slab_%i' % (c + 1, s)) for c in all_colors\n", - " ] for s in all_slabs]\n", - "\n", - " # Compute load of all slabs.\n", - " for s in all_slabs:\n", - " model.Add(sum(assign[o][s] * widths[o] for o in all_orders) == loads[s])\n", - "\n", - " # Orders are assigned to one slab.\n", - " for o in all_orders:\n", - " model.Add(sum(assign[o]) == 1)\n", - "\n", - " # Redundant constraint (sum of loads == sum of widths).\n", - " model.Add(sum(loads) == sum(widths))\n", - "\n", - " # Link present_colors and assign.\n", - " for c in all_colors:\n", - " for s in all_slabs:\n", - " for o in orders_per_color[c]:\n", - " model.AddImplication(assign[o][s], color_is_in_slab[s][c])\n", - " model.AddImplication(color_is_in_slab[s][c].Not(), assign[o][s].Not())\n", - "\n", - " # At most two colors per slab.\n", - " for s in all_slabs:\n", - " model.Add(sum(color_is_in_slab[s]) <= 2)\n", - "\n", - " # Project previous constraint on unique_color_orders\n", - " for s in all_slabs:\n", - " model.Add(sum(assign[o][s] for o in unique_color_orders) <= 2)\n", - "\n", - " # Symmetry breaking.\n", - " for s in range(num_slabs - 1):\n", - " model.Add(loads[s] >= loads[s + 1])\n", - "\n", - " # Collect equivalent orders.\n", - " width_to_unique_color_order = {}\n", - " ordered_equivalent_orders = []\n", - " for c in all_colors:\n", - " colored_orders = orders_per_color[c]\n", - " if not colored_orders:\n", - " continue\n", - " if len(colored_orders) == 1:\n", - " o = colored_orders[0]\n", - " w = widths[o]\n", - " if w not in width_to_unique_color_order:\n", - " width_to_unique_color_order[w] = [o]\n", - " else:\n", - " width_to_unique_color_order[w].append(o)\n", - " else:\n", - " local_width_to_order = {}\n", - " for o in colored_orders:\n", - " w = widths[o]\n", - " if w not in local_width_to_order:\n", - " local_width_to_order[w] = []\n", - " local_width_to_order[w].append(o)\n", - " for w, os in local_width_to_order.items():\n", - " if len(os) > 1:\n", - " for p in range(len(os) - 1):\n", - " ordered_equivalent_orders.append((os[p], os[p + 1]))\n", - " for w, os in width_to_unique_color_order.items():\n", - " if len(os) > 1:\n", - " for p in range(len(os) - 1):\n", - " ordered_equivalent_orders.append((os[p], os[p + 1]))\n", - "\n", - " # Create position variables if there are symmetries to be broken.\n", - " if break_symmetries and ordered_equivalent_orders:\n", - " print(' - creating %i symmetry breaking constraints' %\n", - " len(ordered_equivalent_orders))\n", - " positions = {}\n", - " for p in ordered_equivalent_orders:\n", - " if p[0] not in positions:\n", - " positions[p[0]] = model.NewIntVar(0, num_slabs - 1,\n", - " 'position_of_slab_%i' % p[0])\n", - " model.AddMapDomain(positions[p[0]], assign[p[0]])\n", - " if p[1] not in positions:\n", - " positions[p[1]] = model.NewIntVar(0, num_slabs - 1,\n", - " 'position_of_slab_%i' % p[1])\n", - " model.AddMapDomain(positions[p[1]], assign[p[1]])\n", - " # Finally add the symmetry breaking constraint.\n", - " model.Add(positions[p[0]] <= positions[p[1]])\n", - "\n", - " # Objective.\n", - " obj = model.NewIntVar(0, num_slabs * max_loss, 'obj')\n", - " losses = [model.NewIntVar(0, max_loss, 'loss_%i' % s) for s in all_slabs]\n", - " for s in all_slabs:\n", - " model.AddElement(loads[s], loss_array, losses[s])\n", - " model.Add(obj == sum(losses))\n", - " model.Minimize(obj)\n", - "\n", - " ### Solve model.\n", - " solver = cp_model.CpSolver()\n", - " status = solver.Solve(model)\n", - "\n", - " ### Output the solution.\n", - " if status == cp_model.OPTIMAL:\n", - " print('Loss = %i, time = %f s, %i conflicts' %\n", - " (solver.ObjectiveValue(), solver.WallTime(), solver.NumConflicts()))\n", - " else:\n", - " print('No solution')\n", - "\n", - "class AllSolutionsCollector(cp_model.CpSolverSolutionCallback):\n", - " \"\"\"Collect all solutions callback.\"\"\"\n", - "\n", - " def __init__(self, variables):\n", - " cp_model.CpSolverSolutionCallback.__init__(self)\n", - " self.__solutions = []\n", - " self.__variables = variables\n", - "\n", - " def OnSolutionCallback(self):\n", - " solution = [self.Value(v) for v in self.__variables]\n", - " self.__solutions.append(tuple(solution))\n", - "\n", - " def AllSolutions(self):\n", - " return self.__solutions\n", - "\n", - "def CollectValidSlabs(capacities, colors, widths, loss_array, all_colors):\n", - " \"\"\"Collect valid columns (assign, loss) for one slab.\"\"\"\n", - " max_capacity = max(capacities)\n", - " all_orders = range(len(colors))\n", - " orders_per_color = [\n", - " [o for o in all_orders if colors[o] == c + 1] for c in all_colors\n", - " ]\n", - "\n", - " model = cp_model.CpModel()\n", - " assign = [model.NewBoolVar('assign_%i' % o) for o in all_orders]\n", - " load = model.NewIntVar(0, max_capacity, 'load')\n", - "\n", - " color_in_slab = [model.NewBoolVar('color_%i' % (c + 1)) for c in all_colors]\n", - "\n", - " # Compute load.\n", - " model.Add(sum(assign[o] * widths[o] for o in all_orders) == load)\n", - "\n", - " # Link present_colors and assign.\n", - " for c in all_colors:\n", - " for o in orders_per_color[c]:\n", - " model.AddImplication(assign[o], color_in_slab[c])\n", - " model.AddImplication(color_in_slab[c].Not(), assign[o].Not())\n", - " model.AddBoolOr([color_in_slab[c].Not()] +\n", - " [assign[o] for o in orders_per_color[c]])\n", - "\n", - " # At most two colors per slab.\n", - " model.Add(sum(color_in_slab) <= 2)\n", - "\n", - " # Compute loss.\n", - " loss = model.NewIntVar(0, max(loss_array), 'loss')\n", - " model.AddElement(load, loss_array, loss)\n", - "\n", - " ### Solve model and collect columns.\n", - " print('Collect Valid Slabs...DONE')\n", - " solver = cp_model.CpSolver()\n", - " collector = AllSolutionsCollector(assign + [loss, load])\n", - " solver.SearchForAllSolutions(model, collector)\n", - " print('Collect Valid Slabs...DONE')\n", - " return collector.AllSolutions()\n", - "\n", - "def SteelMillSlabWithValidSlabs(problem, break_symmetries, output_proto):\n", - " \"\"\"Solves the Steel Mill Slab Problem.\"\"\"\n", - " ### Load problem.\n", - " (num_slabs, capacities, num_colors, orders) = BuildProblem(problem)\n", - "\n", - " num_orders = len(orders)\n", - " num_capacities = len(capacities)\n", - " all_slabs = range(num_slabs)\n", - " all_colors = range(num_colors)\n", - " all_orders = range(len(orders))\n", - " print('Solving steel mill with %i orders, %i slabs, and %i capacities' %\n", - " (num_orders, num_slabs, num_capacities - 1))\n", - "\n", - " # Compute auxilliary data.\n", - " widths = [x[0] for x in orders]\n", - " colors = [x[1] for x in orders]\n", - " max_capacity = max(capacities)\n", - " loss_array = [\n", - " min(x for x in capacities if x >= c) - c for c in range(max_capacity + 1)\n", - " ]\n", - " max_loss = max(loss_array)\n", - "\n", - " ### Model problem.\n", - "\n", - " # Create the model and the decision variables.\n", - " model = cp_model.CpModel()\n", - " assign = [[\n", - " model.NewBoolVar('assign_%i_to_slab_%i' % (o, s)) for s in all_slabs\n", - " ] for o in all_orders]\n", - " loads = [model.NewIntVar(0, max_capacity, 'load_%i' % s) for s in all_slabs]\n", - " losses = [model.NewIntVar(0, max_loss, 'loss_%i' % s) for s in all_slabs]\n", - "\n", - " unsorted_valid_slabs = CollectValidSlabs(capacities, colors, widths,\n", - " loss_array, all_colors)\n", - " # Sort slab by descending load/loss. Remove duplicates.\n", - " valid_slabs = sorted(unsorted_valid_slabs, key=lambda c: 1000 * c[-1] + c[-2])\n", - " num_valid_slabs = len(valid_slabs)\n", - " print(' - %i valid slab combinations' % num_valid_slabs)\n", - "\n", - " for s in all_slabs:\n", - " model.AddAllowedAssignments(\n", - " [assign[o][s] for o in all_orders] + [losses[s], loads[s]], valid_slabs)\n", - "\n", - " # Orders are assigned to one slab.\n", - " for o in all_orders:\n", - " model.Add(sum(assign[o]) == 1)\n", - "\n", - " # Redundant constraint (sum of loads == sum of widths).\n", - " model.Add(sum(loads) == sum(widths))\n", - "\n", - " # Symmetry breaking.\n", - " for s in range(num_slabs - 1):\n", - " model.Add(loads[s] >= loads[s + 1])\n", - "\n", - " # Collect equivalent orders.\n", - " if break_symmetries:\n", - " print('Breaking symmetries')\n", - " width_to_unique_color_order = {}\n", - " ordered_equivalent_orders = []\n", - " orders_per_color = [\n", - " [o for o in all_orders if colors[o] == c + 1] for c in all_colors\n", - " ]\n", - " for c in all_colors:\n", - " colored_orders = orders_per_color[c]\n", - " if not colored_orders:\n", - " continue\n", - " if len(colored_orders) == 1:\n", - " o = colored_orders[0]\n", - " w = widths[o]\n", - " if w not in width_to_unique_color_order:\n", - " width_to_unique_color_order[w] = [o]\n", - " else:\n", - " width_to_unique_color_order[w].append(o)\n", - " else:\n", - " local_width_to_order = {}\n", - " for o in colored_orders:\n", - " w = widths[o]\n", - " if w not in local_width_to_order:\n", - " local_width_to_order[w] = []\n", - " local_width_to_order[w].append(o)\n", - " for w, os in local_width_to_order.items():\n", - " if len(os) > 1:\n", - " for p in range(len(os) - 1):\n", - " ordered_equivalent_orders.append((os[p], os[p + 1]))\n", - " for w, os in width_to_unique_color_order.items():\n", - " if len(os) > 1:\n", - " for p in range(len(os) - 1):\n", - " ordered_equivalent_orders.append((os[p], os[p + 1]))\n", - "\n", - " # Create position variables if there are symmetries to be broken.\n", - " if ordered_equivalent_orders:\n", - " print(' - creating %i symmetry breaking constraints' %\n", - " len(ordered_equivalent_orders))\n", - " positions = {}\n", - " for p in ordered_equivalent_orders:\n", - " if p[0] not in positions:\n", - " positions[p[0]] = model.NewIntVar(0, num_slabs - 1,\n", - " 'position_of_slab_%i' % p[0])\n", - " model.AddMapDomain(positions[p[0]], assign[p[0]])\n", - " if p[1] not in positions:\n", - " positions[p[1]] = model.NewIntVar(0, num_slabs - 1,\n", - " 'position_of_slab_%i' % p[1])\n", - " model.AddMapDomain(positions[p[1]], assign[p[1]])\n", - " # Finally add the symmetry breaking constraint.\n", - " model.Add(positions[p[0]] <= positions[p[1]])\n", - "\n", - " # Objective.\n", - " obj = model.NewIntVar(0, num_slabs * max_loss, 'obj')\n", - " model.Add(obj == sum(losses))\n", - " model.Minimize(obj)\n", - "\n", - " print('Model created')\n", - "\n", - " # Output model proto to file.\n", - " if output_proto:\n", - " f = open(output_proto, 'w')\n", - " f.write(str(model.ModelProto()))\n", - " f.close()\n", - "\n", - " ### Solve model.\n", - " solver = cp_model.CpSolver()\n", - " solution_printer = SteelMillSlabSolutionPrinter(orders, assign, loads, losses)\n", - " status = solver.SolveWithSolutionCallback(model, solution_printer)\n", - "\n", - " ### Output the solution.\n", - " if status == cp_model.OPTIMAL:\n", - " print('Loss = %i, time = %f s, %i conflicts' %\n", - " (solver.ObjectiveValue(), solver.WallTime(), solver.NumConflicts()))\n", - " else:\n", - " print('No solution')\n", - "\n", - "def SteelMillSlabWithColumnGeneration(problem, output_proto):\n", - " \"\"\"Solves the Steel Mill Slab Problem.\"\"\"\n", - " ### Load problem.\n", - " (num_slabs, capacities, num_colors, orders) = BuildProblem(problem)\n", - "\n", - " num_orders = len(orders)\n", - " num_capacities = len(capacities)\n", - " all_slabs = range(num_slabs)\n", - " all_colors = range(num_colors)\n", - " all_orders = range(len(orders))\n", - " print('Solving steel mill with %i orders, %i slabs, and %i capacities' %\n", - " (num_orders, num_slabs, num_capacities - 1))\n", - "\n", - " # Compute auxilliary data.\n", - " widths = [x[0] for x in orders]\n", - " colors = [x[1] for x in orders]\n", - " max_capacity = max(capacities)\n", - " loss_array = [\n", - " min(x for x in capacities if x >= c) - c for c in range(max_capacity + 1)\n", - " ]\n", - "\n", - " ### Model problem.\n", - "\n", - " # Generate all valid slabs (columns)\n", - " unsorted_valid_slabs = CollectValidSlabs(capacities, colors, widths,\n", - " loss_array, all_colors)\n", - " # Sort slab by descending load/loss. Remove duplicates.\n", - " valid_slabs = sorted(unsorted_valid_slabs, key=lambda c: 1000 * c[-1] + c[-2])\n", - " num_valid_slabs = len(valid_slabs)\n", - " all_valid_slabs = range(num_valid_slabs)\n", - " print(' - %i valid slab combinations' % num_valid_slabs)\n", - "\n", - " # create model and decision variables.\n", - " model = cp_model.CpModel()\n", - " selected = [model.NewBoolVar('selected_%i' % i) for i in all_valid_slabs]\n", - "\n", - " for o in all_orders:\n", - " model.Add(\n", - " sum(selected[i] for i in all_valid_slabs if valid_slabs[i][o]) == 1)\n", - "\n", - " # Redundant constraint (sum of loads == sum of widths).\n", - " model.Add(\n", - " sum(selected[i] * valid_slabs[i][-1]\n", - " for i in all_valid_slabs) == sum(widths))\n", - "\n", - " # Objective.\n", - " max_loss = max(valid_slabs[i][-2] for i in all_valid_slabs)\n", - " obj = model.NewIntVar(0, num_slabs * max_loss, 'obj')\n", - " model.Add(obj == sum(\n", - " selected[i] * valid_slabs[i][-2] for i in all_valid_slabs))\n", - " model.Minimize(obj)\n", - "\n", - " print('Model created')\n", - "\n", - " # Output model proto to file.\n", - " if output_proto:\n", - " f = open(output_proto, 'w')\n", - " f.write(str(model.ModelProto()))\n", - " f.close()\n", - "\n", - " ### Solve model.\n", - " solver = cp_model.CpSolver()\n", - " solution_printer = SolutionPrinterWithObjective()\n", - " status = solver.SolveWithSolutionCallback(model, solution_printer)\n", - "\n", - " ### Output the solution.\n", - " if status == cp_model.OPTIMAL:\n", - " print('Loss = %i, time = %f s, %i conflicts' %\n", - " (solver.ObjectiveValue(), solver.WallTime(), solver.NumConflicts()))\n", - " else:\n", - " print('No solution')\n", - "\n", - "def SteelMillSlabWithMipColumnGeneration(problem):\n", - " \"\"\"Solves the Steel Mill Slab Problem.\"\"\"\n", - " ### Load problem.\n", - " (num_slabs, capacities, num_colors, orders) = BuildProblem(problem)\n", - "\n", - " num_orders = len(orders)\n", - " num_capacities = len(capacities)\n", - " all_slabs = range(num_slabs)\n", - " all_colors = range(num_colors)\n", - " all_orders = range(len(orders))\n", - " print('Solving steel mill with %i orders, %i slabs, and %i capacities' %\n", - " (num_orders, num_slabs, num_capacities - 1))\n", - "\n", - " # Compute auxilliary data.\n", - " widths = [x[0] for x in orders]\n", - " colors = [x[1] for x in orders]\n", - " max_capacity = max(capacities)\n", - " loss_array = [\n", - " min(x for x in capacities if x >= c) - c for c in range(max_capacity + 1)\n", - " ]\n", - "\n", - " ### Model problem.\n", - "\n", - " # Generate all valid slabs (columns)\n", - " start = time.time()\n", - " unsorted_valid_slabs = CollectValidSlabs(capacities, colors, widths,\n", - " loss_array, all_colors)\n", - " # Sort slab by descending load/loss. Remove duplicates.\n", - " valid_slabs = sorted(unsorted_valid_slabs, key=lambda c: 1000 * c[-1] + c[-2])\n", - " num_valid_slabs = len(valid_slabs)\n", - " all_valid_slabs = range(num_valid_slabs)\n", - " generate = time.time()\n", - " print(' - %i valid slab combinations generated in %f s' % (num_valid_slabs,\n", - " generate - start))\n", - "\n", - " # create model and decision variables.\n", - " solver = pywraplp.Solver('Steel', pywraplp.Solver.BOP_INTEGER_PROGRAMMING)\n", - " selected = [\n", - " solver.IntVar(0.0, 1.0, 'selected_%i' % i) for i in all_valid_slabs\n", - " ]\n", - "\n", - " for order in all_orders:\n", - " solver.Add(\n", - " sum(selected[i] for i in all_valid_slabs if valid_slabs[i][order]) == 1)\n", - "\n", - " # Redundant constraint (sum of loads == sum of widths).\n", - " solver.Add(\n", - " sum(selected[i] * valid_slabs[i][-1]\n", - " for i in all_valid_slabs) == sum(widths))\n", - "\n", - " # Objective.\n", - " solver.Minimize(\n", - " sum(selected[i] * valid_slabs[i][-2] for i in all_valid_slabs))\n", - "\n", - " status = solver.Solve()\n", - "\n", - " ### Output the solution.\n", - " if status == pywraplp.Solver.OPTIMAL:\n", - " print('Objective value = %f found in %f s' % (solver.Objective().Value(),\n", - " time.time() - generate))\n", - " else:\n", - " print('No solution')\n", - "\n", - "'''Main function'''\n", - "if args.solver == 'sat':\n", - " SteelMillSlab(args.problem, args.break_symmetries, args.output_proto)\n", - "elif args.solver == 'sat_table':\n", - " SteelMillSlabWithValidSlabs(args.problem, args.break_symmetries,\n", - " args.output_proto)\n", - "elif args.solver == 'sat_column':\n", - " SteelMillSlabWithColumnGeneration(args.problem, args.output_proto)\n", - "else: # 'mip_column'\n", - " SteelMillSlabWithMipColumnGeneration(args.problem)\n" - ] - } - ], - "metadata": {}, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/examples/notebook/vendor_scheduling_sat.ipynb b/examples/notebook/vendor_scheduling_sat.ipynb deleted file mode 100644 index cbd360d643..0000000000 --- a/examples/notebook/vendor_scheduling_sat.ipynb +++ /dev/null @@ -1,151 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Copyright 2010-2017 Google\n", - "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", - "# you may not use this file except in compliance with the License.\n", - "# You may obtain a copy of the License at\n", - "#\n", - "# http://www.apache.org/licenses/LICENSE-2.0\n", - "#\n", - "# Unless required by applicable law or agreed to in writing, software\n", - "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", - "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", - "# See the License for the specific language governing permissions and\n", - "# limitations under the License.\n", - "\n", - "from __future__ import absolute_import\n", - "from __future__ import division\n", - "from __future__ import print_function\n", - "\n", - "from ortools.sat.python import cp_model\n", - "\n", - "\n", - "class SolutionPrinter(cp_model.CpSolverSolutionCallback):\n", - " \"\"\"Print intermediate solutions.\"\"\"\n", - "\n", - " def __init__(self, num_vendors, num_hours, possible_schedules,\n", - " selected_schedules, hours_stat, min_vendors):\n", - " cp_model.CpSolverSolutionCallback.__init__(self)\n", - " self.__solution_count = 0\n", - " self.__num_vendors = num_vendors\n", - " self.__num_hours = num_hours\n", - " self.__possible_schedules = possible_schedules\n", - " self.__selected_schedules = selected_schedules\n", - " self.__hours_stat = hours_stat\n", - " self.__min_vendors = min_vendors\n", - "\n", - " def OnSolutionCallback(self):\n", - " self.__solution_count += 1\n", - " print ('Solution %i: ', self.__solution_count)\n", - " print(' min vendors:', self.__min_vendors)\n", - " for i in range(self.__num_vendors):\n", - " print(' - vendor %i: ' % i,\n", - " self.__possible_schedules[self.Value(self.__selected_schedules[i])])\n", - " print()\n", - "\n", - " for j in range(self.__num_hours):\n", - " print(' - # workers on day%2i: ' % j, end=' ')\n", - " print(self.Value(self.__hours_stat[j]), end=' ')\n", - " print()\n", - " print()\n", - "\n", - " def SolutionCount(self):\n", - " return self.__solution_count\n", - "\n", - "\n", - "# Create the model.\n", - "model = cp_model.CpModel()\n", - "\n", - "#\n", - "# data\n", - "#\n", - "num_vendors = 9\n", - "num_hours = 10\n", - "num_work_types = 1\n", - "\n", - "traffic = [100, 500, 100, 200, 320, 300, 200, 220, 300, 120]\n", - "max_traffic_per_vendor = 100\n", - "\n", - "# Last columns are :\n", - "# index_of_the_schedule, sum of worked hours (per work type).\n", - "# The index is useful for branching.\n", - "possible_schedules = [[1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 8],\n", - " [1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 4],\n", - " [0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 2, 5],\n", - " [0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 3, 4],\n", - " [1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 4, 3],\n", - " [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 0]]\n", - "\n", - "num_possible_schedules = len(possible_schedules)\n", - "selected_schedules = []\n", - "vendors_stat = []\n", - "hours_stat = []\n", - "\n", - "\n", - "# Auxiliary data\n", - "min_vendors = [t // max_traffic_per_vendor for t in traffic]\n", - "all_vendors = range(num_vendors)\n", - "all_hours = range(num_hours)\n", - "\n", - "#\n", - "# declare variables\n", - "#\n", - "x = {}\n", - "\n", - "for v in all_vendors:\n", - " tmp = []\n", - " for h in all_hours:\n", - " x[v, h] = model.NewIntVar(0, num_work_types, 'x[%i,%i]' % (v, h))\n", - " tmp.append(x[v, h])\n", - " selected_schedule = model.NewIntVar(0, num_possible_schedules - 1,\n", - " 's[%i]' % v)\n", - " hours = model.NewIntVar(0, num_hours, 'h[%i]' % v)\n", - " selected_schedules.append(selected_schedule)\n", - " vendors_stat.append(hours)\n", - " tmp.append(selected_schedule)\n", - " tmp.append(hours)\n", - "\n", - " model.AddAllowedAssignments(tmp, possible_schedules)\n", - "\n", - "#\n", - "# Statistics and constraints for each hour\n", - "#\n", - "for h in all_hours:\n", - " workers = model.NewIntVar(0, 1000, 'workers[%i]' %h)\n", - " model.Add(workers == sum(x[v, h] for v in all_vendors))\n", - " hours_stat.append(workers)\n", - " model.Add(workers * max_traffic_per_vendor >= traffic[h])\n", - "\n", - "#\n", - "# Redundant constraint: sort selected_schedules\n", - "#\n", - "for v in range(num_vendors - 1):\n", - " model.Add(selected_schedules[v] <= selected_schedules[v + 1])\n", - "\n", - "# Solve model.\n", - "solver = cp_model.CpSolver()\n", - "solution_printer = SolutionPrinter(num_vendors, num_hours, possible_schedules,\n", - " selected_schedules, hours_stat,\n", - " min_vendors)\n", - "status = solver.SearchForAllSolutions(model, solution_printer)\n", - "print('Status = %s' % solver.StatusName(status))\n", - "\n", - "print('Statistics')\n", - "print(' - conflicts : %i' % solver.NumConflicts())\n", - "print(' - branches : %i' % solver.NumBranches())\n", - "print(' - wall time : %f s' % solver.WallTime())\n", - "print(' - number of solutions found: %i' % solution_printer.SolutionCount())\n", - "\n" - ] - } - ], - "metadata": {}, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/examples/notebook/wedding_optimal_chart_sat.ipynb b/examples/notebook/wedding_optimal_chart_sat.ipynb deleted file mode 100644 index 9a2a0e2e23..0000000000 --- a/examples/notebook/wedding_optimal_chart_sat.ipynb +++ /dev/null @@ -1,224 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "#\n", - "# Copyright 2018 Google.\n", - "#\n", - "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", - "# you may not use this file except in compliance with the License.\n", - "# You may obtain a copy of the License at\n", - "#\n", - "# http://www.apache.org/licenses/LICENSE-2.0\n", - "#\n", - "# Unless required by applicable law or agreed to in writing, software\n", - "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", - "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", - "# See the License for the specific language governing permissions and\n", - "# limitations under the License.\n", - "\n", - "from __future__ import print_function\n", - "from ortools.sat.python import cp_model\n", - "import time\n", - "\"\"\"Finding an optimal wedding seating chart.\n", - "\n", - "From\n", - "Meghan L. Bellows and J. D. Luc Peterson\n", - "\"Finding an optimal seating chart for a wedding\"\n", - "http://www.improbable.com/news/2012/Optimal-seating-chart.pdf\n", - "http://www.improbable.com/2012/02/12/finding-an-optimal-seating-chart-for-a-wedding\n", - "\n", - "Every year, millions of brides (not to mention their mothers, future\n", - "mothers-in-law, and occasionally grooms) struggle with one of the\n", - "most daunting tasks during the wedding-planning process: the\n", - "seating chart. The guest responses are in, banquet hall is booked,\n", - "menu choices have been made. You think the hard parts are over,\n", - "but you have yet to embark upon the biggest headache of them all.\n", - "In order to make this process easier, we present a mathematical\n", - "formulation that models the seating chart problem. This model can\n", - "be solved to find the optimal arrangement of guests at tables.\n", - "At the very least, it can provide a starting point and hopefully\n", - "minimize stress and arguments.\n", - "\n", - "Adapted from\n", - "https://github.com/google/or-tools/blob/master/examples/csharp/wedding_optimal_chart.cs\n", - "\"\"\"\n", - "\n", - "\n", - "class WeddingChartPrinter(cp_model.CpSolverSolutionCallback):\n", - " \"\"\"Print intermediate solutions.\"\"\"\n", - "\n", - " def __init__(self, seats, names, num_tables, num_guests):\n", - " cp_model.CpSolverSolutionCallback.__init__(self)\n", - " self.__solution_count = 0\n", - " self.__start_time = time.time()\n", - " self.__seats = seats\n", - " self.__names = names\n", - " self.__num_tables = num_tables\n", - " self.__num_guests = num_guests\n", - "\n", - " def OnSolutionCallback(self):\n", - " current_time = time.time()\n", - " objective = self.ObjectiveValue()\n", - " print(\"Solution %i, time = %f s, objective = %i\" %\n", - " (self.__solution_count, current_time - self.__start_time, objective))\n", - " self.__solution_count += 1\n", - "\n", - " for t in range(self.__num_tables):\n", - " print(\"Table %d: \" % t)\n", - " for g in range(self.__num_guests):\n", - " if self.Value(self.__seats[(t, g)]):\n", - " print(\" \" + self.__names[g])\n", - "\n", - " def NumSolutions(self):\n", - " return self.__solution_count\n", - "\n", - "\n", - "def BuildData():\n", - " #\n", - " # Data\n", - " #\n", - "\n", - " # Easy problem (from the paper)\n", - " # num_tables = 2\n", - " # table_capacity = 10\n", - " # min_known_neighbors = 1\n", - "\n", - " # Slightly harder problem (also from the paper)\n", - " num_tables = 5\n", - " table_capacity = 4\n", - " min_known_neighbors = 1\n", - "\n", - " # Connection matrix: who knows who, and how strong\n", - " # is the relation\n", - " C = [[1, 50, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0,\n", - " 0], [50, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0,\n", - " 0], [1, 1, 1, 50, 1, 1, 1, 1, 10, 0, 0, 0, 0, 0, 0, 0,\n", - " 0], [1, 1, 50, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0],\n", - " [1, 1, 1, 1, 1, 50, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0,\n", - " 0], [1, 1, 1, 1, 50, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0,\n", - " 0], [1, 1, 1, 1, 1, 1, 1, 50, 1, 0, 0, 0, 0, 0, 0, 0, 0],\n", - " [1, 1, 1, 1, 1, 1, 50, 1, 1, 0, 0, 0, 0, 0, 0, 0,\n", - " 0], [1, 1, 10, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0,\n", - " 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 50, 1, 1, 1, 1, 1, 1],\n", - " [0, 0, 0, 0, 0, 0, 0, 0, 0, 50, 1, 1, 1, 1, 1, 1,\n", - " 1], [0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1,\n", - " 1], [0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1], [\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1\n", - " ], [0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1], [\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1\n", - " ], [0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1]]\n", - "\n", - " # Names of the guests. B: Bride side, G: Groom side\n", - " names = [\n", - " \"Deb (B)\", \"John (B)\", \"Martha (B)\", \"Travis (B)\", \"Allan (B)\",\n", - " \"Lois (B)\", \"Jayne (B)\", \"Brad (B)\", \"Abby (B)\", \"Mary Helen (G)\",\n", - " \"Lee (G)\", \"Annika (G)\", \"Carl (G)\", \"Colin (G)\", \"Shirley (G)\",\n", - " \"DeAnn (G)\", \"Lori (G)\"\n", - " ]\n", - " return num_tables, table_capacity, min_known_neighbors, C, names\n", - "\n", - "\n", - "def SolveWithDiscreteModel():\n", - " num_tables, table_capacity, min_known_neighbors, C, names = BuildData()\n", - "\n", - " num_guests = len(C)\n", - "\n", - " all_tables = range(num_tables)\n", - " all_guests = range(num_guests)\n", - "\n", - " # Create the cp model.\n", - " model = cp_model.CpModel()\n", - "\n", - " #\n", - " # Decision variables\n", - " #\n", - " seats = {}\n", - " for t in all_tables:\n", - " for g in all_guests:\n", - " seats[(t, g)] = model.NewBoolVar(\"guest %i seats on table %i\" % (g, t))\n", - "\n", - " colocated = {}\n", - " for g1 in range(num_guests - 1):\n", - " for g2 in range(g1 + 1, num_guests):\n", - " colocated[(g1, g2)] = model.NewBoolVar(\n", - " \"guest %i seats with guest %i\" % (g1, g2))\n", - "\n", - " same_table = {}\n", - " for g1 in range(num_guests - 1):\n", - " for g2 in range(g1 + 1, num_guests):\n", - " for t in all_tables:\n", - " same_table[(g1, g2, t)] = model.NewBoolVar(\n", - " \"guest %i seats with guest %i on table %i\" % (g1, g2, t))\n", - "\n", - " # Objective\n", - " model.Maximize(\n", - " sum(C[g1][g2] * colocated[g1, g2]\n", - " for g1 in range(num_guests - 1)\n", - " for g2 in range(g1 + 1, num_guests)\n", - " if C[g1][g2] > 0))\n", - "\n", - " #\n", - " # Constraints\n", - " #\n", - "\n", - " # Everybody seats at one table.\n", - " for g in all_guests:\n", - " model.Add(sum(seats[(t, g)] for t in all_tables) == 1)\n", - "\n", - " # Tables have a max capacity.\n", - " for t in all_tables:\n", - " model.Add(sum(seats[(t, g)] for g in all_guests) <= table_capacity)\n", - "\n", - " # Link colocated with seats\n", - " for g1 in range(num_guests - 1):\n", - " for g2 in range(g1 + 1, num_guests):\n", - " for t in all_tables:\n", - " # Link same_table and seats.\n", - " model.AddBoolOr([\n", - " seats[(t, g1)].Not(), seats[(t, g2)].Not(), same_table[(g1, g2, t)]\n", - " ])\n", - " model.AddImplication(same_table[(g1, g2, t)], seats[(t, g1)])\n", - " model.AddImplication(same_table[(g1, g2, t)], seats[(t, g2)])\n", - "\n", - " # Link colocated and same_table.\n", - " model.Add(\n", - " sum(same_table[(g1, g2, t)] for t in all_tables) == colocated[(g1,\n", - " g2)])\n", - "\n", - " # Min known neighbors rule.\n", - " for t in all_tables:\n", - " model.Add(\n", - " sum(same_table[(g1, g2, t)] for g1 in range(num_guests - 1)\n", - " for g2 in range(g1 + 1, num_guests)\n", - " for t in all_tables\n", - " if C[g1][g2] > 0) >= min_known_neighbors)\n", - "\n", - " # Symmetry breaking. First guest seats on the first table.\n", - " model.Add(seats[(0, 0)] == 1)\n", - "\n", - " ### Solve model.\n", - " solver = cp_model.CpSolver()\n", - " solution_printer = WeddingChartPrinter(seats, names, num_tables, num_guests)\n", - " status = solver.SolveWithSolutionCallback(model, solution_printer)\n", - "\n", - " print(\"Statistics\")\n", - " print(\" - conflicts : %i\" % solver.NumConflicts())\n", - " print(\" - branches : %i\" % solver.NumBranches())\n", - " print(\" - wall time : %f ms\" % solver.WallTime())\n", - " print(\" - num solutions: %i\" % solution_printer.NumSolutions())\n", - "\n", - "\n", - "SolveWithDiscreteModel()\n", - "\n" - ] - } - ], - "metadata": {}, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/examples/notebook/worker_schedule_sat.ipynb b/examples/notebook/worker_schedule_sat.ipynb deleted file mode 100644 index a3f8221704..0000000000 --- a/examples/notebook/worker_schedule_sat.ipynb +++ /dev/null @@ -1,162 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Copyright 2010-2017 Google\n", - "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", - "# you may not use this file except in compliance with the License.\n", - "# You may obtain a copy of the License at\n", - "#\n", - "# http://www.apache.org/licenses/LICENSE-2.0\n", - "#\n", - "# Unless required by applicable law or agreed to in writing, software\n", - "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", - "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", - "# See the License for the specific language governing permissions and\n", - "# limitations under the License.\n", - "\n", - "from ortools.sat.python import cp_model\n", - "\n", - "\n", - "def schedule():\n", - "\n", - " # Input data.\n", - " positions = [\n", - " 1, 2, 8, 10, 5, 3, 4, 3, 6, 6, 4, 5, 4, 3, 4, 4, 3, 4, 2, 1, 0, 0, 0, 0,\n", - " 1, 2, 9, 9, 4, 3, 4, 3, 5, 4, 5, 2, 5, 6, 6, 7, 4, 2, 1, 0, 0, 0, 0, 0, 0,\n", - " 2, 7, 6, 5, 2, 4, 4, 6, 6, 4, 5, 5, 5, 7, 5, 4, 4, 2, 3, 1, 0, 0, 0, 1, 2,\n", - " 9, 7, 2, 2, 4, 2, 4, 5, 3, 2, 6, 7, 5, 6, 4, 4, 2, 1, 0, 0, 0, 0, 2, 2, 8,\n", - " 8, 6, 3, 3, 3, 10, 9, 6, 3, 3, 4, 5, 4, 5, 4, 2, 1, 0, 0, 0, 0, 1, 2, 9,\n", - " 5, 5, 4, 5, 2, 5, 7, 5, 3, 4, 8, 4, 4, 2, 3, 1, 0, 0, 0, 0, 0, 1, 2, 10,\n", - " 5, 5, 4, 5, 2, 4, 6, 7, 4, 4, 5, 4, 4, 3, 3, 2, 1, 0, 0, 0, 0\n", - " ]\n", - "\n", - " possible_shifts = [[\n", - " 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,\n", - " 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1,\n", - " 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1,\n", - " 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1,\n", - " 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 40\n", - " ], [\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", - " 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", - " 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", - " 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", - " 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", - " 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 40\n", - " ], [\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", - " 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", - " 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", - " 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", - " 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 40\n", - " ], [\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0,\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0,\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0,\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0,\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0,\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 40\n", - " ], [\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0\n", - " ], [\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1,\n", - " 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1,\n", - " 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 16\n", - " ], [\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", - " 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", - " 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 6, 16\n", - " ], [\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0,\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 7, 16\n", - " ], [\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1,\n", - " 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1,\n", - " 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1,\n", - " 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0,\n", - " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 8, 40\n", - " ]]\n", - "\n", - " # Useful numbers.\n", - " num_slots = len(positions)\n", - " all_slots = range(num_slots)\n", - "\n", - " num_shifts = len(possible_shifts)\n", - " all_shifts = range(num_shifts)\n", - "\n", - " min_number_of_workers = [5 * x for x in positions]\n", - " num_workers = 300\n", - "\n", - " # Model the problem.\n", - " model = cp_model.CpModel()\n", - "\n", - " workers_per_shift = [\n", - " model.NewIntVar(0, num_workers, 'shift[%i]' % i) for i in all_shifts\n", - " ]\n", - "\n", - " # Satisfy min requirements.\n", - " for slot in all_slots:\n", - " model.Add(\n", - " sum(workers_per_shift[shift] * possible_shifts[shift][slot]\n", - " for shift in all_shifts) >= min_number_of_workers[slot])\n", - "\n", - " # Create the objective variable.\n", - " objective = model.NewIntVar(0, num_workers, 'objective')\n", - "\n", - " # Link the objective.\n", - " model.Add(sum(workers_per_shift) == objective)\n", - "\n", - " # Minimize.\n", - " model.Minimize(objective)\n", - "\n", - " solver = cp_model.CpSolver()\n", - " status = solver.Solve(model)\n", - "\n", - " if status == cp_model.OPTIMAL:\n", - " print('Objective value = %i' % solver.ObjectiveValue())\n", - "\n", - " print('Statistics')\n", - " print(' - conflicts : %i' % solver.NumConflicts())\n", - " print(' - branches : %i' % solver.NumBranches())\n", - " print(' - wall time : %f ms' % solver.WallTime())\n", - "\n", - "\n" - ] - } - ], - "metadata": {}, - "nbformat": 4, - "nbformat_minor": 2 -}