Use new bintest framework (#4928)

This commit is contained in:
Guillaume Chatelet
2025-12-01 10:22:54 +01:00
committed by Mizux Seiha
parent 6555f4d2e4
commit b880e0fb64
227 changed files with 7091 additions and 1533 deletions

View File

@@ -13,7 +13,7 @@
load("@pip_deps//:requirements.bzl", "requirement")
load("@rules_python//python:py_binary.bzl", "py_binary")
load("//bazel:run_binary_test.bzl", "run_binary_test")
load("//tools/testing:bintest.bzl", "bintest")
package(default_visibility = ["//visibility:public"])
@@ -39,10 +39,11 @@ py_binary(
],
)
run_binary_test(
bintest(
name = "assignment_with_constraints_sat_py_test",
size = "medium",
binary = ":assignment_with_constraints_sat_py3",
srcs = [":assignment_with_constraints_sat_py_test.bintest"],
named_data = {"assignment_with_constraints_sat_py3": ":assignment_with_constraints_sat_py3"},
)
py_binary(
@@ -55,10 +56,11 @@ py_binary(
],
)
run_binary_test(
bintest(
name = "balance_group_sat_py_test",
size = "medium",
binary = ":balance_group_sat_py3",
srcs = [":balance_group_sat_py_test.bintest"],
named_data = {"balance_group_sat_py3": ":balance_group_sat_py3"},
)
py_binary(
@@ -71,11 +73,11 @@ py_binary(
],
)
run_binary_test(
bintest(
name = "bus_driver_scheduling_sat_py_test",
size = "medium",
args = ["--params=max_time_in_seconds:40"],
binary = ":bus_driver_scheduling_sat_py3",
srcs = [":bus_driver_scheduling_sat_py_test.bintest"],
named_data = {"bus_driver_scheduling_sat_py3": ":bus_driver_scheduling_sat_py3"},
)
py_binary(
@@ -88,10 +90,11 @@ py_binary(
],
)
run_binary_test(
bintest(
name = "car_sequencing_optimization_sat_py_test",
size = "small",
binary = ":car_sequencing_optimization_sat_py3",
srcs = [":car_sequencing_optimization_sat_py_test.bintest"],
named_data = {"car_sequencing_optimization_sat_py3": ":car_sequencing_optimization_sat_py3"},
)
py_binary(
@@ -104,10 +107,11 @@ py_binary(
],
)
run_binary_test(
bintest(
name = "chemical_balance_sat_py_test",
size = "medium",
binary = ":chemical_balance_sat_py3",
srcs = [":chemical_balance_sat_py_test.bintest"],
named_data = {"chemical_balance_sat_py3": ":chemical_balance_sat_py3"},
)
py_binary(
@@ -120,10 +124,11 @@ py_binary(
],
)
run_binary_test(
bintest(
name = "clustering_sat_py_test",
size = "medium",
binary = ":clustering_sat_py3",
srcs = [":clustering_sat_py_test.bintest"],
named_data = {"clustering_sat_py3": ":clustering_sat_py3"},
)
py_binary(
@@ -136,10 +141,11 @@ py_binary(
],
)
run_binary_test(
bintest(
name = "cover_rectangle_sat_py_test",
size = "medium",
binary = ":cover_rectangle_sat_py3",
srcs = [":cover_rectangle_sat_py_test.bintest"],
named_data = {"cover_rectangle_sat_py3": ":cover_rectangle_sat_py3"},
)
py_binary(
@@ -149,9 +155,10 @@ py_binary(
deps = ["//ortools/sat/python:cp_model"],
)
run_binary_test(
bintest(
name = "flexible_job_shop_sat_py_test",
binary = ":flexible_job_shop_sat_py3",
srcs = [":flexible_job_shop_sat_py_test.bintest"],
named_data = {"flexible_job_shop_sat_py3": ":flexible_job_shop_sat_py3"},
)
py_binary(
@@ -165,9 +172,10 @@ py_binary(
],
)
run_binary_test(
bintest(
name = "gate_scheduling_sat_py_test",
binary = ":gate_scheduling_sat_py3",
srcs = [":gate_scheduling_sat_py_test.bintest"],
named_data = {"gate_scheduling_sat_py3": ":gate_scheduling_sat_py3"},
)
py_binary(
@@ -180,10 +188,11 @@ py_binary(
],
)
run_binary_test(
bintest(
name = "golomb_sat_py_test",
size = "medium",
binary = ":golomb_sat_py3",
srcs = [":golomb_sat_py_test.bintest"],
named_data = {"golomb_sat_py3": ":golomb_sat_py3"},
)
py_binary(
@@ -197,9 +206,10 @@ py_binary(
],
)
run_binary_test(
bintest(
name = "hidato_sat_py_test",
binary = ":hidato_sat_py3",
srcs = [":hidato_sat_py_test.bintest"],
named_data = {"hidato_sat_py3": ":hidato_sat_py3"},
)
py_binary(
@@ -209,9 +219,10 @@ py_binary(
deps = ["//ortools/sat/python:cp_model"],
)
run_binary_test(
bintest(
name = "jobshop_ft06_distance_sat_py_test",
binary = ":jobshop_ft06_distance_sat_py3",
srcs = [":jobshop_ft06_distance_sat_py_test.bintest"],
named_data = {"jobshop_ft06_distance_sat_py3": ":jobshop_ft06_distance_sat_py3"},
)
py_binary(
@@ -224,9 +235,10 @@ py_binary(
],
)
run_binary_test(
bintest(
name = "jobshop_ft06_sat_py_test",
binary = ":jobshop_ft06_sat_py3",
srcs = [":jobshop_ft06_sat_py_test.bintest"],
named_data = {"jobshop_ft06_sat_py3": ":jobshop_ft06_sat_py3"},
)
py_binary(
@@ -239,10 +251,11 @@ py_binary(
],
)
run_binary_test(
bintest(
name = "jobshop_with_maintenance_sat_py_test",
size = "medium",
binary = ":jobshop_with_maintenance_sat_py3",
srcs = [":jobshop_with_maintenance_sat_py_test.bintest"],
named_data = {"jobshop_with_maintenance_sat_py3": ":jobshop_with_maintenance_sat_py3"},
)
py_binary(
@@ -257,10 +270,11 @@ py_binary(
],
)
run_binary_test(
bintest(
name = "knapsack_2d_sat_py_test",
size = "medium",
binary = ":knapsack_2d_sat_py3",
srcs = [":knapsack_2d_sat_py_test.bintest"],
named_data = {"knapsack_2d_sat_py3": ":knapsack_2d_sat_py3"},
)
py_binary(
@@ -273,12 +287,13 @@ py_binary(
],
)
run_binary_test(
bintest(
name = "line_balancing_sat_salbp_20_1_py_test",
args = ["--input=$(rootpath //examples/python/testdata:salbp_20_1.alb)"],
binary = ":line_balancing_sat_py3",
data = ["//examples/python/testdata:salbp_20_1.alb"],
grep_lines = ["objective: 3"],
srcs = [":line_balancing_sat_salbp_20_1_py_test.bintest"],
named_data = {
"line_balancing_sat_py3": ":line_balancing_sat_py3",
"salbp_20_1.alb": "//examples/python/testdata:salbp_20_1.alb",
},
)
py_binary(
@@ -291,10 +306,11 @@ py_binary(
],
)
run_binary_test(
bintest(
name = "maximize_combinations_sat_py_test",
size = "medium",
binary = ":maximize_combinations_sat_py3",
srcs = [":maximize_combinations_sat_py_test.bintest"],
named_data = {"maximize_combinations_sat_py3": ":maximize_combinations_sat_py3"},
)
py_binary(
@@ -307,9 +323,10 @@ py_binary(
],
)
run_binary_test(
bintest(
name = "maze_escape_sat_py_test",
binary = ":maze_escape_sat_py3",
srcs = [":maze_escape_sat_py_test.bintest"],
named_data = {"maze_escape_sat_py3": ":maze_escape_sat_py3"},
)
py_binary(
@@ -322,9 +339,10 @@ py_binary(
],
)
run_binary_test(
bintest(
name = "music_playlist_sat_py_test",
binary = ":music_playlist_sat_py3",
srcs = [":music_playlist_sat_py_test.bintest"],
named_data = {"music_playlist_sat_py3": ":music_playlist_sat_py3"},
)
py_binary(
@@ -337,10 +355,11 @@ py_binary(
],
)
run_binary_test(
bintest(
name = "no_wait_baking_scheduling_sat_py_test",
size = "medium",
binary = ":no_wait_baking_scheduling_sat_py3",
srcs = [":no_wait_baking_scheduling_sat_py_test.bintest"],
named_data = {"no_wait_baking_scheduling_sat_py3": ":no_wait_baking_scheduling_sat_py3"},
)
py_binary(
@@ -353,10 +372,11 @@ py_binary(
],
)
run_binary_test(
bintest(
name = "pell_equation_sat_py_test",
size = "medium",
binary = ":pell_equation_sat_py3",
srcs = [":pell_equation_sat_py_test.bintest"],
named_data = {"pell_equation_sat_py3": ":pell_equation_sat_py3"},
)
py_binary(
@@ -369,10 +389,11 @@ py_binary(
],
)
run_binary_test(
bintest(
name = "pentominoes_sat_py_test",
size = "medium",
binary = ":pentominoes_sat_py3",
srcs = [":pentominoes_sat_py_test.bintest"],
named_data = {"pentominoes_sat_py3": ":pentominoes_sat_py3"},
)
py_binary(
@@ -385,10 +406,11 @@ py_binary(
],
)
run_binary_test(
bintest(
name = "prize_collecting_tsp_sat_py_test",
size = "medium",
binary = ":prize_collecting_tsp_sat_py3",
srcs = [":prize_collecting_tsp_sat_py_test.bintest"],
named_data = {"prize_collecting_tsp_sat_py3": ":prize_collecting_tsp_sat_py3"},
)
py_binary(
@@ -401,10 +423,11 @@ py_binary(
],
)
run_binary_test(
bintest(
name = "prize_collecting_vrp_sat_py_test",
size = "medium",
binary = ":prize_collecting_vrp_sat_py3",
srcs = [":prize_collecting_vrp_sat_py_test.bintest"],
named_data = {"prize_collecting_vrp_sat_py3": ":prize_collecting_vrp_sat_py3"},
)
py_binary(
@@ -417,26 +440,29 @@ py_binary(
],
)
run_binary_test(
bintest(
name = "qubo_sat_py_test",
size = "medium",
binary = ":qubo_sat_py3",
srcs = [":qubo_sat_py_test.bintest"],
named_data = {"qubo_sat_py3": ":qubo_sat_py3"},
)
run_binary_test(
bintest(
name = "rcpsp_sat_c1510_1_py_test",
args = ["--input=$(rootpath //ortools/scheduling/testdata:c1510_1.mm.txt)"],
binary = ":rcpsp_sat_py3",
data = ["//ortools/scheduling/testdata:c1510_1.mm.txt"],
grep_lines = ["objective: 21"],
srcs = [":rcpsp_sat_c1510_1_py_test.bintest"],
named_data = {
"rcpsp_sat_py3": ":rcpsp_sat_py3",
"c1510_1.mm.txt": "//ortools/scheduling/testdata:c1510_1.mm.txt",
},
)
run_binary_test(
bintest(
name = "rcpsp_sat_j301_1_py_test",
args = ["--input=$(rootpath //ortools/scheduling/testdata:j301_1.sm)"],
binary = ":rcpsp_sat_py3",
data = ["//ortools/scheduling/testdata:j301_1.sm"],
grep_lines = ["objective: 43"],
srcs = [":rcpsp_sat_j301_1_py_test.bintest"],
named_data = {
"rcpsp_sat_py3": ":rcpsp_sat_py3",
"j301_1.sm": "//ortools/scheduling/testdata:j301_1.sm",
},
)
py_binary(
@@ -461,10 +487,10 @@ py_binary(
],
)
run_binary_test(
bintest(
name = "shift_scheduling_sat_py_test",
args = ["--params=max_time_in_seconds:10"],
binary = ":shift_scheduling_sat_py3",
srcs = [":shift_scheduling_sat_py_test.bintest"],
named_data = {"shift_scheduling_sat_py3": ":shift_scheduling_sat_py3"},
)
py_binary(
@@ -477,10 +503,11 @@ py_binary(
],
)
run_binary_test(
bintest(
name = "single_machine_scheduling_with_setup_release_due_dates_sat_py_test",
size = "medium",
binary = ":single_machine_scheduling_with_setup_release_due_dates_sat_py3",
srcs = [":single_machine_scheduling_with_setup_release_due_dates_sat_py_test.bintest"],
named_data = {"single_machine_scheduling_with_setup_release_due_dates_sat_py3": ":single_machine_scheduling_with_setup_release_due_dates_sat_py3"},
)
py_binary(
@@ -493,9 +520,10 @@ py_binary(
],
)
run_binary_test(
bintest(
name = "spread_robots_sat_py_test",
binary = ":spread_robots_sat_py3",
srcs = [":spread_robots_sat_py_test.bintest"],
named_data = {"spread_robots_sat_py3": ":spread_robots_sat_py3"},
)
py_binary(
@@ -508,9 +536,10 @@ py_binary(
],
)
run_binary_test(
bintest(
name = "steel_mill_slab_sat_py_test",
binary = ":steel_mill_slab_sat_py3",
srcs = [":steel_mill_slab_sat_py_test.bintest"],
named_data = {"steel_mill_slab_sat_py3": ":steel_mill_slab_sat_py3"},
)
py_binary(
@@ -520,9 +549,10 @@ py_binary(
deps = ["//ortools/sat/python:cp_model"],
)
run_binary_test(
bintest(
name = "sudoku_sat_py_test",
binary = ":sudoku_sat_py3",
srcs = [":sudoku_sat_py_test.bintest"],
named_data = {"sudoku_sat_py3": ":sudoku_sat_py3"},
)
py_binary(
@@ -535,10 +565,11 @@ py_binary(
],
)
run_binary_test(
bintest(
name = "task_allocation_sat_py_test",
size = "medium",
binary = ":task_allocation_sat_py3",
srcs = [":task_allocation_sat_py_test.bintest"],
named_data = {"task_allocation_sat_py3": ":task_allocation_sat_py3"},
)
py_binary(
@@ -551,10 +582,11 @@ py_binary(
],
)
run_binary_test(
bintest(
name = "tasks_and_workers_assignment_sat_py_test",
size = "medium",
binary = ":tasks_and_workers_assignment_sat_py3",
srcs = [":tasks_and_workers_assignment_sat_py_test.bintest"],
named_data = {"tasks_and_workers_assignment_sat_py3": ":tasks_and_workers_assignment_sat_py3"},
)
py_binary(
@@ -568,10 +600,11 @@ py_binary(
],
)
run_binary_test(
bintest(
name = "test_scheduling_sat_py_test",
size = "medium",
binary = ":test_scheduling_sat_py3",
srcs = [":test_scheduling_sat_py_test.bintest"],
named_data = {"test_scheduling_sat_py3": ":test_scheduling_sat_py3"},
)
py_binary(
@@ -581,10 +614,11 @@ py_binary(
deps = ["//ortools/sat/python:cp_model"],
)
run_binary_test(
bintest(
name = "tsp_sat_py_test",
size = "medium",
binary = ":tsp_sat_py3",
srcs = [":tsp_sat_py_test.bintest"],
named_data = {"tsp_sat_py3": ":tsp_sat_py3"},
)
py_binary(
@@ -597,10 +631,11 @@ py_binary(
],
)
run_binary_test(
bintest(
name = "vendor_scheduling_sat_py_test",
size = "medium",
binary = ":vendor_scheduling_sat_py3",
srcs = [":vendor_scheduling_sat_py_test.bintest"],
named_data = {"vendor_scheduling_sat_py3": ":vendor_scheduling_sat_py3"},
)
py_binary(
@@ -613,10 +648,11 @@ py_binary(
],
)
run_binary_test(
bintest(
name = "wedding_optimal_chart_sat_py_test",
size = "medium",
binary = ":wedding_optimal_chart_sat_py3",
srcs = [":wedding_optimal_chart_sat_py_test.bintest"],
named_data = {"wedding_optimal_chart_sat_py3": ":wedding_optimal_chart_sat_py3"},
)
py_binary(
@@ -626,7 +662,8 @@ py_binary(
deps = ["//ortools/sat/python:cp_model"],
)
run_binary_test(
bintest(
name = "zebra_sat_py_test",
binary = ":zebra_sat_py3",
srcs = [":zebra_sat_py_test.bintest"],
named_data = {"zebra_sat_py3": ":zebra_sat_py3"},
)

View File

@@ -0,0 +1,434 @@
# This file is auto generated by bazel2cmake.py from examples/python/BUILD.bazel
# Don't edit manually, your changes will be lost.
# You can update this file by running:
# python3 tools/build/bazel2cmake.py examples/python/BUILD.bazel
add_python_binary(
NAME bzl_py_example_arc_flow_cutting_stock_sat_py3
FILE ${CMAKE_CURRENT_SOURCE_DIR}/arc_flow_cutting_stock_sat.py
)
add_python_binary(
NAME bzl_py_example_assignment_with_constraints_sat_py3
FILE ${CMAKE_CURRENT_SOURCE_DIR}/assignment_with_constraints_sat.py
)
ortools_cxx_bintest(
NAME bzl_py_example_assignment_with_constraints_sat_py_test
SCRIPT ${CMAKE_CURRENT_SOURCE_DIR}/assignment_with_constraints_sat_py_test.bintest
ENVIRONMENT BINTEST_assignment_with_constraints_sat_py3=$<TARGET_FILE:bzl_py_example_assignment_with_constraints_sat_py3>
)
add_python_binary(
NAME bzl_py_example_balance_group_sat_py3
FILE ${CMAKE_CURRENT_SOURCE_DIR}/balance_group_sat.py
)
ortools_cxx_bintest(
NAME bzl_py_example_balance_group_sat_py_test
SCRIPT ${CMAKE_CURRENT_SOURCE_DIR}/balance_group_sat_py_test.bintest
ENVIRONMENT BINTEST_balance_group_sat_py3=$<TARGET_FILE:bzl_py_example_balance_group_sat_py3>
)
add_python_binary(
NAME bzl_py_example_bus_driver_scheduling_sat_py3
FILE ${CMAKE_CURRENT_SOURCE_DIR}/bus_driver_scheduling_sat.py
)
ortools_cxx_bintest(
NAME bzl_py_example_bus_driver_scheduling_sat_py_test
SCRIPT ${CMAKE_CURRENT_SOURCE_DIR}/bus_driver_scheduling_sat_py_test.bintest
ENVIRONMENT BINTEST_bus_driver_scheduling_sat_py3=$<TARGET_FILE:bzl_py_example_bus_driver_scheduling_sat_py3>
)
add_python_binary(
NAME bzl_py_example_car_sequencing_optimization_sat_py3
FILE ${CMAKE_CURRENT_SOURCE_DIR}/car_sequencing_optimization_sat.py
)
ortools_cxx_bintest(
NAME bzl_py_example_car_sequencing_optimization_sat_py_test
SCRIPT ${CMAKE_CURRENT_SOURCE_DIR}/car_sequencing_optimization_sat_py_test.bintest
ENVIRONMENT BINTEST_car_sequencing_optimization_sat_py3=$<TARGET_FILE:bzl_py_example_car_sequencing_optimization_sat_py3>
)
add_python_binary(
NAME bzl_py_example_chemical_balance_sat_py3
FILE ${CMAKE_CURRENT_SOURCE_DIR}/chemical_balance_sat.py
)
ortools_cxx_bintest(
NAME bzl_py_example_chemical_balance_sat_py_test
SCRIPT ${CMAKE_CURRENT_SOURCE_DIR}/chemical_balance_sat_py_test.bintest
ENVIRONMENT BINTEST_chemical_balance_sat_py3=$<TARGET_FILE:bzl_py_example_chemical_balance_sat_py3>
)
add_python_binary(
NAME bzl_py_example_clustering_sat_py3
FILE ${CMAKE_CURRENT_SOURCE_DIR}/clustering_sat.py
)
ortools_cxx_bintest(
NAME bzl_py_example_clustering_sat_py_test
SCRIPT ${CMAKE_CURRENT_SOURCE_DIR}/clustering_sat_py_test.bintest
ENVIRONMENT BINTEST_clustering_sat_py3=$<TARGET_FILE:bzl_py_example_clustering_sat_py3>
)
add_python_binary(
NAME bzl_py_example_cover_rectangle_sat_py3
FILE ${CMAKE_CURRENT_SOURCE_DIR}/cover_rectangle_sat.py
)
ortools_cxx_bintest(
NAME bzl_py_example_cover_rectangle_sat_py_test
SCRIPT ${CMAKE_CURRENT_SOURCE_DIR}/cover_rectangle_sat_py_test.bintest
ENVIRONMENT BINTEST_cover_rectangle_sat_py3=$<TARGET_FILE:bzl_py_example_cover_rectangle_sat_py3>
)
add_python_binary(
NAME bzl_py_example_flexible_job_shop_sat_py3
FILE ${CMAKE_CURRENT_SOURCE_DIR}/flexible_job_shop_sat.py
)
ortools_cxx_bintest(
NAME bzl_py_example_flexible_job_shop_sat_py_test
SCRIPT ${CMAKE_CURRENT_SOURCE_DIR}/flexible_job_shop_sat_py_test.bintest
ENVIRONMENT BINTEST_flexible_job_shop_sat_py3=$<TARGET_FILE:bzl_py_example_flexible_job_shop_sat_py3>
)
add_python_binary(
NAME bzl_py_example_gate_scheduling_sat_py3
FILE ${CMAKE_CURRENT_SOURCE_DIR}/gate_scheduling_sat.py
)
ortools_cxx_bintest(
NAME bzl_py_example_gate_scheduling_sat_py_test
SCRIPT ${CMAKE_CURRENT_SOURCE_DIR}/gate_scheduling_sat_py_test.bintest
ENVIRONMENT BINTEST_gate_scheduling_sat_py3=$<TARGET_FILE:bzl_py_example_gate_scheduling_sat_py3>
)
add_python_binary(
NAME bzl_py_example_golomb_sat_py3
FILE ${CMAKE_CURRENT_SOURCE_DIR}/golomb_sat.py
)
ortools_cxx_bintest(
NAME bzl_py_example_golomb_sat_py_test
SCRIPT ${CMAKE_CURRENT_SOURCE_DIR}/golomb_sat_py_test.bintest
ENVIRONMENT BINTEST_golomb_sat_py3=$<TARGET_FILE:bzl_py_example_golomb_sat_py3>
)
add_python_binary(
NAME bzl_py_example_hidato_sat_py3
FILE ${CMAKE_CURRENT_SOURCE_DIR}/hidato_sat.py
)
ortools_cxx_bintest(
NAME bzl_py_example_hidato_sat_py_test
SCRIPT ${CMAKE_CURRENT_SOURCE_DIR}/hidato_sat_py_test.bintest
ENVIRONMENT BINTEST_hidato_sat_py3=$<TARGET_FILE:bzl_py_example_hidato_sat_py3>
)
add_python_binary(
NAME bzl_py_example_jobshop_ft06_distance_sat_py3
FILE ${CMAKE_CURRENT_SOURCE_DIR}/jobshop_ft06_distance_sat.py
)
ortools_cxx_bintest(
NAME bzl_py_example_jobshop_ft06_distance_sat_py_test
SCRIPT ${CMAKE_CURRENT_SOURCE_DIR}/jobshop_ft06_distance_sat_py_test.bintest
ENVIRONMENT BINTEST_jobshop_ft06_distance_sat_py3=$<TARGET_FILE:bzl_py_example_jobshop_ft06_distance_sat_py3>
)
add_python_binary(
NAME bzl_py_example_jobshop_ft06_sat_py3
FILE ${CMAKE_CURRENT_SOURCE_DIR}/jobshop_ft06_sat.py
)
ortools_cxx_bintest(
NAME bzl_py_example_jobshop_ft06_sat_py_test
SCRIPT ${CMAKE_CURRENT_SOURCE_DIR}/jobshop_ft06_sat_py_test.bintest
ENVIRONMENT BINTEST_jobshop_ft06_sat_py3=$<TARGET_FILE:bzl_py_example_jobshop_ft06_sat_py3>
)
add_python_binary(
NAME bzl_py_example_jobshop_with_maintenance_sat_py3
FILE ${CMAKE_CURRENT_SOURCE_DIR}/jobshop_with_maintenance_sat.py
)
ortools_cxx_bintest(
NAME bzl_py_example_jobshop_with_maintenance_sat_py_test
SCRIPT ${CMAKE_CURRENT_SOURCE_DIR}/jobshop_with_maintenance_sat_py_test.bintest
ENVIRONMENT BINTEST_jobshop_with_maintenance_sat_py3=$<TARGET_FILE:bzl_py_example_jobshop_with_maintenance_sat_py3>
)
add_python_binary(
NAME bzl_py_example_knapsack_2d_sat_py3
FILE ${CMAKE_CURRENT_SOURCE_DIR}/knapsack_2d_sat.py
)
ortools_cxx_bintest(
NAME bzl_py_example_knapsack_2d_sat_py_test
SCRIPT ${CMAKE_CURRENT_SOURCE_DIR}/knapsack_2d_sat_py_test.bintest
ENVIRONMENT BINTEST_knapsack_2d_sat_py3=$<TARGET_FILE:bzl_py_example_knapsack_2d_sat_py3>
)
add_python_binary(
NAME bzl_py_example_line_balancing_sat_py3
FILE ${CMAKE_CURRENT_SOURCE_DIR}/line_balancing_sat.py
)
ortools_cxx_bintest(
NAME bzl_py_example_line_balancing_sat_salbp_20_1_py_test
SCRIPT ${CMAKE_CURRENT_SOURCE_DIR}/line_balancing_sat_salbp_20_1_py_test.bintest
ENVIRONMENT BINTEST_line_balancing_sat_py3=$<TARGET_FILE:bzl_py_example_line_balancing_sat_py3> BINTEST_salbp_20_1.alb=${CMAKE_SOURCE_DIR}/examples/python/testdata/salbp_20_1.alb
)
add_python_binary(
NAME bzl_py_example_maximize_combinations_sat_py3
FILE ${CMAKE_CURRENT_SOURCE_DIR}/maximize_combinations_sat.py
)
ortools_cxx_bintest(
NAME bzl_py_example_maximize_combinations_sat_py_test
SCRIPT ${CMAKE_CURRENT_SOURCE_DIR}/maximize_combinations_sat_py_test.bintest
ENVIRONMENT BINTEST_maximize_combinations_sat_py3=$<TARGET_FILE:bzl_py_example_maximize_combinations_sat_py3>
)
add_python_binary(
NAME bzl_py_example_maze_escape_sat_py3
FILE ${CMAKE_CURRENT_SOURCE_DIR}/maze_escape_sat.py
)
ortools_cxx_bintest(
NAME bzl_py_example_maze_escape_sat_py_test
SCRIPT ${CMAKE_CURRENT_SOURCE_DIR}/maze_escape_sat_py_test.bintest
ENVIRONMENT BINTEST_maze_escape_sat_py3=$<TARGET_FILE:bzl_py_example_maze_escape_sat_py3>
)
add_python_binary(
NAME bzl_py_example_music_playlist_sat_py3
FILE ${CMAKE_CURRENT_SOURCE_DIR}/music_playlist_sat.py
)
ortools_cxx_bintest(
NAME bzl_py_example_music_playlist_sat_py_test
SCRIPT ${CMAKE_CURRENT_SOURCE_DIR}/music_playlist_sat_py_test.bintest
ENVIRONMENT BINTEST_music_playlist_sat_py3=$<TARGET_FILE:bzl_py_example_music_playlist_sat_py3>
)
add_python_binary(
NAME bzl_py_example_no_wait_baking_scheduling_sat_py3
FILE ${CMAKE_CURRENT_SOURCE_DIR}/no_wait_baking_scheduling_sat.py
)
ortools_cxx_bintest(
NAME bzl_py_example_no_wait_baking_scheduling_sat_py_test
SCRIPT ${CMAKE_CURRENT_SOURCE_DIR}/no_wait_baking_scheduling_sat_py_test.bintest
ENVIRONMENT BINTEST_no_wait_baking_scheduling_sat_py3=$<TARGET_FILE:bzl_py_example_no_wait_baking_scheduling_sat_py3>
)
add_python_binary(
NAME bzl_py_example_pell_equation_sat_py3
FILE ${CMAKE_CURRENT_SOURCE_DIR}/pell_equation_sat.py
)
ortools_cxx_bintest(
NAME bzl_py_example_pell_equation_sat_py_test
SCRIPT ${CMAKE_CURRENT_SOURCE_DIR}/pell_equation_sat_py_test.bintest
ENVIRONMENT BINTEST_pell_equation_sat_py3=$<TARGET_FILE:bzl_py_example_pell_equation_sat_py3>
)
add_python_binary(
NAME bzl_py_example_pentominoes_sat_py3
FILE ${CMAKE_CURRENT_SOURCE_DIR}/pentominoes_sat.py
)
ortools_cxx_bintest(
NAME bzl_py_example_pentominoes_sat_py_test
SCRIPT ${CMAKE_CURRENT_SOURCE_DIR}/pentominoes_sat_py_test.bintest
ENVIRONMENT BINTEST_pentominoes_sat_py3=$<TARGET_FILE:bzl_py_example_pentominoes_sat_py3>
)
add_python_binary(
NAME bzl_py_example_prize_collecting_tsp_sat_py3
FILE ${CMAKE_CURRENT_SOURCE_DIR}/prize_collecting_tsp_sat.py
)
ortools_cxx_bintest(
NAME bzl_py_example_prize_collecting_tsp_sat_py_test
SCRIPT ${CMAKE_CURRENT_SOURCE_DIR}/prize_collecting_tsp_sat_py_test.bintest
ENVIRONMENT BINTEST_prize_collecting_tsp_sat_py3=$<TARGET_FILE:bzl_py_example_prize_collecting_tsp_sat_py3>
)
add_python_binary(
NAME bzl_py_example_prize_collecting_vrp_sat_py3
FILE ${CMAKE_CURRENT_SOURCE_DIR}/prize_collecting_vrp_sat.py
)
ortools_cxx_bintest(
NAME bzl_py_example_prize_collecting_vrp_sat_py_test
SCRIPT ${CMAKE_CURRENT_SOURCE_DIR}/prize_collecting_vrp_sat_py_test.bintest
ENVIRONMENT BINTEST_prize_collecting_vrp_sat_py3=$<TARGET_FILE:bzl_py_example_prize_collecting_vrp_sat_py3>
)
add_python_binary(
NAME bzl_py_example_qubo_sat_py3
FILE ${CMAKE_CURRENT_SOURCE_DIR}/qubo_sat.py
)
ortools_cxx_bintest(
NAME bzl_py_example_qubo_sat_py_test
SCRIPT ${CMAKE_CURRENT_SOURCE_DIR}/qubo_sat_py_test.bintest
ENVIRONMENT BINTEST_qubo_sat_py3=$<TARGET_FILE:bzl_py_example_qubo_sat_py3>
)
ortools_cxx_bintest(
NAME bzl_py_example_rcpsp_sat_c1510_1_py_test
SCRIPT ${CMAKE_CURRENT_SOURCE_DIR}/rcpsp_sat_c1510_1_py_test.bintest
ENVIRONMENT BINTEST_rcpsp_sat_py3=$<TARGET_FILE:bzl_py_example_rcpsp_sat_py3> BINTEST_c1510_1.mm.txt=${CMAKE_SOURCE_DIR}/ortools/scheduling/testdata/c1510_1.mm.txt
)
ortools_cxx_bintest(
NAME bzl_py_example_rcpsp_sat_j301_1_py_test
SCRIPT ${CMAKE_CURRENT_SOURCE_DIR}/rcpsp_sat_j301_1_py_test.bintest
ENVIRONMENT BINTEST_rcpsp_sat_py3=$<TARGET_FILE:bzl_py_example_rcpsp_sat_py3> BINTEST_j301_1.sm=${CMAKE_SOURCE_DIR}/ortools/scheduling/testdata/j301_1.sm
)
add_python_binary(
NAME bzl_py_example_rcpsp_sat_py3
FILE ${CMAKE_CURRENT_SOURCE_DIR}/rcpsp_sat.py
)
add_python_binary(
NAME bzl_py_example_shift_scheduling_sat_py3
FILE ${CMAKE_CURRENT_SOURCE_DIR}/shift_scheduling_sat.py
)
ortools_cxx_bintest(
NAME bzl_py_example_shift_scheduling_sat_py_test
SCRIPT ${CMAKE_CURRENT_SOURCE_DIR}/shift_scheduling_sat_py_test.bintest
ENVIRONMENT BINTEST_shift_scheduling_sat_py3=$<TARGET_FILE:bzl_py_example_shift_scheduling_sat_py3>
)
add_python_binary(
NAME bzl_py_example_single_machine_scheduling_with_setup_release_due_dates_sat_py3
FILE ${CMAKE_CURRENT_SOURCE_DIR}/single_machine_scheduling_with_setup_release_due_dates_sat.py
)
ortools_cxx_bintest(
NAME bzl_py_example_single_machine_scheduling_with_setup_release_due_dates_sat_py_test
SCRIPT ${CMAKE_CURRENT_SOURCE_DIR}/single_machine_scheduling_with_setup_release_due_dates_sat_py_test.bintest
ENVIRONMENT BINTEST_single_machine_scheduling_with_setup_release_due_dates_sat_py3=$<TARGET_FILE:bzl_py_example_single_machine_scheduling_with_setup_release_due_dates_sat_py3>
)
add_python_binary(
NAME bzl_py_example_spread_robots_sat_py3
FILE ${CMAKE_CURRENT_SOURCE_DIR}/spread_robots_sat.py
)
ortools_cxx_bintest(
NAME bzl_py_example_spread_robots_sat_py_test
SCRIPT ${CMAKE_CURRENT_SOURCE_DIR}/spread_robots_sat_py_test.bintest
ENVIRONMENT BINTEST_spread_robots_sat_py3=$<TARGET_FILE:bzl_py_example_spread_robots_sat_py3>
)
add_python_binary(
NAME bzl_py_example_steel_mill_slab_sat_py3
FILE ${CMAKE_CURRENT_SOURCE_DIR}/steel_mill_slab_sat.py
)
ortools_cxx_bintest(
NAME bzl_py_example_steel_mill_slab_sat_py_test
SCRIPT ${CMAKE_CURRENT_SOURCE_DIR}/steel_mill_slab_sat_py_test.bintest
ENVIRONMENT BINTEST_steel_mill_slab_sat_py3=$<TARGET_FILE:bzl_py_example_steel_mill_slab_sat_py3>
)
add_python_binary(
NAME bzl_py_example_sudoku_sat_py3
FILE ${CMAKE_CURRENT_SOURCE_DIR}/sudoku_sat.py
)
ortools_cxx_bintest(
NAME bzl_py_example_sudoku_sat_py_test
SCRIPT ${CMAKE_CURRENT_SOURCE_DIR}/sudoku_sat_py_test.bintest
ENVIRONMENT BINTEST_sudoku_sat_py3=$<TARGET_FILE:bzl_py_example_sudoku_sat_py3>
)
add_python_binary(
NAME bzl_py_example_task_allocation_sat_py3
FILE ${CMAKE_CURRENT_SOURCE_DIR}/task_allocation_sat.py
)
ortools_cxx_bintest(
NAME bzl_py_example_task_allocation_sat_py_test
SCRIPT ${CMAKE_CURRENT_SOURCE_DIR}/task_allocation_sat_py_test.bintest
ENVIRONMENT BINTEST_task_allocation_sat_py3=$<TARGET_FILE:bzl_py_example_task_allocation_sat_py3>
)
add_python_binary(
NAME bzl_py_example_tasks_and_workers_assignment_sat_py3
FILE ${CMAKE_CURRENT_SOURCE_DIR}/tasks_and_workers_assignment_sat.py
)
ortools_cxx_bintest(
NAME bzl_py_example_tasks_and_workers_assignment_sat_py_test
SCRIPT ${CMAKE_CURRENT_SOURCE_DIR}/tasks_and_workers_assignment_sat_py_test.bintest
ENVIRONMENT BINTEST_tasks_and_workers_assignment_sat_py3=$<TARGET_FILE:bzl_py_example_tasks_and_workers_assignment_sat_py3>
)
add_python_binary(
NAME bzl_py_example_test_scheduling_sat_py3
FILE ${CMAKE_CURRENT_SOURCE_DIR}/test_scheduling_sat.py
)
ortools_cxx_bintest(
NAME bzl_py_example_test_scheduling_sat_py_test
SCRIPT ${CMAKE_CURRENT_SOURCE_DIR}/test_scheduling_sat_py_test.bintest
ENVIRONMENT BINTEST_test_scheduling_sat_py3=$<TARGET_FILE:bzl_py_example_test_scheduling_sat_py3>
)
add_python_binary(
NAME bzl_py_example_tsp_sat_py3
FILE ${CMAKE_CURRENT_SOURCE_DIR}/tsp_sat.py
)
ortools_cxx_bintest(
NAME bzl_py_example_tsp_sat_py_test
SCRIPT ${CMAKE_CURRENT_SOURCE_DIR}/tsp_sat_py_test.bintest
ENVIRONMENT BINTEST_tsp_sat_py3=$<TARGET_FILE:bzl_py_example_tsp_sat_py3>
)
add_python_binary(
NAME bzl_py_example_vendor_scheduling_sat_py3
FILE ${CMAKE_CURRENT_SOURCE_DIR}/vendor_scheduling_sat.py
)
ortools_cxx_bintest(
NAME bzl_py_example_vendor_scheduling_sat_py_test
SCRIPT ${CMAKE_CURRENT_SOURCE_DIR}/vendor_scheduling_sat_py_test.bintest
ENVIRONMENT BINTEST_vendor_scheduling_sat_py3=$<TARGET_FILE:bzl_py_example_vendor_scheduling_sat_py3>
)
add_python_binary(
NAME bzl_py_example_wedding_optimal_chart_sat_py3
FILE ${CMAKE_CURRENT_SOURCE_DIR}/wedding_optimal_chart_sat.py
)
ortools_cxx_bintest(
NAME bzl_py_example_wedding_optimal_chart_sat_py_test
SCRIPT ${CMAKE_CURRENT_SOURCE_DIR}/wedding_optimal_chart_sat_py_test.bintest
ENVIRONMENT BINTEST_wedding_optimal_chart_sat_py3=$<TARGET_FILE:bzl_py_example_wedding_optimal_chart_sat_py3>
)
add_python_binary(
NAME bzl_py_example_zebra_sat_py3
FILE ${CMAKE_CURRENT_SOURCE_DIR}/zebra_sat.py
)
ortools_cxx_bintest(
NAME bzl_py_example_zebra_sat_py_test
SCRIPT ${CMAKE_CURRENT_SOURCE_DIR}/zebra_sat_py_test.bintest
ENVIRONMENT BINTEST_zebra_sat_py3=$<TARGET_FILE:bzl_py_example_zebra_sat_py3>
)

View File

@@ -15,12 +15,6 @@ if(NOT BUILD_PYTHON_EXAMPLES)
return()
endif()
file(GLOB PYTHON_SRCS "*.py")
# Remove too long examples
list(FILTER PYTHON_SRCS EXCLUDE REGEX ".*/line_balancing_sat.py") # need input file
list(FILTER PYTHON_SRCS EXCLUDE REGEX ".*/bus_driver_scheduling_sat.py") # too long
list(FILTER PYTHON_SRCS EXCLUDE REGEX ".*/cvrptw_plot.py") # depend on numpy
foreach(FILE_NAME IN LISTS PYTHON_SRCS)
add_python_example(FILE_NAME ${FILE_NAME})
endforeach()
if(NOT WIN32)
include("CMakeBazel.txt")
endif()

View File

@@ -0,0 +1 @@
RUN: $(appointments_py3)

View File

@@ -0,0 +1 @@
RUN: $(assignment_with_constraints_sat_py3)

View File

@@ -0,0 +1 @@
RUN: $(balance_group_sat_py3)

View File

@@ -0,0 +1 @@
RUN: $(bus_driver_scheduling_sat_py3) --params=max_time_in_seconds:40

View File

@@ -0,0 +1 @@
RUN: $(car_sequencing_optimization_sat_py3)

View File

@@ -0,0 +1 @@
RUN: $(chemical_balance_sat_py3)

View File

@@ -0,0 +1 @@
RUN: $(clustering_sat_py3)

View File

@@ -0,0 +1 @@
RUN: $(cover_rectangle_sat_py3)

View File

@@ -0,0 +1 @@
RUN: $(cryptarithm_sat_py3)

View File

@@ -1,753 +0,0 @@
# This Python file uses the following encoding: utf-8
# Copyright 2015 Tin Arm Engineering AB
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Capacitated Vehicle Routing Problem with Time Windows (and optional orders).
This is a sample using the routing library python wrapper to solve a
CVRPTW problem.
A description of the problem can be found here:
http://en.wikipedia.org/wiki/Vehicle_routing_problem.
The variant which is tackled by this model includes a capacity dimension,
time windows and optional orders, with a penalty cost if orders are not
performed.
To help explore the problem, two classes are provided Customers() and
Vehicles(): used to randomly locate orders and depots, and to randomly
generate demands, time-window constraints and vehicles.
Distances are computed using the Great Circle distances. Distances are in km
and times in seconds.
A function for the displaying of the vehicle plan
display_vehicle_output
The optimization engine uses local search to improve solutions, first
solutions being generated using a cheapest addition heuristic.
Numpy and Matplotlib are required for the problem creation and display.
"""
import os
import numpy as np
from matplotlib import pyplot as plt
from collections import namedtuple
from ortools.constraint_solver import pywrapcp
from ortools.constraint_solver import routing_enums_pb2
from datetime import datetime, timedelta
class Customers():
"""
A class that generates and holds customers information.
Randomly normally distribute a number of customers and locations within
a region described by a rectangle. Generate a random demand for each
customer. Generate a random time window for each customer.
May either be initiated with the extents, as a dictionary describing
two corners of a rectangle in latitude and longitude OR as a center
point (lat, lon), and box_size in km. The default arguments are for a
10 x 10 km square centered in Sheffield).
Args: extents (Optional[Dict]): A dictionary describing a rectangle in
latitude and longitude with the keys 'llcrnrlat', 'llcrnrlon' &
'urcrnrlat' & 'urcrnrlat' center (Optional(Tuple): A tuple of
(latitude, longitude) describing the centre of the rectangle. box_size
(Optional float: The length in km of the box's sides. num_stops (int):
The number of customers, including the depots that are placed normally
distributed in the rectangle. min_demand (int): Lower limit on the
randomly generated demand at each customer. max_demand (int): Upper
limit on the randomly generated demand at each customer.
min_tw: shortest random time window for a customer, in hours.
max_tw: longest random time window for a customer, in hours.
Examples: To place 100 customers randomly within 100 km x 100 km
rectangle, centered in the default location, with a random demand of
between 5 and 10 units: >>> customers = Customers(num_stops=100,
box_size=100, ... min_demand=5, max_demand=10)
alternatively, to place 75 customers in the same area with default
arguments for demand: >>> extents = {'urcrnrlon': 0.03403, 'llcrnrlon':
-2.98325, ... 'urcrnrlat': 54.28127, 'llcrnrlat': 52.48150} >>>
customers = Customers(num_stops=75, extents=extents)
"""
def __init__(self,
extents=None,
center=(53.381393, -1.474611),
box_size=10,
num_stops=100,
min_demand=0,
max_demand=25,
min_tw=1,
max_tw=5):
self.number = num_stops #: The number of customers and depots
#: Location, a named tuple for locations.
Location = namedtuple('Location', ['lat', 'lon'])
if extents is not None:
self.extents = extents #: The lower left and upper right points
#: Location[lat,lon]: the centre point of the area.
self.center = Location(
extents['urcrnrlat'] - 0.5 *
(extents['urcrnrlat'] - extents['llcrnrlat']),
extents['urcrnrlon'] - 0.5 *
(extents['urcrnrlon'] - extents['llcrnrlon']))
else:
#: Location[lat,lon]: the centre point of the area.
(clat, clon) = self.center = Location(center[0], center[1])
rad_earth = 6367 # km
circ_earth = np.pi * rad_earth
#: The lower left and upper right points
self.extents = {
'llcrnrlon': (clon - 180 * box_size /
(circ_earth * np.cos(np.deg2rad(clat)))),
'llcrnrlat':
clat - 180 * box_size / circ_earth,
'urcrnrlon': (clon + 180 * box_size /
(circ_earth * np.cos(np.deg2rad(clat)))),
'urcrnrlat':
clat + 180 * box_size / circ_earth
}
# The 'name' of the stop, indexed from 0 to num_stops-1
stops = np.array(range(0, num_stops))
# normaly distributed random distribution of stops within the box
stdv = 6 # the number of standard deviations 99.9% will be within +-3
lats = (self.extents['llcrnrlat'] + np.random.randn(num_stops) *
(self.extents['urcrnrlat'] - self.extents['llcrnrlat']) / stdv)
lons = (self.extents['llcrnrlon'] + np.random.randn(num_stops) *
(self.extents['urcrnrlon'] - self.extents['llcrnrlon']) / stdv)
# uniformly distributed integer demands.
demands = np.random.randint(min_demand, max_demand, num_stops)
self.time_horizon = 24 * 60**2 # A 24 hour period.
# The customers demand min_tw to max_tw hour time window for each
# delivery
time_windows = np.random.randint(min_tw * 3600, max_tw * 3600,
num_stops)
# The last time a delivery window can start
latest_time = self.time_horizon - time_windows
start_times = [None for o in time_windows]
stop_times = [None for o in time_windows]
# Make random timedeltas, nominally from the start of the day.
for idx in range(self.number):
stime = int(np.random.randint(0, latest_time[idx]))
start_times[idx] = timedelta(seconds=stime)
stop_times[idx] = (
start_times[idx] + timedelta(seconds=int(time_windows[idx])))
# A named tuple for the customer
Customer = namedtuple(
'Customer',
[
'index', # the index of the stop
'demand', # the demand for the stop
'lat', # the latitude of the stop
'lon', # the longitude of the stop
'tw_open', # timedelta window open
'tw_close'
]) # timedelta window cls
self.customers = [
Customer(idx, dem, lat, lon, tw_open, tw_close)
for idx, dem, lat, lon, tw_open, tw_close in zip(
stops, demands, lats, lons, start_times, stop_times)
]
# The number of seconds needed to 'unload' 1 unit of goods.
self.service_time_per_dem = 300 # seconds
def set_manager(self, manager):
self.manager = manager
def central_start_node(self, invert=False):
"""
Return a random starting node, with probability weighted by distance
from the centre of the extents, so that a central starting node is
likely.
Args: invert (Optional bool): When True, a peripheral starting node is
most likely.
Returns:
int: a node index.
Examples:
>>> customers.central_start_node(invert=True)
42
"""
num_nodes = len(self.customers)
dist = np.empty((num_nodes, 1))
for idx_to in range(num_nodes):
dist[idx_to] = self._haversine(self.center.lon, self.center.lat,
self.customers[idx_to].lon,
self.customers[idx_to].lat)
furthest = np.max(dist)
if invert:
prob = dist * 1.0 / sum(dist)
else:
prob = (furthest - dist * 1.0) / sum(furthest - dist)
indexes = np.array([range(num_nodes)])
start_node = np.random.choice(
indexes.flatten(), size=1, replace=True, p=prob.flatten())
return start_node[0]
def make_distance_mat(self, method='haversine'):
"""
Return a distance matrix and make it a member of Customer, using the
method given in the call. Currently only Haversine (GC distance) is
implemented, but Manhattan, or using a maps API could be added here.
Raises an AssertionError for all other methods.
Args: method (Optional[str]): method of distance calculation to use. The
Haversine formula is the only method implemented.
Returns:
Numpy array of node to node distances.
Examples:
>>> dist_mat = customers.make_distance_mat(method='haversine')
>>> dist_mat = customers.make_distance_mat(method='manhattan')
AssertionError
"""
self.distmat = np.zeros((self.number, self.number))
methods = {'haversine': self._haversine}
assert (method in methods)
for frm_idx in range(self.number):
for to_idx in range(self.number):
if frm_idx != to_idx:
frm_c = self.customers[frm_idx]
to_c = self.customers[to_idx]
self.distmat[frm_idx, to_idx] = self._haversine(
frm_c.lon, frm_c.lat, to_c.lon, to_c.lat)
return (self.distmat)
def _haversine(self, lon1, lat1, lon2, lat2):
"""
Calculate the great circle distance between two points
on the earth specified in decimal degrees of latitude and longitude.
https://en.wikipedia.org/wiki/Haversine_formula
Args:
lon1: longitude of pt 1,
lat1: latitude of pt 1,
lon2: longitude of pt 2,
lat2: latitude of pt 2
Returns:
the distace in km between pt1 and pt2
"""
# convert decimal degrees to radians
lon1, lat1, lon2, lat2 = map(np.radians, [lon1, lat1, lon2, lat2])
# haversine formula
dlon = lon2 - lon1
dlat = lat2 - lat1
a = (np.sin(dlat / 2)**2 +
np.cos(lat1) * np.cos(lat2) * np.sin(dlon / 2)**2)
c = 2 * np.arcsin(np.sqrt(a))
# 6367 km is the radius of the Earth
km = 6367 * c
return km
def get_total_demand(self):
"""
Return the total demand of all customers.
"""
return (sum([c.demand for c in self.customers]))
def return_dist_callback(self, **kwargs):
"""
Return a callback function for the distance matrix.
Args: **kwargs: Arbitrary keyword arguments passed on to
make_distance_mat()
Returns:
function: dist_return(a,b) A function that takes the 'from' node
index and the 'to' node index and returns the distance in km.
"""
self.make_distance_mat(**kwargs)
def dist_return(from_index, to_index):
# Convert from routing variable Index to distance matrix NodeIndex.
from_node = self.manager.IndexToNode(from_index)
to_node = self.manager.IndexToNode(to_index)
return (self.distmat[from_node][to_node])
return dist_return
def return_dem_callback(self):
"""
Return a callback function that gives the demands.
Returns:
function: dem_return(a) A function that takes the 'from' node
index and returns the distance in km.
"""
def dem_return(from_index):
# Convert from routing variable Index to distance matrix NodeIndex.
from_node = self.manager.IndexToNode(from_index)
return (self.customers[from_node].demand)
return dem_return
def zero_depot_demands(self, depot):
"""
Zero out the demands and time windows of depot. The Depots do not have
demands or time windows so this function clears them.
Args: depot (int): index of the stop to modify into a depot.
Examples: >>> customers.zero_depot_demands(5) >>>
customers.customers[5].demand == 0 True
"""
start_depot = self.customers[depot]
self.customers[depot] = start_depot._replace(
demand=0, tw_open=None, tw_close=None)
def make_service_time_call_callback(self):
"""
Return a callback function that provides the time spent servicing the
customer. Here is it proportional to the demand given by
self.service_time_per_dem, default 300 seconds per unit demand.
Returns:
function [dem_return(a, b)]: A function that takes the from/a node
index and the to/b node index and returns the service time at a
"""
def service_time_return(a, b):
return (self.customers[a].demand * self.service_time_per_dem)
return service_time_return
def make_transit_time_callback(self, speed_kmph=10):
"""
Creates a callback function for transit time. Assuming an average
speed of speed_kmph
Args:
speed_kmph: the average speed in km/h
Returns:
function [transit_time_return(a, b)]: A function that takes the
from/a node index and the to/b node index and returns the
transit time from a to b.
"""
def transit_time_return(a, b):
return (self.distmat[a][b] / (speed_kmph * 1.0 / 60**2))
return transit_time_return
class Vehicles():
"""
A Class to create and hold vehicle information.
The Vehicles in a CVRPTW problem service the customers and belong to a
depot. The class Vehicles creates a list of named tuples describing the
Vehicles. The main characteristics are the vehicle capacity, fixed cost,
and cost per km. The fixed cost of using a certain type of vehicles can be
higher or lower than others. If a vehicle is used, i.e. this vehicle serves
at least one node, then this cost is added to the objective function.
Note:
If numpy arrays are given for capacity and cost, then they must be of
the same length, and the number of vehicles are inferred from them.
If scalars are given, the fleet is homogeneous, and the number of
vehicles is determined by number.
Args: capacity (scalar or numpy array): The integer capacity of demand
units. cost (scalar or numpy array): The fixed cost of the vehicle. number
(Optional [int]): The number of vehicles in a homogeneous fleet.
"""
def __init__(self, capacity=100, cost=100, number=None):
Vehicle = namedtuple('Vehicle', ['index', 'capacity', 'cost'])
if number is None:
self.number = np.size(capacity)
else:
self.number = number
idxs = np.array(range(0, self.number))
if np.isscalar(capacity):
capacities = capacity * np.ones_like(idxs)
elif np.size(capacity) != self.number:
print('capacity is neither scalar, nor the same size as num!')
else:
capacities = capacity
if np.isscalar(cost):
costs = cost * np.ones_like(idxs)
elif np.size(cost) != self.number:
print(np.size(cost))
print('cost is neither scalar, nor the same size as num!')
else:
costs = cost
self.vehicles = [
Vehicle(idx, capacity, cost)
for idx, capacity, cost in zip(idxs, capacities, costs)
]
def get_total_capacity(self):
return (sum([c.capacity for c in self.vehicles]))
def return_starting_callback(self, customers, sameStartFinish=False):
# create a different starting and finishing depot for each vehicle
self.starts = [
int(customers.central_start_node()) for o in range(self.number)
]
if sameStartFinish:
self.ends = self.starts
else:
self.ends = [
int(customers.central_start_node(invert=True))
for o in range(self.number)
]
# the depots will not have demands, so zero them.
for depot in self.starts:
customers.zero_depot_demands(depot)
for depot in self.ends:
customers.zero_depot_demands(depot)
def start_return(v):
return (self.starts[v])
return start_return
def discrete_cmap(N, base_cmap=None):
"""
Create an N-bin discrete colormap from the specified input map
"""
# Note that if base_cmap is a string or None, you can simply do
# return plt.cm.get_cmap(base_cmap, N)
# The following works for string, None, or a colormap instance:
base = plt.cm.get_cmap(base_cmap)
color_list = base(np.linspace(0, 1, N))
cmap_name = base.name + str(N)
return base.from_list(cmap_name, color_list, N)
def vehicle_output_string(manager, routing, plan):
"""
Return a string displaying the output of the routing instance and
assignment (plan).
Args: routing (ortools.constraint_solver.pywrapcp.RoutingModel): routing.
plan (ortools.constraint_solver.pywrapcp.Assignment): the assignment.
Returns:
(string) plan_output: describing each vehicle's plan.
(List) dropped: list of dropped orders.
"""
dropped = []
for order in range(routing.Size()):
if (plan.Value(routing.NextVar(order)) == order):
dropped.append(str(order))
capacity_dimension = routing.GetDimensionOrDie('Capacity')
time_dimension = routing.GetDimensionOrDie('Time')
plan_output = ''
for route_number in range(routing.vehicles()):
order = routing.Start(route_number)
plan_output += 'Route {0}:'.format(route_number)
if routing.IsEnd(plan.Value(routing.NextVar(order))):
plan_output += ' Empty \n'
else:
while True:
load_var = capacity_dimension.CumulVar(order)
time_var = time_dimension.CumulVar(order)
node = manager.IndexToNode(order)
plan_output += \
' {node} Load({load}) Time({tmin}, {tmax}) -> '.format(
node=node,
load=plan.Value(load_var),
tmin=str(timedelta(seconds=plan.Min(time_var))),
tmax=str(timedelta(seconds=plan.Max(time_var))))
if routing.IsEnd(order):
plan_output += ' EndRoute {0}. \n'.format(route_number)
break
order = plan.Value(routing.NextVar(order))
plan_output += '\n'
return (plan_output, dropped)
def build_vehicle_route(manager, routing, plan, customers, veh_number):
"""
Build a route for a vehicle by starting at the strat node and
continuing to the end node.
Args: routing (ortools.constraint_solver.pywrapcp.RoutingModel): routing.
plan (ortools.constraint_solver.pywrapcp.Assignment): the assignment.
customers (Customers): the customers instance. veh_number (int): index of
the vehicle
Returns:
(List) route: indexes of the customers for vehicle veh_number
"""
veh_used = routing.IsVehicleUsed(plan, veh_number)
print('Vehicle {0} is used {1}'.format(veh_number, veh_used))
if veh_used:
route = []
node = routing.Start(veh_number) # Get the starting node index
route.append(customers.customers[manager.IndexToNode(node)])
while not routing.IsEnd(node):
route.append(customers.customers[manager.IndexToNode(node)])
node = plan.Value(routing.NextVar(node))
route.append(customers.customers[manager.IndexToNode(node)])
return route
else:
return None
def plot_vehicle_routes(veh_route, ax1, customers, vehicles):
"""
Plot the vehicle routes on matplotlib axis ax1.
Args: veh_route (dict): a dictionary of routes keyed by vehicle idx. ax1
(matplotlib.axes._subplots.AxesSubplot): Matplotlib axes customers
(Customers): the customers instance. vehicles (Vehicles): the vehicles
instance.
"""
veh_used = [v for v in veh_route if veh_route[v] is not None]
cmap = discrete_cmap(vehicles.number + 2, 'nipy_spectral')
for veh_number in veh_used:
lats, lons = zip(*[(c.lat, c.lon) for c in veh_route[veh_number]])
lats = np.array(lats)
lons = np.array(lons)
s_dep = customers.customers[vehicles.starts[veh_number]]
s_fin = customers.customers[vehicles.ends[veh_number]]
ax1.annotate(
'v({veh}) S @ {node}'.format(
veh=veh_number, node=vehicles.starts[veh_number]),
xy=(s_dep.lon, s_dep.lat),
xytext=(10, 10),
xycoords='data',
textcoords='offset points',
arrowprops=dict(
arrowstyle='->',
connectionstyle='angle3,angleA=90,angleB=0',
shrinkA=0.05),
)
ax1.annotate(
'v({veh}) F @ {node}'.format(
veh=veh_number, node=vehicles.ends[veh_number]),
xy=(s_fin.lon, s_fin.lat),
xytext=(10, -20),
xycoords='data',
textcoords='offset points',
arrowprops=dict(
arrowstyle='->',
connectionstyle='angle3,angleA=-90,angleB=0',
shrinkA=0.05),
)
ax1.plot(lons, lats, 'o', mfc=cmap(veh_number + 1))
ax1.quiver(
lons[:-1],
lats[:-1],
lons[1:] - lons[:-1],
lats[1:] - lats[:-1],
scale_units='xy',
angles='xy',
scale=1,
color=cmap(veh_number + 1))
def main():
# Create a set of customer, (and depot) stops.
customers = Customers(
num_stops=50,
min_demand=1,
max_demand=15,
box_size=40,
min_tw=3,
max_tw=6)
# Create a list of inhomgenious vehicle capacities as integer units.
capacity = [50, 75, 100, 125, 150, 175, 200, 250]
# Create a list of inhomogeneous fixed vehicle costs.
cost = [int(100 + 2 * np.sqrt(c)) for c in capacity]
# Create a set of vehicles, the number set by the length of capacity.
vehicles = Vehicles(capacity=capacity, cost=cost)
# check to see that the problem is feasible, if we don't have enough
# vehicles to cover the demand, there is no point in going further.
assert (customers.get_total_demand() < vehicles.get_total_capacity())
# Set the starting nodes, and create a callback fn for the starting node.
start_fn = vehicles.return_starting_callback(
customers, sameStartFinish=False)
# Create the routing index manager.
manager = pywrapcp.RoutingIndexManager(
customers.number, # int number
vehicles.number, # int number
vehicles.starts, # List of int start depot
vehicles.ends) # List of int end depot
customers.set_manager(manager)
# Set model parameters
model_parameters = pywrapcp.DefaultRoutingModelParameters()
# The solver parameters can be accessed from the model parameters. For example :
# model_parameters.solver_parameters.CopyFrom(
# pywrapcp.Solver.DefaultSolverParameters())
# model_parameters.solver_parameters.trace_propagation = True
# Make the routing model instance.
routing = pywrapcp.RoutingModel(manager, model_parameters)
parameters = pywrapcp.DefaultRoutingSearchParameters()
# Setting first solution heuristic (cheapest addition).
parameters.first_solution_strategy = (
routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC)
# Routing: forbids use of TSPOpt neighborhood, (this is the default behaviour)
parameters.local_search_operators.use_tsp_opt = pywrapcp.BOOL_FALSE
# Disabling Large Neighborhood Search, (this is the default behaviour)
parameters.local_search_operators.use_path_lns = pywrapcp.BOOL_FALSE
parameters.local_search_operators.use_inactive_lns = pywrapcp.BOOL_FALSE
parameters.time_limit.seconds = 10
parameters.use_full_propagation = True
#parameters.log_search = True
# Create callback fns for distances, demands, service and transit-times.
dist_fn = customers.return_dist_callback()
dist_fn_index = routing.RegisterTransitCallback(dist_fn)
dem_fn = customers.return_dem_callback()
dem_fn_index = routing.RegisterUnaryTransitCallback(dem_fn)
# Create and register a transit callback.
serv_time_fn = customers.make_service_time_call_callback()
transit_time_fn = customers.make_transit_time_callback()
def tot_time_fn(from_index, to_index):
"""
The time function we want is both transit time and service time.
"""
# Convert from routing variable Index to distance matrix NodeIndex.
from_node = manager.IndexToNode(from_index)
to_node = manager.IndexToNode(to_index)
return serv_time_fn(from_node, to_node) + transit_time_fn(from_node, to_node)
tot_time_fn_index = routing.RegisterTransitCallback(tot_time_fn)
# Set the cost function (distance callback) for each arc, homogeneous for
# all vehicles.
routing.SetArcCostEvaluatorOfAllVehicles(dist_fn_index)
# Set vehicle costs for each vehicle, not homogeneous.
for veh in vehicles.vehicles:
routing.SetFixedCostOfVehicle(veh.cost, int(veh.index))
# Add a dimension for vehicle capacities
null_capacity_slack = 0
routing.AddDimensionWithVehicleCapacity(
dem_fn_index, # demand callback
null_capacity_slack,
capacity, # capacity array
True,
'Capacity')
# Add a dimension for time and a limit on the total time_horizon
routing.AddDimension(
tot_time_fn_index, # total time function callback
customers.time_horizon,
customers.time_horizon,
True,
'Time')
time_dimension = routing.GetDimensionOrDie('Time')
for cust in customers.customers:
if cust.tw_open is not None:
time_dimension.CumulVar(manager.NodeToIndex(cust.index)).SetRange(
cust.tw_open.seconds, cust.tw_close.seconds)
"""
To allow the dropping of orders, we add disjunctions to all the customer
nodes. Each disjunction is a list of 1 index, which allows that customer to
be active or not, with a penalty if not. The penalty should be larger
than the cost of servicing that customer, or it will always be dropped!
"""
# To add disjunctions just to the customers, make a list of non-depots.
non_depot = set(range(customers.number))
non_depot.difference_update(vehicles.starts)
non_depot.difference_update(vehicles.ends)
penalty = 400000 # The cost for dropping a node from the plan.
nodes = [routing.AddDisjunction([manager.NodeToIndex(c)], penalty) for c in non_depot]
# This is how you would implement partial routes if you already knew part
# of a feasible solution for example:
# partial = np.random.choice(list(non_depot), size=(4,5), replace=False)
# routing.CloseModel()
# partial_list = [partial[0,:].tolist(),
# partial[1,:].tolist(),
# partial[2,:].tolist(),
# partial[3,:].tolist(),
# [],[],[],[]]
# print(routing.ApplyLocksToAllVehicles(partial_list, False))
# Solve the problem !
assignment = routing.SolveWithParameters(parameters)
# The rest is all optional for saving, printing or plotting the solution.
if assignment:
## save the assignment, (Google Protobuf format)
#save_file_base = os.path.realpath(__file__).split('.')[0]
#if routing.WriteAssignment(save_file_base + '_assignment.ass'):
# print('succesfully wrote assignment to file ' + save_file_base +
# '_assignment.ass')
print('The Objective Value is {0}'.format(assignment.ObjectiveValue()))
plan_output, dropped = vehicle_output_string(manager, routing, assignment)
print(plan_output)
print('dropped nodes: ' + ', '.join(dropped))
# you could print debug information like this:
# print(routing.DebugOutputAssignment(assignment, 'Capacity'))
vehicle_routes = {}
for veh in range(vehicles.number):
vehicle_routes[veh] = build_vehicle_route(manager, routing, assignment,
customers, veh)
# Plotting of the routes in matplotlib.
fig = plt.figure()
ax = fig.add_subplot(111)
# Plot all the nodes as black dots.
clon, clat = zip(*[(c.lon, c.lat) for c in customers.customers])
ax.plot(clon, clat, 'k.')
# plot the routes as arrows
plot_vehicle_routes(vehicle_routes, ax, customers, vehicles)
plt.show()
else:
print('No assignment')
if __name__ == '__main__':
main()

View File

@@ -0,0 +1 @@
RUN: $(flexible_job_shop_sat_py3)

View File

@@ -24,9 +24,8 @@ The objective is to minimize the max end time of all jobs.
"""
from absl import app
from ortools.sat.colab import visualization
from ortools.sat.python import cp_model
from ortools.sat.colab import visualization
def main(_) -> None:

View File

@@ -0,0 +1 @@
RUN: $(gate_scheduling_sat_py3)

View File

@@ -0,0 +1 @@
RUN: $(golomb8_py3)

View File

@@ -0,0 +1 @@
RUN: $(golomb_sat_py3)

View File

@@ -0,0 +1 @@
RUN: $(hidato_sat_py3)

View File

@@ -0,0 +1,297 @@
#!/usr/bin/env python3
# Copyright 2010-2025 Google LLC
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Horse Jumping Show.
A major three-day horse jumping competition is scheduled next winter in Geneva.
The show features riders and horses from all over the world, competing in
several different competitions throughout the show. Six months before the show,
riders submit the entries (i.e., rider name, horse, competition) to the
organizers. Riders can submit multiple entries, for example, to compete in the
same competition with multiple horses, or to compete in several competitions.
There are additional space limitations. For example, the venue has 100 stalls,
4 arenas (where competitions can be scheduled), and 6 paddocks (where riders
warm up before their turn). It is also ideal that paddocks are not overloaded by
riders from multiple competitions.
The organizer's goal is find a schedule in which competitions don't overlap, and
the times at which they happen are scattered throughout the day (and hopefully
not that early in the morning). The starting times of the competitions should be
at the hour or 30 minutes past the hour (e.g. 9:30, 10:00, 10:30, etc.).
Competitions can only be scheduled while there is daylight, except for
competitions scheduled in the Main Stage arena, which is covered and has proper
lighting. Also, beginner competitions (1.10m or less) are scheduled on the first
day, and advanced competitions (1.50m or more) are scheduled on the last day.
The information for next winter's show is as follows:
Available stalls: 100
Number of riders: 100
Number of horses: 130
Number of requested Entries: 200
Number of competitions: 15
Venue:
- Main Stage arena: Covered (9AM-11PM)
- Highlands arena: Daylight Only (9AM-5PM)
- Sawdust arena: Daylight Only (9AM-5PM)
- Paddock1 has capacity for 10 riders and serves Main Stage
- Paddock2 has capacity for 6 riders and serves Main Stage
- Paddock3 has capacity for 8 riders and serves Main Stage, Highlands
- Paddock4 has capacity for 8 riders and serves Highlands, Sawdust
- Paddock5 has capacity for 9 riders and serves Sawdust
- Paddock6 has capacity for 7 riders and serves Sawdust
competitions:
- C_5_1.10m_Year_Olds 1.10m - 60 minutes
- C_6_1.25m_Year_Olds 1.25m - 90 minutes
- C_7_1.35m_Year_Olds 1.35m - 120 minutes
- C_0.8m_Jumpers 0.80m - 240 minutes
- C_1.0m_Jumpers 1.00m - 180 minutes
- C_1.10m_Jumpers 1.10m - 180 minutes
- C_1.20m_Jumpers 1.20m - 120 minutes
- C_1.30m_Jumpers 1.30m - 120 minutes
- C_1.40m_Jumpers 1.40m - 120 minutes
- C_1.20m_Derby 1.20m - 180 minutes
- C_1.35m_Derby 1.35m - 180 minutes
- C_1.45m_Derby 1.45m - 180 minutes
- C_1.40m_Open 1.40m - 120 minutes
- C_1.50m_Open 1.50m - 180 minutes
- C_1.60m_Grand_Prix 1.60m - 240 minutes
"""
import dataclasses
from absl import app
import numpy as np
from ortools.sat.python import cp_model
@dataclasses.dataclass(frozen=True)
class Arena:
"""Data for an arena."""
id: str
hours: str
@dataclasses.dataclass(frozen=True)
class Competition:
"""Data for a competition."""
id: str
height: float
duration: int
@dataclasses.dataclass(frozen=True)
class HorseJumpingShowData:
"""Horse Jumping Show Data."""
num_days: int
competitions: list[Competition]
arenas: list[Arena]
@dataclasses.dataclass(frozen=True)
class ScheduledCompetition:
"""Horse Jumping Show Schedule."""
completion: str
day: int
arena: str
start_time: str
end_time: str
def generate_horse_jumping_show_data() -> HorseJumpingShowData:
"""Generates the horse jumping show data."""
arenas = [
Arena(id="Main Stage", hours="9AM-9PM"),
Arena(id="Highlands", hours="9AM-5PM"),
Arena(id="Sawdust", hours="9AM-5PM"),
]
competitions = [
Competition(id="C_5_1.10m_Year_Olds", height=1.1, duration=60),
Competition(id="C_6_1.25m_Year_Olds", height=1.25, duration=90),
Competition(id="C_7_1.35m_Year_Olds", height=1.35, duration=120),
Competition(id="C_0.8m_Jumpers", height=0.8, duration=240),
Competition(id="C_1.0m_Jumpers", height=1.0, duration=180),
Competition(id="C_1.10m_Jumpers", height=1.10, duration=180),
Competition(id="C_1.20m_Jumpers", height=1.20, duration=120),
Competition(id="C_1.30m_Jumpers", height=1.30, duration=120),
Competition(id="C_1.40m_Jumpers", height=1.40, duration=120),
Competition(id="C_1.20m_Derby", height=1.20, duration=180),
Competition(id="C_1.35m_Derby", height=1.35, duration=180),
Competition(id="C_1.45m_Derby", height=1.45, duration=180),
Competition(id="C_1.40m_Open", height=1.40, duration=120),
Competition(id="C_1.50m_Open", height=1.50, duration=180),
Competition(id="C_1.60m_Grand_Prix", height=1.60, duration=240),
]
return HorseJumpingShowData(num_days=3, competitions=competitions, arenas=arenas)
def solve() -> list[ScheduledCompetition]:
"""Solves the horse jumping show problem."""
data = generate_horse_jumping_show_data()
num_days = data.num_days
competitions = data.competitions
arenas = data.arenas
day_index = list(range(num_days))
# Time parser.
def parse_time(t_str):
hour = int(t_str[:-2])
if "PM" in t_str and hour != 12:
hour += 12
if "AM" in t_str and hour == 12:
hour = 0
return hour * 60
# Schedule time intervals for each arena.
schedule_interval_by_arena = {}
for arena in arenas:
start_h_str, end_h_str = arena.hours.split("-")
start_time = parse_time(start_h_str)
end_time = parse_time(end_h_str)
schedule_interval_by_arena[arena.id] = (start_time, end_time)
# Map time to 30-minute intervals and back.
time_slot_size = 30
def time_to_slot(time_in_minutes: int):
return time_in_minutes // time_slot_size
def slot_to_time(slot_index: int):
return slot_index * time_slot_size
# --- Model Creation ---
model = cp_model.CpModel()
# --- Variables ---
# Competition scheduling variables per arena and day.
competition_assignments = np.empty(
(len(competitions), len(arenas), num_days), dtype=object
)
for c, comp in enumerate(competitions):
for a, arena in enumerate(arenas):
for d in day_index:
competition_assignments[c, a, d] = model.new_bool_var(
f"competition_scheduled_{comp.id}_{arena.id}_{d}"
)
# Time intervals and start times for each competition. We model time steps
# 0,1,2,... to represent the start times in 30 minutes intervals, as opposed
# to represent the start times in minutes.
competition_start_times = np.empty(
(len(competitions), len(arenas), num_days), dtype=object
)
competition_intervals = np.empty(
(len(competitions), len(arenas), num_days), dtype=object
)
for c, comp in enumerate(competitions):
for a, arena in enumerate(arenas):
earliest_start_time, latest_end_time = schedule_interval_by_arena[arena.id]
latest_start_time = latest_end_time - comp.duration
for d in day_index:
competition_start_times[c, a, d] = model.new_int_var(
time_to_slot(earliest_start_time),
time_to_slot(latest_start_time),
f"start_time_{comp.id}_{arena.id}_{d}",
)
competition_intervals[c, a, d] = (
model.new_optional_fixed_size_interval_var(
competition_start_times[c, a, d],
time_to_slot(comp.duration),
competition_assignments[c, a, d],
f"task_{comp.id}_{arena.id}_{d}",
)
)
# --- Constraints ---
# Every competition must be scheduled, enforcing that beginner competitions
# are on day 1, and advanced competitions are on day 3.
for c, comp in enumerate(competitions):
model.add(np.sum(competition_assignments[c, :, :]) == 1)
# Beginner competitions are on the first day.
if comp.height <= 1.10:
beginners_day = 0
model.add(np.sum(competition_assignments[c, :, beginners_day]) == 1)
# Advanced competitions are on the last day.
if comp.height >= 1.50:
advanced_day = num_days - 1
model.add(np.sum(competition_assignments[c, :, advanced_day]) == 1)
# Competitions scheduled on the same arena and on the same day can't overlap.
for a, _ in enumerate(arenas):
for day in range(num_days):
model.add_no_overlap(competition_intervals[:, a, day])
# Start times should be scattered across the day.
for a, _ in enumerate(arenas):
for day in day_index:
model.add_all_different(competition_start_times[:, a, day])
# --- Objective ---
model.maximize(np.sum(competition_start_times))
# --- Solve ---
solver = cp_model.CpSolver()
solver.parameters.max_time_in_seconds = 30.0
solver.parameters.log_search_progress = True
solver.parameters.num_workers = 16
status = solver.solve(model)
# --- Print Solution ---
if status == cp_model.OPTIMAL or status == cp_model.FEASIBLE:
schedule = []
for day in range(num_days):
for c, comp in enumerate(competitions):
for a, arena in enumerate(arenas):
if solver.value(competition_assignments[c, a, day]):
start_time_minutes = slot_to_time(
solver.value(competition_start_times[c, a, day])
)
start_h, start_m = divmod(start_time_minutes, 60)
end_h, end_m = divmod(start_time_minutes + comp.duration, 60)
schedule.append(
ScheduledCompetition(
completion=comp.id,
day=day + 1,
arena=arena.id,
start_time=f"{start_h:02d}:{start_m:02d}",
end_time=f"{end_h:02d}:{end_m:02d}",
)
)
# Sort and print schedule for readability.
schedule.sort(key=lambda x: (x.day, x.start_time))
print("Schedule:")
for item in schedule:
print(
f"Day {item.day}: {item.completion} in {item.arena} from"
f" {item.start_time} to {item.end_time}."
)
return schedule
elif status == cp_model.INFEASIBLE:
print("Problem is infeasible.")
else:
print("No solution found.")
# Return an empty schedule if no solution is found.
return []
def main(_):
solve()
if __name__ == "__main__":
app.run(main)

View File

@@ -0,0 +1,2 @@
RUN: $(horse_jumping_show_py3)
CHECK: "Day 3: C_1.60m_Grand_Prix"

View File

@@ -0,0 +1 @@
RUN: $(integer_programming_py3)

View File

@@ -0,0 +1 @@
RUN: $(jobshop_ft06_distance_sat_py3)

View File

@@ -0,0 +1 @@
RUN: $(jobshop_ft06_sat_py3)

View File

@@ -0,0 +1 @@
RUN: $(jobshop_with_maintenance_sat_py3)

View File

@@ -0,0 +1 @@
RUN: $(knapsack_2d_sat_py3)

View File

@@ -0,0 +1,2 @@
RUN: $(line_balancing_sat_py3) --input=$(salbp_20_1.alb)
CHECK: "objective: 3"

View File

@@ -0,0 +1 @@
RUN: $(linear_assignment_api_py3)

View File

@@ -0,0 +1 @@
RUN: $(linear_programming_py3)

View File

@@ -0,0 +1 @@
RUN: $(magic_sequence_distribute_py3)

View File

@@ -0,0 +1 @@
RUN: $(magic_sequence_distribute_py3) 5

View File

@@ -0,0 +1 @@
RUN: $(maximize_combinations_sat_py3)

View File

@@ -0,0 +1 @@
RUN: $(maze_escape_sat_py3)

View File

@@ -0,0 +1 @@
RUN: $(memory_layout_and_infeasibility_sat_py3)

View File

@@ -0,0 +1 @@
RUN: $(music_playlist_sat_py3)

View File

@@ -0,0 +1 @@
RUN: $(no_wait_baking_scheduling_sat_py3)

View File

@@ -0,0 +1 @@
RUN: $(nqueens_sat_py3)

View File

@@ -0,0 +1 @@
RUN: $(pell_equation_sat_py3)

View File

@@ -0,0 +1 @@
RUN: $(pentominoes_sat_py3)

View File

@@ -0,0 +1 @@
RUN: $(prize_collecting_tsp_sat_py3)

View File

@@ -0,0 +1 @@
RUN: $(prize_collecting_vrp_sat_py3)

View File

@@ -0,0 +1 @@
RUN: $(pyflow_example_py3)

View File

@@ -0,0 +1 @@
RUN: $(qubo_sat_py3)

View File

@@ -0,0 +1,2 @@
RUN: $(rcpsp_sat_py3) --input=$(c1510_1.mm.txt)
CHECK: "objective: 21"

View File

@@ -0,0 +1,2 @@
RUN: $(rcpsp_sat_py3) --input=$(j301_1.sm)
CHECK: "objective: 43"

View File

@@ -0,0 +1,2 @@
RUN: $(rcpsp_sat_py3) --input=$(rip1.sch)
CHECK: "objective: 100"

View File

@@ -0,0 +1 @@
RUN: $(rcpsp_sat_py3) --input=$(testset_mm30_psp3.sch) --params=max_time_in_seconds:8.0

View File

@@ -0,0 +1,2 @@
RUN: $(rcpsp_sat_py3) --input=$(ubo_10_psp2.sch)
CHECK: "objective: 45"

View File

@@ -0,0 +1 @@
RUN: $(shift_scheduling_sat_py3) --params=max_time_in_seconds:10

View File

@@ -0,0 +1 @@
RUN: $(single_machine_scheduling_with_setup_release_due_dates_sat_py3)

View File

@@ -0,0 +1,360 @@
#!/usr/bin/env python3
# Copyright 2010-2025 Google LLC
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Solves the problem of buying physical machines to meet VM demand.
The Spillover problem is defined as follows:
You have M types of physical machines and V types of Virtual Machines (VMs). You
can use a physical machine of type m to get n_mv copies of VM v. Each physical
machine m has a cost of c_m. Each VM has a demand of d_v. VMs are assigned to
physical machines by the following rule. The demand for each VM type arrives
equally spaced out over the interval [0, 1]. For each VM type, there is a
priority order over the physical machine types that you must follow. When a
demand arrives, if there are any machines of the highest priority type
available, you use them first, then you move on to the second priority machine
type, and so on. Each VM type has a list of compatible physical machine types,
and when the list is exhausted, the remaining demand is not met. Your goal is
to pick quantities of the physical machines to buy (minimizing cost) so that at
least some target service level (e.g. 95%) of the total demand of all VM is met.
The number of machines bought of each type and the number of VMs demanded of
each type is large enough that you can solve an approximate problem instead,
where the number of machines purchased and the assignment of machines to VMs is
fractional, if it is helpful to do so.
The problem is not particularly interesting in isolation, it is more interesting
to embed this LP inside a larger optimization problem (e.g. consider a two stage
problem where in stage one, you buy machines, then in stage two, you realize VM
demand).
The continuous approximation of this problem can be solved by LP (see the
MathOpt python examples). Doing this, instead of using MIP, is nontrivial.
Below, we show that continuous relaxation can be approximately solved by CP-SAT
as well, despite not having continuous variables. If you were solving the
problem in isolation, you should just use an LP solver, but if you were to add
side constraints or embed this within a more complex model, using CP-SAT could
be appropriate.
If for each VM type, the physical machines that are most cost effective are the
highest priority, AND the target service level is 100%, then the problem has a
trivial optimal solution:
1. Rank the VMs by lowest cost to meet a unit of demand with the #1 preferred
machine type.
2. For each VM type in the order above, buy machines from #1 preferred machine
type, until either you have met all demand for the VM type.
MOE:begin_strip
This example is motivated by the Cloudy problem, see go/fluid-model.
MOE:end_strip
"""
from collections.abc import Sequence
import dataclasses
import math
import random
from absl import app
from absl import flags
from ortools.sat.python import cp_model
_MACHINE_TYPES = flags.DEFINE_integer(
"machine_types",
100,
"How many types of machines we can fulfill demand with.",
)
_VM_TYPES = flags.DEFINE_integer(
"vm_types", 500, "How many types of VMs we need to supply."
)
_FUNGIBILITY = flags.DEFINE_integer(
"fungibility",
10,
"Each VM type can be satisfied with this many machine types, selected"
" uniformly at random.",
)
_MAX_DEMAND = flags.DEFINE_integer(
"max_demand",
100,
"Demand for each VM type is in [max_demand//2, max_demand], uniformly at"
" random.",
)
_TEST_DATA = flags.DEFINE_bool(
"test_data", False, "Use small test instance instead of random data."
)
_SEED = flags.DEFINE_integer("seed", 13, "RNG seed for instance creation.")
_TIME_STEPS = flags.DEFINE_integer("time_steps", 100, "How much to discretize time.")
@dataclasses.dataclass(frozen=True)
class MachineUse:
machine_type: int
vms_per_machine: int
@dataclasses.dataclass(frozen=True)
class VmDemand:
compatible_machines: tuple[MachineUse, ...]
vm_quantity: int
@dataclasses.dataclass(frozen=True)
class SpilloverProblem:
machine_cost: tuple[float, ...]
machine_limit: tuple[int, ...]
vm_demands: tuple[VmDemand, ...]
service_level: float
time_horizon: int
def _random_spillover_problem(
num_machines: int,
num_vms: int,
fungibility: int,
max_vm_demand: int,
horizon: int,
) -> SpilloverProblem:
"""Generates a random SpilloverProblem."""
machine_costs = tuple(random.random() for _ in range(num_machines))
vm_demands = []
all_machines = list(range(num_machines))
min_vm_demand = max_vm_demand // 2
for _ in range(num_vms):
vm_use = []
for machine in random.sample(all_machines, fungibility):
vm_use.append(
MachineUse(machine_type=machine, vms_per_machine=random.randint(1, 10))
)
vm_demands.append(
VmDemand(
compatible_machines=tuple(vm_use),
vm_quantity=random.randint(min_vm_demand, max_vm_demand),
)
)
machine_need_ub = num_vms * max_vm_demand
machine_limit = (machine_need_ub,) * num_machines
return SpilloverProblem(
machine_cost=machine_costs,
machine_limit=machine_limit,
vm_demands=tuple(vm_demands),
service_level=0.95,
time_horizon=horizon,
)
def _test_problem() -> SpilloverProblem:
"""Creates a small SpilloverProblem with optimal objective of 360."""
# To avoid machine type 2, ensure we buy enough of 1 to not stock out, cost
# 20
vm_a = VmDemand(
vm_quantity=10,
compatible_machines=(
MachineUse(machine_type=1, vms_per_machine=1),
MachineUse(machine_type=2, vms_per_machine=1),
),
)
# machine type 0 is cheaper, but we don't want to stock out of machine type 1,
# so use all machine type 1, cost 40.
vm_b = VmDemand(
vm_quantity=20,
compatible_machines=(
MachineUse(machine_type=1, vms_per_machine=1),
MachineUse(machine_type=0, vms_per_machine=1),
),
)
# Will use 3 copies of machine type 2, cost 300
vm_c = VmDemand(
vm_quantity=30,
compatible_machines=(MachineUse(machine_type=2, vms_per_machine=10),),
)
return SpilloverProblem(
machine_cost=(1.0, 2.0, 100.0),
machine_limit=(60, 60, 60),
vm_demands=(vm_a, vm_b, vm_c),
service_level=1.0,
time_horizon=100,
)
# Indices:
# * i in I, the VM demands
# * j in J, the machines supplied
#
# Data:
# * c_j: cost of a machine of type j
# * l_j: a limit of how many machines of type j you can buy.
# * n_ij: how many VMs of type i you get from a machine of type j
# * d_i: the total demand for VMs of type i
# * service_level: the target fraction of demand that is met.
# * P_i subset J: the compatible machine types for VM demand i.
# * UP_i(j) subset P_i, for j in P_i: for VM demand type i, the machines of
# priority higher than j
# * T: the number of integer time steps.
#
# Note: when d_i/n_ij is not integer, some approximation error is introduced in
# constraint 6 below.
#
# Decision variables:
# * s_j: the supply of machine type j
# * w_j: the time we run out of machine j, or 1 if we never run out
# * v_ij: when we start using supply j to meet demand i, or w_j if we never use
# this machine type for this demand.
# * o_i: the time we start failing to meet vm demand i
# * m_i: the total demand met for vm type i.
#
# Model the problem:
# min sum_{j in J} c_j s_j
# s.t.
# 1: sum_i m_i >= service_level * sum_{i in I} d_i
# 2: T * m_i <= o_i * d_i for all i in I
# 3: v_ij >= w_r for all i in I, j in C_i, r in UP_i(j)
# 4: v_ij <= w_j for all i in I, j in C_i
# 5: o_i = sum_{j in P_i} (w_j - v_ij) for all i in I
# 6: sum_{i in I: j in P_i}ceil(d_i/n_ij)(w_j - v_ij)<=T*s_j for all j in J
# o_i, w_j, v_ij in [0, T]
# 0 <= m_i <= d_i
# 0 <= s_j <= l_j
#
# The constraints say:
# 1. The amount of demand served must be at least 95% of total demand.
# 2. The demand served for VM type i is linear in the time we fail to keep
# serving demand.
# 3. Don't start using machine type j for demand i until all higher priority
# machine types r are used up.
# 4. The time we run out of machine type j must be after we start using it for
# VM demand type i.
# 5. The time we are unable to serve further VM demand i is the sum of the
# time spent serving the demand with each eligible machine type.
# 6. The total use of machine type j to serve demand does not exceed the
# supply. The ceil function above introduces some approximation error when
# d_i/n_ij is not integer.
def _solve_spillover_problem(problem: SpilloverProblem) -> None:
"""Solves the spillover problem and prints the optimal objective."""
model = cp_model.CpModel()
num_machines = len(problem.machine_cost)
num_vms = len(problem.vm_demands)
horizon = problem.time_horizon
s = [
model.new_int_var(lb=0, ub=problem.machine_limit[j], name=f"s_{j}")
for j in range(num_machines)
]
w = [
model.new_int_var(lb=0, ub=horizon, name=f"w_{i}") for i in range(num_machines)
]
o = [model.new_int_var(lb=0, ub=horizon, name=f"o_{j}") for j in range(num_vms)]
m = [
model.new_int_var(lb=0, ub=problem.vm_demands[j].vm_quantity, name=f"m_{j}")
for j in range(num_vms)
]
v = [
{
compat.machine_type: model.new_int_var(
lb=0, ub=horizon, name=f"v_{i}_{compat.machine_type}"
)
for compat in vm_demand.compatible_machines
}
for i, vm_demand in enumerate(problem.vm_demands)
]
obj = 0
for j in range(num_machines):
obj += s[j] * problem.machine_cost[j]
model.minimize(obj)
# Constraint 1: demand served is at least service_level fraction of total.
total_vm_demand = sum(vm_demand.vm_quantity for vm_demand in problem.vm_demands)
model.add(sum(m) >= int(math.ceil(problem.service_level * total_vm_demand)))
# Constraint 2: demand served is linear in time we stop serving.
for i in range(num_vms):
model.add(
problem.time_horizon * m[i] <= o[i] * problem.vm_demands[i].vm_quantity
)
# Constraint 3: use machine type j for demand i after all higher priority
# machine types r are used up.
for i in range(num_vms):
for k, meet_demand in enumerate(problem.vm_demands[i].compatible_machines):
j = meet_demand.machine_type
for l in range(k):
r = problem.vm_demands[i].compatible_machines[l].machine_type
model.add(v[i][j] >= w[r])
# Constraint 4: outage time of machine j is after start time for using j to
# meet VM demand i.
for i in range(num_vms):
for meet_demand in problem.vm_demands[i].compatible_machines:
j = meet_demand.machine_type
model.add(v[i][j] <= w[j])
# Constraint 5: For VM demand i, time service ends is the sum of the time
# spent serving with each eligible machine type.
for i in range(num_vms):
sum_serving = 0
for meet_demand in problem.vm_demands[i].compatible_machines:
j = meet_demand.machine_type
sum_serving += w[j] - v[i][j]
model.add(o[i] == sum_serving)
# Constraint 6: Total use of machine type j is at most the supply.
#
# We build the constraints in bulk because our data is transposed.
total_machine_use = [0 for _ in range(num_machines)]
for i in range(num_vms):
for meet_demand in problem.vm_demands[i].compatible_machines:
j = meet_demand.machine_type
nij = meet_demand.vms_per_machine
vm_quantity = problem.vm_demands[i].vm_quantity
# Want vm_quantity/nij, over estimate with ceil(vm_quantity/nij) to use
# integer coefficients.
rate = (vm_quantity + nij - 1) // nij
total_machine_use[j] += rate * (w[j] - v[i][j])
for j in range(num_machines):
model.add(total_machine_use[j] <= horizon * s[j])
solver = cp_model.CpSolver()
solver.parameters.num_workers = 16
solver.parameters.log_search_progress = True
solver.max_time_in_seconds = 30.0
status = solver.solve(model)
if status != cp_model.OPTIMAL:
raise RuntimeError(f"expected optimal, found: {status}")
print(f"objective: {solver.objective_value}")
def main(argv: Sequence[str]) -> None:
del argv # Unused.
random.seed(_SEED.value)
if _TEST_DATA.value:
problem = _test_problem()
else:
problem = _random_spillover_problem(
_MACHINE_TYPES.value,
_VM_TYPES.value,
_FUNGIBILITY.value,
_MAX_DEMAND.value,
_TIME_STEPS.value,
)
print(problem)
_solve_spillover_problem(problem)
if __name__ == "__main__":
app.run(main)

View File

@@ -0,0 +1,2 @@
RUN: $(spillover_sat) --test_data
CHECK: "objective: 360.0"

View File

@@ -0,0 +1 @@
RUN: $(spread_robots_sat_py3)

View File

@@ -0,0 +1 @@
RUN: $(steel_mill_slab_sat_py3)

View File

@@ -0,0 +1 @@
RUN: $(sudoku_sat_py3)

View File

@@ -0,0 +1 @@
RUN: $(task_allocation_sat_py3)

View File

@@ -0,0 +1 @@
RUN: $(tasks_and_workers_assignment_sat_py3)

View File

@@ -0,0 +1 @@
RUN: $(test_scheduling_sat_py3)

View File

@@ -0,0 +1 @@
RUN: $(tsp_py3) --tsp_use_random_matrix=false

View File

@@ -0,0 +1 @@
RUN: $(tsp_py3)

View File

@@ -0,0 +1 @@
RUN: $(tsp_sat_py3)

View File

@@ -0,0 +1 @@
RUN: $(vendor_scheduling_sat_py3)

View File

@@ -0,0 +1 @@
RUN: $(wedding_optimal_chart_sat_py3)

View File

@@ -0,0 +1 @@
RUN: $(weighted_latency_problem_sat_py3)

View File

@@ -0,0 +1 @@
RUN: $(zebra_sat_py3)