diff --git a/.gitignore b/.gitignore index 2f91a2e2177..fad35df17c8 100644 --- a/.gitignore +++ b/.gitignore @@ -107,8 +107,7 @@ examples/dotnet/obj CMakeCache.txt CMakeFiles DartConfiguration.tcl -*build*/* -build/ +/build*/ # Ignore Bzlmod lock file until it is more stable MODULE.bazel.lock diff --git a/bazel/BUILD.bazel b/bazel/BUILD.bazel index 9c85c963070..f3560a8f7ec 100644 --- a/bazel/BUILD.bazel +++ b/bazel/BUILD.bazel @@ -36,10 +36,3 @@ compile_pip_requirements( ) package(default_visibility = ["//visibility:public"]) - -filegroup( - name = "test_runner_template", - testonly = 1, - srcs = ["test_runner_template.sh"], - visibility = ["//visibility:public"], -) diff --git a/bazel/run_binary_test.bzl b/bazel/run_binary_test.bzl deleted file mode 100644 index 000b4dc7c2e..00000000000 --- a/bazel/run_binary_test.bzl +++ /dev/null @@ -1,106 +0,0 @@ -# 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. - -"""run_binary_test will run a xx_binary as test with the given args.""" - -load("@bazel_skylib//rules:expand_template.bzl", "expand_template") -load("@rules_shell//shell:sh_test.bzl", "sh_test") - -def parse_label(label): - """Parse a label into (package, name). - - Args: - label: string in relative or absolute form. - - Returns: - Pair of strings: package, relative_name - - Raises: - ValueError for malformed label (does not do an exhaustive validation) - """ - if label.startswith("//"): - label = label[2:] # drop the leading // - colon_split = label.split(":") - if len(colon_split) == 1: # no ":" in label - pkg = label - _, _, target = label.rpartition("/") - else: - pkg, target = colon_split # fails if len(colon_split) != 2 - else: - colon_split = label.split(":") - if len(colon_split) == 1: # no ":" in label - pkg, target = native.package_name(), label - else: - pkg2, target = colon_split # fails if len(colon_split) != 2 - pkg = native.package_name() + ("/" + pkg2 if pkg2 else "") - return pkg, target - -def get_check_contains_code(line): - return """ -if ! grep --quiet --fixed-strings "{line}" "${{LOGFILE}}"; then - echo "---------------------------------------------------------------" - cat "${{LOGFILE}}" - echo "---------------------------------------------------------------" - echo "FAILURE: string '{line}' was not found in the output." - echo "---------------------------------------------------------------" - exit 1 -fi -""".format(line = line) - -def run_binary_test( - name, - binary, - template = "//bazel:test_runner_template", - args = [], - data = [], - grep_lines = [], - **kwargs): - """Create a sh_test to run the given binary as test. - - Args: - name: name of the test target. - binary: name of the binary target to run. - template: template file for executing the binary target. - args: args to use to run the binary. - data: data files required by this test. - grep_lines: lines to grep for in the log file. - **kwargs: other attributes that are applicable to tests, size, tags, etc. - - """ - shell_script = name + ".sh" - - # Get the path to the binary we want to run. - binary_pkg, binary_name = parse_label(binary) - binary_path = "/".join([binary_pkg, binary_name]) - - # We would like to include args in the generated shell script, so that "blaze-bin/.../test" can - # be run manually. Unfortunately `expand_template` does not resolve $(location) and other Make - # variables so we only pass them in `sh_test` below. - expand_template( - name = name + "_gensh", - template = template, - out = shell_script, - testonly = 1, - substitutions = { - "{{binary_path}}": binary_path, - "{{post_script}}": "\n".join([get_check_contains_code(line) for line in grep_lines]), - }, - ) - sh_test( - name = name, - testonly = 1, - srcs = [shell_script], - data = data + [binary], - args = args, - **kwargs - ) diff --git a/cmake/cpp.cmake b/cmake/cpp.cmake index 101df2e431f..c588c73536b 100644 --- a/cmake/cpp.cmake +++ b/cmake/cpp.cmake @@ -309,6 +309,126 @@ function(ortools_cxx_library) message(STATUS "Configuring library ${LIBRARY_NAME} ...DONE") endfunction() + +# ortools_cxx_binary() +# CMake function to generate and build C++ library. +# Parameters: +# NAME: CMake target name +# SOURCES: List of source files +# [COMPILE_DEFINITIONS]: List of private compile definitions +# [COMPILE_OPTIONS]: List of private compile options +# [LINK_LIBRARIES]: List of **public** libraries to use when linking +# note: ortools::ortools is always linked to the target +# [LINK_OPTIONS]: List of private link options +# e.g.: +# ortools_cxx_binary( +# NAME +# foo_bar_binary +# SOURCES +# bar_binary.cc +# ${PROJECT_SOURCE_DIR}/ortools/foo/bar_binary.cc +# LINK_LIBRARIES +# GTest::gmock +# GTest::gtest_main +# TESTING +# ) +function(ortools_cxx_binary) + set(options "TESTING") + set(oneValueArgs "NAME") + set(multiValueArgs + "SOURCES;COMPILE_DEFINITIONS;COMPILE_OPTIONS;LINK_LIBRARIES;LINK_OPTIONS") + cmake_parse_arguments(BINARY + "${options}" + "${oneValueArgs}" + "${multiValueArgs}" + ${ARGN} + ) + if(BINARY_TESTING AND NOT BUILD_TESTING) + return() + endif() + + if(NOT BINARY_NAME) + message(FATAL_ERROR "no NAME provided") + endif() + if(NOT BINARY_SOURCES) + message(FATAL_ERROR "no SOURCES provided") + endif() + message(STATUS "Configuring binary ${BINARY_NAME} ...") + + add_executable(${BINARY_NAME} ${BINARY_TYPE} "") + target_sources(${BINARY_NAME} PRIVATE ${BINARY_SOURCES}) + target_include_directories(${BINARY_NAME} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) + target_compile_definitions(${BINARY_NAME} PRIVATE ${BINARY_COMPILE_DEFINITIONS}) + target_compile_features(${BINARY_NAME} PRIVATE cxx_std_17) + target_compile_options(${BINARY_NAME} PRIVATE ${BINARY_COMPILE_OPTIONS}) + target_link_libraries(${BINARY_NAME} PRIVATE ${PROJECT_NAMESPACE}::ortools ${BINARY_LINK_LIBRARIES}) + target_link_options(${BINARY_NAME} PRIVATE ${BINARY_LINK_OPTIONS}) + + include(GNUInstallDirs) + if(APPLE) + set_target_properties(${BINARY_NAME} PROPERTIES + INSTALL_RPATH "@loader_path/../${CMAKE_INSTALL_LIBDIR};@loader_path") + elseif(UNIX) + cmake_path(RELATIVE_PATH CMAKE_INSTALL_FULL_LIBDIR + BASE_DIRECTORY ${CMAKE_INSTALL_FULL_BINDIR} + OUTPUT_VARIABLE libdir_relative_path) + set_target_properties(${BINARY_NAME} PROPERTIES + INSTALL_RPATH "$ORIGIN/${libdir_relative_path}:$ORIGIN") + endif() + add_executable(${PROJECT_NAMESPACE}::${BINARY_NAME} ALIAS ${BINARY_NAME}) + message(STATUS "Configuring binary ${BINARY_NAME} ...DONE") +endfunction() + +find_package(Python3 COMPONENTS Interpreter) + +# ortools_cxx_bintest() +# CMake function to generate and build C++ test. +# Parameters: +# NAME: CMake target name +# SCRIPT: The script to run the test. +# e.g.: +# ortools_cxx_bintest( +# NAME +# foo_bar_bintest +# SCRIPT +# foo_bar.bintest +# ENVIRONMENT +# "BINTEST_foo_bar=$" +# "BINTEST_foo_bar_data=$(CMAKE_CURRENT_SOURCE_DIR)/foo_bar_data.txt" +# ) +function(ortools_cxx_bintest) + set(options "") + set(oneValueArgs "NAME;SCRIPT") + set(multiValueArgs "ENVIRONMENT") + cmake_parse_arguments(BINTEST + "${options}" + "${oneValueArgs}" + "${multiValueArgs}" + ${ARGN} + ) + if(NOT BINTEST_NAME) + message(FATAL_ERROR "no NAME provided") + endif() + if(NOT BINTEST_SCRIPT) + message(FATAL_ERROR "no SCRIPT provided") + endif() + if(NOT Python3_Interpreter_FOUND) + message(WARNING "No python3 interpreter found, the bintest ${BINTEST_NAME} is disable") + return() + endif() + + message(STATUS "Configuring bintest ${BINTEST_NAME} ...") + add_test( + NAME ${BINTEST_NAME} + COMMAND ${Python3_EXECUTABLE} -m tools.testing.bintest_script_launcher ${BINTEST_SCRIPT} + WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} + ) + set_tests_properties(${BINTEST_NAME} PROPERTIES + ENVIRONMENT "${BINTEST_ENVIRONMENT}" + ) + message(STATUS "Configuring bintest ${BINTEST_NAME} ...DONE") +endfunction() + ################## ## PROTO FILE ## ################## diff --git a/cmake/python.cmake b/cmake/python.cmake index 681cf5356f2..edd7e42c5cc 100644 --- a/cmake/python.cmake +++ b/cmake/python.cmake @@ -1020,3 +1020,44 @@ if(NOT EXAMPLE_FILE_NAME) endif() message(STATUS "Configuring example ${EXAMPLE_FILE_NAME} ...DONE") endfunction() + +# add_python_binary() +# CMake function to generate a shell wrapper to execute a Python program. +# Parameters: +# NAME: the target name +# FILE: the Python filename +# e.g.: +# add_python_binary( +# NAME +# foo_bin +# FILE +# ${PROJECT_SOURCE_DIR}/examples/foo/bar.py +# ) +function(add_python_binary) + set(options "") + set(oneValueArgs NAME;FILE) + set(multiValueArgs "") + cmake_parse_arguments(PY_BINARY + "${options}" + "${oneValueArgs}" + "${multiValueArgs}" + ${ARGN} + ) + message(STATUS "Configuring python binary ${PY_BINARY_NAME} ...") + if(NOT PY_BINARY_NAME) + message(FATAL_ERROR "no NAME provided for python binary") + endif() + if(NOT PY_BINARY_FILE) + message(FATAL_ERROR "no FILE provided for python binary") + endif() + file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/${PY_BINARY_NAME}" "#!/usr/bin/env sh\n${VENV_Python3_EXECUTABLE} ${PY_BINARY_FILE} \"$@\"\n") + file(CHMOD "${CMAKE_CURRENT_BINARY_DIR}/${PY_BINARY_NAME}" + FILE_PERMISSIONS + OWNER_READ OWNER_EXECUTE + GROUP_READ GROUP_EXECUTE + WORLD_READ WORLD_EXECUTE + ) + add_executable("${PY_BINARY_NAME}" IMPORTED) + set_target_properties("${PY_BINARY_NAME}" PROPERTIES IMPORTED_LOCATION "${CMAKE_CURRENT_BINARY_DIR}/${PY_BINARY_NAME}") + message(STATUS "Configuring python binary ${PY_BINARY_NAME} ...DONE") +endfunction() diff --git a/examples/cpp/BUILD.bazel b/examples/cpp/BUILD.bazel index c1db00a5787..b0924bdbb78 100644 --- a/examples/cpp/BUILD.bazel +++ b/examples/cpp/BUILD.bazel @@ -13,7 +13,7 @@ load("@rules_cc//cc:cc_binary.bzl", "cc_binary") load("@rules_cc//cc:cc_library.bzl", "cc_library") -load("//bazel:run_binary_test.bzl", "run_binary_test") +load("//tools/testing:bintest.bzl", "bintest") # Description: # C++ examples for operations_research. @@ -44,15 +44,61 @@ cc_binary( ], ) -run_binary_test( +bintest( name = "binpacking_2d_sat_class01_instance2_test", size = "medium", - args = [ - "--input $(rootpath //ortools/packing/testdata:Class_01.2bp)", - "--instance 2", + srcs = [":binpacking_2d_sat_class01_instance2_test.bintest"], + named_data = { + "binpacking_2d_sat": ":binpacking_2d_sat", + "Class_01.2bp": "//ortools/packing/testdata:Class_01.2bp", + }, +) + +cc_library( + name = "cgc", + srcs = ["cgc.cc"], + hdrs = [ + "cgc.h", + "cgc_data.h", ], - binary = ":binpacking_2d_sat", - data = ["//ortools/packing/testdata:Class_01.2bp"], + deps = [ + "//ortools/base", + "//ortools/base:file", + "//ortools/constraint_solver:cp", + "@abseil-cpp//absl/container:btree", + "@abseil-cpp//absl/strings", + "@abseil-cpp//absl/strings:str_format", + "@abseil-cpp//absl/time", + "@abseil-cpp//absl/types:span", + "@re2", + ], +) + +cc_binary( + name = "cgc_main", + srcs = ["cgc_main.cc"], + deps = [ + ":cgc", + "//ortools/base", + "@abseil-cpp//absl/flags:flag", + "@abseil-cpp//absl/strings:str_format", + "@abseil-cpp//absl/time", + ], +) + +bintest( + name = "cgc_test_solution", + size = "small", + srcs = [":cgc_test_solution.bintest"], + named_data = { + "cgc_main": ":cgc_main", + "1.in": "testdata/cgc/1.in", + "2.in": "testdata/cgc/2.in", + "3.in": "testdata/cgc/3.in", + "cgcut1.in": "testdata/cgc/cgcut1.in", + "cgcut2.in": "testdata/cgc/cgcut2.in", + "cgcut3.in": "testdata/cgc/cgcut3.in", + }, ) cc_binary( @@ -67,10 +113,11 @@ cc_binary( ], ) -run_binary_test( +bintest( name = "constraint_programming_cp_test", size = "small", - binary = ":constraint_programming_cp", + srcs = [":constraint_programming_cp_test.bintest"], + named_data = {"constraint_programming_cp": ":constraint_programming_cp"}, ) cc_binary( @@ -91,37 +138,25 @@ cc_binary( ], ) -run_binary_test( +bintest( name = "costas_array_sat_model1_test", size = "medium", - args = [ - "--minsize=6", - "--maxsize=6", - "--model=1", - ], - binary = ":costas_array_sat", + srcs = [":costas_array_sat_model1_test.bintest"], + named_data = {"costas_array_sat": ":costas_array_sat"}, ) -run_binary_test( +bintest( name = "costas_array_sat_model2_test", size = "medium", - args = [ - "--minsize=6", - "--maxsize=6", - "--model=2", - ], - binary = ":costas_array_sat", + srcs = [":costas_array_sat_model2_test.bintest"], + named_data = {"costas_array_sat": ":costas_array_sat"}, ) -run_binary_test( +bintest( name = "costas_array_sat_model3_test", size = "medium", - args = [ - "--minsize=6", - "--maxsize=6", - "--model=3", - ], - binary = ":costas_array_sat", + srcs = [":costas_array_sat_model3_test.bintest"], + named_data = {"costas_array_sat": ":costas_array_sat"}, ) cc_binary( @@ -130,10 +165,11 @@ cc_binary( deps = ["//ortools/sat:cp_model"], ) -run_binary_test( +bintest( name = "cryptarithm_sat_test", size = "small", - binary = ":cryptarithm_sat", + srcs = [":cryptarithm_sat_test.bintest"], + named_data = {"cryptarithm_sat": ":cryptarithm_sat"}, ) cc_binary( @@ -151,11 +187,11 @@ cc_binary( ], ) -run_binary_test( +bintest( name = "dobble_ls_test", size = "medium", - args = ["--time_limit_in_ms=10000"], - binary = ":dobble_ls", + srcs = [":dobble_ls_test.bintest"], + named_data = {"dobble_ls": ":dobble_ls"}, ) cc_binary( @@ -176,11 +212,11 @@ cc_binary( ], ) -run_binary_test( +bintest( name = "golomb_sat_test", size = "medium", - args = ["--size 5"], - binary = ":golomb_sat", + srcs = [":golomb_sat_test.bintest"], + named_data = {"golomb_sat": ":golomb_sat"}, ) cc_binary( @@ -203,15 +239,14 @@ cc_binary( ], ) -run_binary_test( +bintest( name = "knapsack_2d_sat_class01_instance2_test", size = "medium", - args = [ - "--input $(rootpath //ortools/packing/testdata:Class_01.2bp)", - "--instance 2", - ], - binary = ":knapsack_2d_sat", - data = ["//ortools/packing/testdata:Class_01.2bp"], + srcs = [":knapsack_2d_sat_class01_instance2_test.bintest"], + named_data = { + "knapsack_2d_sat": ":knapsack_2d_sat", + "Class_01.2bp": "//ortools/packing/testdata:Class_01.2bp", + }, ) cc_binary( @@ -241,12 +276,14 @@ cc_binary( ], ) -run_binary_test( +bintest( name = "jobshop_sat_ft06", size = "small", - args = ["--input $(rootpath //ortools/scheduling/testdata:ft06)"], - binary = ":jobshop_sat", - data = ["//ortools/scheduling/testdata:ft06"], + srcs = [":jobshop_sat_ft06.bintest"], + named_data = { + "jobshop_sat": ":jobshop_sat", + "ft06": "//ortools/scheduling/testdata:ft06", + }, ) cc_binary( @@ -264,10 +301,11 @@ cc_binary( ], ) -run_binary_test( +bintest( name = "magic_sequence_sat_test", size = "medium", - binary = ":magic_sequence_sat", + srcs = [":magic_sequence_sat_test.bintest"], + named_data = {"magic_sequence_sat": ":magic_sequence_sat"}, ) cc_binary( @@ -284,10 +322,11 @@ cc_binary( ], ) -run_binary_test( +bintest( name = "multi_knapsack_sat_test", size = "medium", - binary = ":multi_knapsack_sat", + srcs = [":multi_knapsack_sat_test.bintest"], + named_data = {"multi_knapsack_sat": ":multi_knapsack_sat"}, ) cc_binary( @@ -309,12 +348,14 @@ cc_binary( ], ) -run_binary_test( +bintest( name = "shift_minimization_sat_test", size = "medium", - args = ["--input $(rootpath :shift_minimization.dat)"], - binary = ":shift_minimization_sat", - data = [":shift_minimization.dat"], + srcs = [":shift_minimization_sat_test.bintest"], + named_data = { + "shift_minimization_sat": ":shift_minimization_sat", + "shift_minimization.dat": "testdata/shift_minimization.dat", + }, ) cc_binary( @@ -339,12 +380,14 @@ cc_binary( ], ) -run_binary_test( +bintest( name = "weighted_tardiness_sat_test", size = "medium", - args = ["--input $(rootpath :wt40.txt)"], - binary = ":weighted_tardiness_sat", - data = [":wt40.txt"], + srcs = [":weighted_tardiness_sat_test.bintest"], + named_data = { + "weighted_tardiness_sat": ":weighted_tardiness_sat", + "wt40.txt": "testdata/wt40.txt", + }, ) cc_binary( @@ -363,10 +406,11 @@ cc_binary( ], ) -run_binary_test( +bintest( name = "magic_square_sat_test", size = "medium", - binary = ":magic_square_sat", + srcs = [":magic_square_sat_test.bintest"], + named_data = {"magic_square_sat": ":magic_square_sat"}, ) cc_binary( @@ -394,23 +438,11 @@ cc_binary( ], ) -run_binary_test( +bintest( name = "network_routing_sat_test", size = "medium", - args = [ - "--clients=10", - "--backbones=5", - "--demands=10", - "--traffic_min=5", - "--traffic_max=10", - "--min_client_degree=2", - "--max_client_degree=5", - "--min_backbone_degree=3", - "--max_backbone_degree=5", - "--max_capacity=20", - "--fixed_charge_cost=10", - ], - binary = ":network_routing_sat", + srcs = [":network_routing_sat_test.bintest"], + named_data = {"network_routing_sat": ":network_routing_sat"}, ) cc_binary( @@ -426,10 +458,11 @@ cc_binary( ], ) -run_binary_test( +bintest( name = "nqueens_test", size = "small", - binary = ":nqueens", + srcs = [":nqueens_test.bintest"], + named_data = {"nqueens": ":nqueens"}, ) cc_binary( @@ -451,10 +484,11 @@ cc_binary( ], ) -run_binary_test( +bintest( name = "sports_scheduling_sat_test", size = "medium", - binary = ":sports_scheduling_sat", + srcs = [":sports_scheduling_sat_test.bintest"], + named_data = {"sports_scheduling_sat": ":sports_scheduling_sat"}, ) cc_binary( @@ -485,6 +519,26 @@ cc_binary( ], ) +bintest( + name = "pdptw_test", + size = "medium", + srcs = [":pdptw_test.bintest"], + named_data = { + "pdptw": ":pdptw", + "lc102.txt": "testdata/lc102.txt", + }, +) + +bintest( + name = "pdptw_non_homogenous_fleet_test", + size = "medium", + srcs = [":pdptw_non_homogenous_fleet_test.bintest"], + named_data = { + "pdptw": ":pdptw", + "lc102.txt": "testdata/lc102.txt", + }, +) + # Routing examples. cc_binary( name = "random_tsp", @@ -523,10 +577,11 @@ cc_binary( ], ) -run_binary_test( +bintest( name = "integer_programming_test", size = "small", - binary = ":integer_programming", + srcs = [":integer_programming_test.bintest"], + named_data = {"integer_programming": ":integer_programming"}, ) cc_binary( @@ -543,10 +598,11 @@ cc_binary( ], ) -run_binary_test( +bintest( name = "linear_programming_test", size = "small", - binary = ":linear_programming", + srcs = [":linear_programming_test.bintest"], + named_data = {"linear_programming": ":linear_programming"}, ) cc_binary( @@ -559,10 +615,11 @@ cc_binary( ], ) -run_binary_test( +bintest( name = "linear_solver_protocol_buffers_test", size = "small", - binary = ":linear_solver_protocol_buffers", + srcs = [":linear_solver_protocol_buffers_test.bintest"], + named_data = {"linear_solver_protocol_buffers": ":linear_solver_protocol_buffers"}, ) cc_binary( @@ -579,11 +636,11 @@ cc_binary( ], ) -run_binary_test( +bintest( name = "strawberry_fields_with_column_generation_test", size = "large", - args = ["--colgen_instance=4"], - binary = ":strawberry_fields_with_column_generation", + srcs = [":strawberry_fields_with_column_generation_test.bintest"], + named_data = {"strawberry_fields_with_column_generation": ":strawberry_fields_with_column_generation"}, ) # Dimacs assignment problems @@ -625,10 +682,30 @@ cc_binary( "//ortools/graph:linear_assignment", "@abseil-cpp//absl/container:flat_hash_map", "@abseil-cpp//absl/flags:flag", - "@abseil-cpp//absl/strings:str_format", + "@abseil-cpp//absl/strings:string_view", ], ) +bintest( + name = "dimacs_assignment_min_cost_test", + size = "small", + srcs = [":dimacs_assignment_min_cost_test.bintest"], + named_data = { + "dimacs_assignment": ":dimacs_assignment", + "dimacs_example.txt": "testdata/dimacs_example.txt", + }, +) + +bintest( + name = "dimacs_assignment_max_cost_test", + size = "small", + srcs = [":dimacs_assignment_max_cost_test.bintest"], + named_data = { + "dimacs_assignment": ":dimacs_assignment", + "dimacs_example.txt": "testdata/dimacs_example.txt", + }, +) + # MPS driver for LP and MIP. cc_binary( name = "mps_driver", @@ -658,12 +735,14 @@ cc_binary( ], ) -run_binary_test( +bintest( name = "mps_driver_test", size = "small", - args = ["--input $(rootpath //ortools/linear_solver/testdata:maximization.mps)"], - binary = ":mps_driver", - data = ["//ortools/linear_solver/testdata:maximization.mps"], + srcs = [":mps_driver_test.bintest"], + named_data = { + "mps_driver": ":mps_driver", + "maximization.mps": "//ortools/linear_solver/testdata:maximization.mps", + }, ) # Linear Assignment C++ Example @@ -677,10 +756,11 @@ cc_binary( ], ) -run_binary_test( +bintest( name = "linear_assignment_api_test", size = "small", - binary = ":linear_assignment_api", + srcs = [":linear_assignment_api_test.bintest"], + named_data = {"linear_assignment_api": ":linear_assignment_api"}, ) # Flow C++ Example @@ -695,10 +775,11 @@ cc_binary( ], ) -run_binary_test( +bintest( name = "flow_api_test", size = "small", - binary = ":flow_api", + srcs = [":flow_api_test.bintest"], + named_data = {"flow_api": ":flow_api"}, ) cc_binary( @@ -714,10 +795,11 @@ cc_binary( ], ) -run_binary_test( +bintest( name = "max_flow_test", size = "small", - binary = ":max_flow", + srcs = [":max_flow_test.bintest"], + named_data = {"max_flow": ":max_flow"}, ) cc_binary( @@ -732,10 +814,11 @@ cc_binary( ], ) -run_binary_test( +bintest( name = "min_cost_flow_test", size = "small", - binary = ":min_cost_flow", + srcs = [":min_cost_flow_test.bintest"], + named_data = {"min_cost_flow": ":min_cost_flow"}, ) # Frequency Assignment Problem @@ -827,10 +910,11 @@ cc_binary( ], ) -run_binary_test( +bintest( name = "slitherlink_sat_test", size = "small", - binary = ":slitherlink_sat", + srcs = [":slitherlink_sat_test.bintest"], + named_data = {"slitherlink_sat": ":slitherlink_sat"}, ) cc_binary( @@ -862,10 +946,11 @@ cc_binary( ], ) -run_binary_test( +bintest( name = "variable_intervals_sat_test", size = "small", - binary = ":variable_intervals_sat", + srcs = [":variable_intervals_sat_test.bintest"], + named_data = {"variable_intervals_sat": ":variable_intervals_sat"}, ) cc_binary( diff --git a/examples/cpp/CMakeBazel.txt b/examples/cpp/CMakeBazel.txt new file mode 100644 index 00000000000..063affbafd9 --- /dev/null +++ b/examples/cpp/CMakeBazel.txt @@ -0,0 +1,425 @@ +# This file is auto generated by bazel2cmake.py from examples/cpp/BUILD.bazel +# Don't edit manually, your changes will be lost. +# You can update this file by running: +# python3 tools/build/bazel2cmake.py examples/cpp/BUILD.bazel + + +ortools_cxx_binary( + NAME bzl_cc_example_binpacking_2d_sat + SOURCES binpacking_2d_sat.cc +) + +ortools_cxx_bintest( + NAME bzl_cc_example_binpacking_2d_sat_class01_instance2_test + SCRIPT ${CMAKE_CURRENT_SOURCE_DIR}/binpacking_2d_sat_class01_instance2_test.bintest + ENVIRONMENT BINTEST_binpacking_2d_sat=$ BINTEST_Class_01.2bp=${CMAKE_SOURCE_DIR}/ortools/packing/testdata/Class_01.2bp +) + +ortools_cxx_library( + NAME bzl_cc_example_cgc + SOURCES cgc.cc cgc.h cgc_data.h + TYPE SHARED +) + +ortools_cxx_binary( + NAME bzl_cc_example_cgc_main + SOURCES cgc_main.cc + LINK_LIBRARIES bzl_cc_example_cgc +) + +ortools_cxx_bintest( + NAME bzl_cc_example_cgc_test_solution + SCRIPT ${CMAKE_CURRENT_SOURCE_DIR}/cgc_test_solution.bintest + ENVIRONMENT BINTEST_cgc_main=$ BINTEST_1.in=${CMAKE_CURRENT_SOURCE_DIR}/testdata/cgc/1.in BINTEST_2.in=${CMAKE_CURRENT_SOURCE_DIR}/testdata/cgc/2.in BINTEST_3.in=${CMAKE_CURRENT_SOURCE_DIR}/testdata/cgc/3.in BINTEST_cgcut1.in=${CMAKE_CURRENT_SOURCE_DIR}/testdata/cgc/cgcut1.in BINTEST_cgcut2.in=${CMAKE_CURRENT_SOURCE_DIR}/testdata/cgc/cgcut2.in BINTEST_cgcut3.in=${CMAKE_CURRENT_SOURCE_DIR}/testdata/cgc/cgcut3.in +) + +ortools_cxx_binary( + NAME bzl_cc_example_constraint_programming_cp + SOURCES constraint_programming_cp.cc +) + +ortools_cxx_bintest( + NAME bzl_cc_example_constraint_programming_cp_test + SCRIPT ${CMAKE_CURRENT_SOURCE_DIR}/constraint_programming_cp_test.bintest + ENVIRONMENT BINTEST_constraint_programming_cp=$ +) + +ortools_cxx_binary( + NAME bzl_cc_example_costas_array_sat + SOURCES costas_array_sat.cc +) + +ortools_cxx_bintest( + NAME bzl_cc_example_costas_array_sat_model1_test + SCRIPT ${CMAKE_CURRENT_SOURCE_DIR}/costas_array_sat_model1_test.bintest + ENVIRONMENT BINTEST_costas_array_sat=$ +) + +ortools_cxx_bintest( + NAME bzl_cc_example_costas_array_sat_model2_test + SCRIPT ${CMAKE_CURRENT_SOURCE_DIR}/costas_array_sat_model2_test.bintest + ENVIRONMENT BINTEST_costas_array_sat=$ +) + +ortools_cxx_bintest( + NAME bzl_cc_example_costas_array_sat_model3_test + SCRIPT ${CMAKE_CURRENT_SOURCE_DIR}/costas_array_sat_model3_test.bintest + ENVIRONMENT BINTEST_costas_array_sat=$ +) + +ortools_cxx_binary( + NAME bzl_cc_example_cryptarithm_sat + SOURCES cryptarithm_sat.cc +) + +ortools_cxx_bintest( + NAME bzl_cc_example_cryptarithm_sat_test + SCRIPT ${CMAKE_CURRENT_SOURCE_DIR}/cryptarithm_sat_test.bintest + ENVIRONMENT BINTEST_cryptarithm_sat=$ +) + +ortools_cxx_binary( + NAME bzl_cc_example_dobble_ls + SOURCES dobble_ls.cc +) + +ortools_cxx_bintest( + NAME bzl_cc_example_dobble_ls_test + SCRIPT ${CMAKE_CURRENT_SOURCE_DIR}/dobble_ls_test.bintest + ENVIRONMENT BINTEST_dobble_ls=$ +) + +ortools_cxx_binary( + NAME bzl_cc_example_golomb_sat + SOURCES golomb_sat.cc +) + +ortools_cxx_bintest( + NAME bzl_cc_example_golomb_sat_test + SCRIPT ${CMAKE_CURRENT_SOURCE_DIR}/golomb_sat_test.bintest + ENVIRONMENT BINTEST_golomb_sat=$ +) + +ortools_cxx_binary( + NAME bzl_cc_example_knapsack_2d_sat + SOURCES knapsack_2d_sat.cc +) + +ortools_cxx_bintest( + NAME bzl_cc_example_knapsack_2d_sat_class01_instance2_test + SCRIPT ${CMAKE_CURRENT_SOURCE_DIR}/knapsack_2d_sat_class01_instance2_test.bintest + ENVIRONMENT BINTEST_knapsack_2d_sat=$ BINTEST_Class_01.2bp=${CMAKE_SOURCE_DIR}/ortools/packing/testdata/Class_01.2bp +) + +ortools_cxx_binary( + NAME bzl_cc_example_jobshop_sat + SOURCES jobshop_sat.cc +) + +ortools_cxx_bintest( + NAME bzl_cc_example_jobshop_sat_ft06 + SCRIPT ${CMAKE_CURRENT_SOURCE_DIR}/jobshop_sat_ft06.bintest + ENVIRONMENT BINTEST_jobshop_sat=$ BINTEST_ft06=${CMAKE_SOURCE_DIR}/ortools/scheduling/testdata/ft06 +) + +ortools_cxx_binary( + NAME bzl_cc_example_magic_sequence_sat + SOURCES magic_sequence_sat.cc +) + +ortools_cxx_bintest( + NAME bzl_cc_example_magic_sequence_sat_test + SCRIPT ${CMAKE_CURRENT_SOURCE_DIR}/magic_sequence_sat_test.bintest + ENVIRONMENT BINTEST_magic_sequence_sat=$ +) + +ortools_cxx_binary( + NAME bzl_cc_example_multi_knapsack_sat + SOURCES multi_knapsack_sat.cc +) + +ortools_cxx_bintest( + NAME bzl_cc_example_multi_knapsack_sat_test + SCRIPT ${CMAKE_CURRENT_SOURCE_DIR}/multi_knapsack_sat_test.bintest + ENVIRONMENT BINTEST_multi_knapsack_sat=$ +) + +ortools_cxx_binary( + NAME bzl_cc_example_shift_minimization_sat + SOURCES shift_minimization_sat.cc +) + +ortools_cxx_bintest( + NAME bzl_cc_example_shift_minimization_sat_test + SCRIPT ${CMAKE_CURRENT_SOURCE_DIR}/shift_minimization_sat_test.bintest + ENVIRONMENT BINTEST_shift_minimization_sat=$ BINTEST_shift_minimization.dat=${CMAKE_CURRENT_SOURCE_DIR}/testdata/shift_minimization.dat +) + +ortools_cxx_binary( + NAME bzl_cc_example_weighted_tardiness_sat + SOURCES weighted_tardiness_sat.cc +) + +ortools_cxx_bintest( + NAME bzl_cc_example_weighted_tardiness_sat_test + SCRIPT ${CMAKE_CURRENT_SOURCE_DIR}/weighted_tardiness_sat_test.bintest + ENVIRONMENT BINTEST_weighted_tardiness_sat=$ BINTEST_wt40.txt=${CMAKE_CURRENT_SOURCE_DIR}/testdata/wt40.txt +) + +ortools_cxx_binary( + NAME bzl_cc_example_magic_square_sat + SOURCES magic_square_sat.cc +) + +ortools_cxx_bintest( + NAME bzl_cc_example_magic_square_sat_test + SCRIPT ${CMAKE_CURRENT_SOURCE_DIR}/magic_square_sat_test.bintest + ENVIRONMENT BINTEST_magic_square_sat=$ +) + +ortools_cxx_binary( + NAME bzl_cc_example_network_routing_sat + SOURCES network_routing_sat.cc +) + +ortools_cxx_bintest( + NAME bzl_cc_example_network_routing_sat_test + SCRIPT ${CMAKE_CURRENT_SOURCE_DIR}/network_routing_sat_test.bintest + ENVIRONMENT BINTEST_network_routing_sat=$ +) + +ortools_cxx_binary( + NAME bzl_cc_example_nqueens + SOURCES nqueens.cc +) + +ortools_cxx_bintest( + NAME bzl_cc_example_nqueens_test + SCRIPT ${CMAKE_CURRENT_SOURCE_DIR}/nqueens_test.bintest + ENVIRONMENT BINTEST_nqueens=$ +) + +ortools_cxx_binary( + NAME bzl_cc_example_sports_scheduling_sat + SOURCES sports_scheduling_sat.cc +) + +ortools_cxx_bintest( + NAME bzl_cc_example_sports_scheduling_sat_test + SCRIPT ${CMAKE_CURRENT_SOURCE_DIR}/sports_scheduling_sat_test.bintest + ENVIRONMENT BINTEST_sports_scheduling_sat=$ +) + +ortools_cxx_binary( + NAME bzl_cc_example_pdptw + SOURCES pdptw.cc +) + +ortools_cxx_bintest( + NAME bzl_cc_example_pdptw_test + SCRIPT ${CMAKE_CURRENT_SOURCE_DIR}/pdptw_test.bintest + ENVIRONMENT BINTEST_pdptw=$ BINTEST_lc102.txt=${CMAKE_CURRENT_SOURCE_DIR}/testdata/lc102.txt +) + +ortools_cxx_bintest( + NAME bzl_cc_example_pdptw_non_homogenous_fleet_test + SCRIPT ${CMAKE_CURRENT_SOURCE_DIR}/pdptw_non_homogenous_fleet_test.bintest + ENVIRONMENT BINTEST_pdptw=$ BINTEST_lc102.txt=${CMAKE_CURRENT_SOURCE_DIR}/testdata/lc102.txt +) + +ortools_cxx_binary( + NAME bzl_cc_example_random_tsp + SOURCES random_tsp.cc +) + +ortools_cxx_binary( + NAME bzl_cc_example_integer_programming + SOURCES integer_programming.cc +) + +ortools_cxx_bintest( + NAME bzl_cc_example_integer_programming_test + SCRIPT ${CMAKE_CURRENT_SOURCE_DIR}/integer_programming_test.bintest + ENVIRONMENT BINTEST_integer_programming=$ +) + +ortools_cxx_binary( + NAME bzl_cc_example_linear_programming + SOURCES linear_programming.cc +) + +ortools_cxx_bintest( + NAME bzl_cc_example_linear_programming_test + SCRIPT ${CMAKE_CURRENT_SOURCE_DIR}/linear_programming_test.bintest + ENVIRONMENT BINTEST_linear_programming=$ +) + +ortools_cxx_binary( + NAME bzl_cc_example_linear_solver_protocol_buffers + SOURCES linear_solver_protocol_buffers.cc +) + +ortools_cxx_bintest( + NAME bzl_cc_example_linear_solver_protocol_buffers_test + SCRIPT ${CMAKE_CURRENT_SOURCE_DIR}/linear_solver_protocol_buffers_test.bintest + ENVIRONMENT BINTEST_linear_solver_protocol_buffers=$ +) + +ortools_cxx_binary( + NAME bzl_cc_example_strawberry_fields_with_column_generation + SOURCES strawberry_fields_with_column_generation.cc +) + +ortools_cxx_bintest( + NAME bzl_cc_example_strawberry_fields_with_column_generation_test + SCRIPT ${CMAKE_CURRENT_SOURCE_DIR}/strawberry_fields_with_column_generation_test.bintest + ENVIRONMENT BINTEST_strawberry_fields_with_column_generation=$ +) + +ortools_cxx_library( + NAME bzl_cc_example_print_dimacs_assignment + SOURCES print_dimacs_assignment.h + TYPE INTERFACE +) + +ortools_cxx_library( + NAME bzl_cc_example_parse_dimacs_assignment + SOURCES parse_dimacs_assignment.cc parse_dimacs_assignment.h + TYPE SHARED +) + +ortools_cxx_binary( + NAME bzl_cc_example_dimacs_assignment + SOURCES dimacs_assignment.cc + LINK_LIBRARIES bzl_cc_example_parse_dimacs_assignment bzl_cc_example_print_dimacs_assignment +) + +ortools_cxx_bintest( + NAME bzl_cc_example_dimacs_assignment_min_cost_test + SCRIPT ${CMAKE_CURRENT_SOURCE_DIR}/dimacs_assignment_min_cost_test.bintest + ENVIRONMENT BINTEST_dimacs_assignment=$ BINTEST_dimacs_example.txt=${CMAKE_CURRENT_SOURCE_DIR}/testdata/dimacs_example.txt +) + +ortools_cxx_bintest( + NAME bzl_cc_example_dimacs_assignment_max_cost_test + SCRIPT ${CMAKE_CURRENT_SOURCE_DIR}/dimacs_assignment_max_cost_test.bintest + ENVIRONMENT BINTEST_dimacs_assignment=$ BINTEST_dimacs_example.txt=${CMAKE_CURRENT_SOURCE_DIR}/testdata/dimacs_example.txt +) + +ortools_cxx_binary( + NAME bzl_cc_example_mps_driver + SOURCES mps_driver.cc +) + +ortools_cxx_bintest( + NAME bzl_cc_example_mps_driver_test + SCRIPT ${CMAKE_CURRENT_SOURCE_DIR}/mps_driver_test.bintest + ENVIRONMENT BINTEST_mps_driver=$ BINTEST_maximization.mps=${CMAKE_SOURCE_DIR}/ortools/linear_solver/testdata/maximization.mps +) + +ortools_cxx_binary( + NAME bzl_cc_example_linear_assignment_api + SOURCES linear_assignment_api.cc +) + +ortools_cxx_bintest( + NAME bzl_cc_example_linear_assignment_api_test + SCRIPT ${CMAKE_CURRENT_SOURCE_DIR}/linear_assignment_api_test.bintest + ENVIRONMENT BINTEST_linear_assignment_api=$ +) + +ortools_cxx_binary( + NAME bzl_cc_example_flow_api + SOURCES flow_api.cc +) + +ortools_cxx_bintest( + NAME bzl_cc_example_flow_api_test + SCRIPT ${CMAKE_CURRENT_SOURCE_DIR}/flow_api_test.bintest + ENVIRONMENT BINTEST_flow_api=$ +) + +ortools_cxx_binary( + NAME bzl_cc_example_max_flow + SOURCES max_flow.cc +) + +ortools_cxx_bintest( + NAME bzl_cc_example_max_flow_test + SCRIPT ${CMAKE_CURRENT_SOURCE_DIR}/max_flow_test.bintest + ENVIRONMENT BINTEST_max_flow=$ +) + +ortools_cxx_binary( + NAME bzl_cc_example_min_cost_flow + SOURCES min_cost_flow.cc +) + +ortools_cxx_bintest( + NAME bzl_cc_example_min_cost_flow_test + SCRIPT ${CMAKE_CURRENT_SOURCE_DIR}/min_cost_flow_test.bintest + ENVIRONMENT BINTEST_min_cost_flow=$ +) + +ortools_cxx_library( + NAME bzl_cc_example_fap_parser + SOURCES fap_parser.cc fap_parser.h + TYPE SHARED +) + +ortools_cxx_library( + NAME bzl_cc_example_fap_model_printer + SOURCES fap_model_printer.cc fap_model_printer.h + LINK_LIBRARIES bzl_cc_example_fap_parser + TYPE SHARED +) + +ortools_cxx_library( + NAME bzl_cc_example_fap_utilities + SOURCES fap_utilities.cc fap_utilities.h + LINK_LIBRARIES bzl_cc_example_fap_parser + TYPE SHARED +) + +ortools_cxx_binary( + NAME bzl_cc_example_frequency_assignment_problem + SOURCES frequency_assignment_problem.cc + LINK_LIBRARIES bzl_cc_example_fap_model_printer bzl_cc_example_fap_parser bzl_cc_example_fap_utilities +) + +ortools_cxx_binary( + NAME bzl_cc_example_qap_sat + SOURCES qap_sat.cc +) + +ortools_cxx_binary( + NAME bzl_cc_example_slitherlink_sat + SOURCES slitherlink_sat.cc +) + +ortools_cxx_bintest( + NAME bzl_cc_example_slitherlink_sat_test + SCRIPT ${CMAKE_CURRENT_SOURCE_DIR}/slitherlink_sat_test.bintest + ENVIRONMENT BINTEST_slitherlink_sat=$ +) + +ortools_cxx_binary( + NAME bzl_cc_example_uncapacitated_facility_location + SOURCES uncapacitated_facility_location.cc +) + +ortools_cxx_binary( + NAME bzl_cc_example_variable_intervals_sat + SOURCES variable_intervals_sat.cc +) + +ortools_cxx_bintest( + NAME bzl_cc_example_variable_intervals_sat_test + SCRIPT ${CMAKE_CURRENT_SOURCE_DIR}/variable_intervals_sat_test.bintest + ENVIRONMENT BINTEST_variable_intervals_sat=$ +) + +ortools_cxx_binary( + NAME bzl_cc_example_pdlp_solve + SOURCES pdlp_solve.cc +) \ No newline at end of file diff --git a/examples/cpp/CMakeLists.txt b/examples/cpp/CMakeLists.txt index 3f4f6515589..522f06f068c 100644 --- a/examples/cpp/CMakeLists.txt +++ b/examples/cpp/CMakeLists.txt @@ -15,51 +15,4 @@ if(NOT BUILD_CXX_EXAMPLES) return() endif() -#file(GLOB_RECURSE proto_files RELATIVE ${PROJECT_SOURCE_DIR} "*.proto") -#foreach(PROTO_FILE IN LISTS proto_files) -# message(STATUS "protoc proto(cc): ${PROTO_FILE}") -# get_filename_component(PROTO_DIR ${PROTO_FILE} DIRECTORY) -# get_filename_component(PROTO_NAME ${PROTO_FILE} NAME_WE) -# set(PROTO_HDR ${PROJECT_BINARY_DIR}/${PROTO_DIR}/${PROTO_NAME}.pb.h) -# set(PROTO_SRC ${PROJECT_BINARY_DIR}/${PROTO_DIR}/${PROTO_NAME}.pb.cc) -# message(STATUS "protoc hdr: ${PROTO_HDR}") -# message(STATUS "protoc src: ${PROTO_SRC}") -# add_custom_command( -# OUTPUT ${PROTO_SRC} ${PROTO_HDR} -# COMMAND ${PROTOC_PRG} -# "--proto_path=${PROJECT_SOURCE_DIR}" -# ${PROTO_DIRS} -# "--cpp_out=${PROJECT_BINARY_DIR}" -# ${PROTO_FILE} -# DEPENDS ${PROTO_FILE} ${PROTOC_PRG} -# COMMENT "Generate C++ protocol buffer for ${PROTO_FILE}" -# VERBATIM) -# list(APPEND PROTO_HDRS ${PROTO_HDR}) -# list(APPEND PROTO_SRCS ${PROTO_SRC}) -#endforeach() - -file(GLOB CXX_SRCS "*.cc") -list(FILTER CXX_SRCS EXCLUDE REGEX ".*/binpacking_2d_sat.cc") -list(FILTER CXX_SRCS EXCLUDE REGEX ".*/course_scheduling_run.cc") # missing proto -list(FILTER CXX_SRCS EXCLUDE REGEX ".*/course_scheduling.cc") # missing proto -list(FILTER CXX_SRCS EXCLUDE REGEX ".*/dimacs_assignment.cc") # crash -list(FILTER CXX_SRCS EXCLUDE REGEX ".*/dobble_ls.cc") # Too long -list(FILTER CXX_SRCS EXCLUDE REGEX ".*/fap_model_printer.cc") # lib -list(FILTER CXX_SRCS EXCLUDE REGEX ".*/fap_parser.cc") # lib -list(FILTER CXX_SRCS EXCLUDE REGEX ".*/fap_utilities.cc") # lib -list(FILTER CXX_SRCS EXCLUDE REGEX ".*/frequency_assignment_problem.cc") # crash -list(FILTER CXX_SRCS EXCLUDE REGEX ".*/jobshop_sat.cc") # crash -list(FILTER CXX_SRCS EXCLUDE REGEX ".*/knapsack_2d_sat.cc") -list(FILTER CXX_SRCS EXCLUDE REGEX ".*/mps_driver.cc") # crash -list(FILTER CXX_SRCS EXCLUDE REGEX ".*/multi_knapsack_sat.cc") # crash -list(FILTER CXX_SRCS EXCLUDE REGEX ".*/network_routing_sat.cc") -list(FILTER CXX_SRCS EXCLUDE REGEX ".*/parse_dimacs_assignment.cc") # lib -list(FILTER CXX_SRCS EXCLUDE REGEX ".*/pdlp_solve.cc") -list(FILTER CXX_SRCS EXCLUDE REGEX ".*/pdptw.cc") -list(FILTER CXX_SRCS EXCLUDE REGEX ".*/shift_minimization_sat.cc") -list(FILTER CXX_SRCS EXCLUDE REGEX ".*/strawberry_fields_with_column_generation.cc") # Too long -list(FILTER CXX_SRCS EXCLUDE REGEX ".*/vector_bin_packing_solver.cc") -list(FILTER CXX_SRCS EXCLUDE REGEX ".*/weighted_tardiness_sat.cc") -foreach(EXAMPLE IN LISTS CXX_SRCS) - add_cxx_example(FILE_NAME ${EXAMPLE}) -endforeach() +include("CMakeBazel.txt") diff --git a/examples/cpp/binpacking_2d_sat_class01_instance2_test.bintest b/examples/cpp/binpacking_2d_sat_class01_instance2_test.bintest new file mode 100644 index 00000000000..1c18fa902f8 --- /dev/null +++ b/examples/cpp/binpacking_2d_sat_class01_instance2_test.bintest @@ -0,0 +1 @@ +RUN: $(binpacking_2d_sat) --input $(Class_01.2bp) --instance 2 diff --git a/examples/cpp/cgc.cc b/examples/cpp/cgc.cc new file mode 100644 index 00000000000..c23ce870ad3 --- /dev/null +++ b/examples/cpp/cgc.cc @@ -0,0 +1,573 @@ +// 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. + +// Two-Dimensional Constrained Guillotine Cutting + +#include "examples/cpp/cgc.h" + +#include +#include +#include +#include + +#include "absl/container/btree_set.h" +#include "absl/strings/str_format.h" +#include "absl/strings/str_split.h" +#include "absl/time/time.h" +#include "absl/types/span.h" +#include "examples/cpp/cgc_data.h" +#include "ortools/base/helpers.h" +#include "ortools/base/logging.h" +#include "ortools/base/options.h" +#include "ortools/constraint_solver/constraint_solver.h" +#include "re2/re2.h" + +namespace operations_research { + +bool ConstrainedGuillotineCuttingData::LoadFromFile( + const std::string& input_file) { + std::string buffer; + + if (!file::GetContents(input_file, &buffer, file::Defaults()).ok()) { + LOG(ERROR) << "Could not read from file " << input_file; + return false; + } + const std::vector lines = + absl::StrSplit(buffer, '\n', absl::SkipEmpty()); + + if (lines.empty()) { + LOG(ERROR) << "Empty file: " << input_file; + return false; + } + + int num_pieces; + if (!RE2::FullMatch(lines[0], "\\s*(\\d+)\\s*", &num_pieces)) { + LOG(ERROR) << "Could not parse number of pieces"; + return false; + } + + if (0 >= num_pieces) { + LOG(ERROR) << "There are no pieces in the problem specification"; + return false; + } + + if (lines.size() != num_pieces + 2) { + LOG(ERROR) << "File: " << input_file << " does not respect the format"; + return false; + } + + if (!RE2::FullMatch(lines[1], "\\s*(\\d+)\\s+(\\d+)\\s*", &root_length_, + &root_width_)) { + LOG(ERROR) << "Could not parse the size of the main rectangle"; + return false; + } + + pieces_.resize(num_pieces); + for (int i = 0; i < num_pieces; ++i) { + if (!RE2::FullMatch(lines[i + 2], + "\\s*(\\d+)\\s+(\\d+)\\s+(\\d+)\\s+(\\d+)\\s*", + &pieces_[i].length, &pieces_[i].width, + &pieces_[i].max_appearances, &pieces_[i].value)) { + LOG(ERROR) << "Could not parse piece on line " << lines[i + 2] + << ", line number " << i + 3; + return false; + } + } + + return true; +} + +namespace { + +// Depending on the part that was cut, returns an IntVar representing +// if the cut that was made on the (index / 2 + 1) rectangle is +// a guillotine cut. +IntVar* IsAGuillotineCut(int index, + const std::vector& size_currently_cut, + const std::vector& size_not_cut, + const std::vector& parent_index, + const std::vector& pieces_size, Solver* solver) { + CHECK(solver != nullptr); + // The size of the cut must be >= 1 in order for the cut to be a valid one. + IntVar* const condition_var = + solver->MakeIsGreaterOrEqualCstVar(size_currently_cut[index], 1); + + // The part that is not cut should remain the same. + IntVar* const same_uncut_size_as_sibling = + solver->MakeIsEqualVar(size_not_cut[index], size_not_cut[index + 1]); + + // The part that is not cut should remain the same with the parent. + IntVar* const same_uncut_size_as_parent = solver->MakeIsEqualVar( + size_not_cut[index], + solver->MakeElement(size_not_cut, parent_index[index / 2 + 1])); + + // We make a cut if the size of the cut matches at least one of the pieces. + IntVar* const cut_equals_piece_size = solver->MakeIsEqualCstVar( + solver->MakeElement(pieces_size, size_currently_cut[index]), 1); + + // The sum of the sizes that were cut should equal the parent size. + IntVar* const sum_of_sizes = solver->MakeIsEqualVar( + solver->MakeSum(size_currently_cut[index], size_currently_cut[index + 1]), + solver->MakeElement(size_currently_cut, parent_index[index / 2 + 1])); + + const std::vector cut_implications = { + same_uncut_size_as_sibling, same_uncut_size_as_parent, + cut_equals_piece_size, sum_of_sizes}; + + return solver + ->MakeConditionalExpression( + condition_var, + solver->MakeIsEqualCstVar(solver->MakeSum(cut_implications), + cut_implications.size()), + 0) + ->Var(); +} + +// Sets initial variables: +// stores piece sizes related variables and sets maximum value +// and maximum number of elements in a cutting path. +void SetInitialElements(const ConstrainedGuillotineCuttingData& data, + std::vector* sizes_to_pieces, + std::vector* piece_length, + std::vector* piece_width, int* maximum_value, + int* maximum_elements) { + CHECK(sizes_to_pieces != nullptr); + CHECK(piece_length != nullptr); + CHECK(piece_width != nullptr); + CHECK(maximum_value != nullptr); + CHECK(maximum_elements != nullptr); + sizes_to_pieces->resize((data.root_length() + 1) * (data.root_width() + 1), + data.pieces().size()); + piece_length->resize(data.root_length() + 1, 0); + piece_width->resize(data.root_width() + 1, 0); + + const int main_rectangle_area = data.root_length() * data.root_width(); + + // Number of elements in the path should dependent on the number + // of end pieces. Considering that at every point we could in 2 cuts + // get to an end piece, which means maximum 4 new pieces, + // a limit of 4 * number_of_end_pieces should fit the path. + static const int kMultiplyNumOfEndPiecesBy = 4; + *maximum_value = 0; + *maximum_elements = 1; + + int index = 0; + for (const ConstrainedGuillotineCuttingData::Piece& piece : data.pieces()) { + if (piece.length <= data.root_length() && + piece.width <= data.root_width()) { + (*sizes_to_pieces)[piece.length * data.root_width() + piece.width] = + index; + (*piece_length)[piece.length] = 1; + (*piece_width)[piece.width] = 1; + } + + const int number_of_appearances = + std::min(piece.max_appearances, + main_rectangle_area / (piece.length * piece.width)); + + *maximum_value += piece.value * number_of_appearances; + *maximum_elements += kMultiplyNumOfEndPiecesBy * number_of_appearances; + index++; + } + + // TODO(user): a better upper bound for value and for + // maximum_elements. +} + +void SetRectanglesVariablesAndAddConstraints( + const std::vector& piece_length, const std::vector& piece_width, + const int maximum_elements, const int root_length, const int root_width, + std::vector* parent_index, std::vector* rectangle_length, + std::vector* rectangle_width, Solver* solver) { + CHECK(parent_index != nullptr); + CHECK(rectangle_length != nullptr); + CHECK(rectangle_width != nullptr); + CHECK(solver != nullptr); + + static const int kMainRectangleIndex = 0; + + solver->MakeIntVarArray(maximum_elements / 2 + 2, -1, maximum_elements, + "parent_index_", parent_index); + solver->MakeIntVarArray(maximum_elements, 0, root_length, "length_", + rectangle_length); + solver->MakeIntVarArray(maximum_elements, 0, root_width, "width_", + rectangle_width); + + (*parent_index)[kMainRectangleIndex]->SetValue(-1); + (*rectangle_length)[kMainRectangleIndex]->SetValue(root_length); + (*rectangle_width)[kMainRectangleIndex]->SetValue(root_width); + + // Any rectangle can be cut just once. + solver->AddConstraint(solver->MakeAllDifferent(*parent_index)); + + std::vector x_guillotine_cut; + std::vector y_guillotine_cut; + + // Every 2 consecutive cuts are from the same rectangle starting with + // position 1, since at index 0 we keep information regarding the + // main rectangle. + for (int i = 1; i < maximum_elements; i += 2) { + // The rectangle from which we cut needs to be < i. In case we do not cut + // anything (the elements are all 0) the parent_index will be i. + solver->AddConstraint( + solver->MakeLessOrEqual((*parent_index)[i / 2 + 1], i)); + + // If one of the sizes is 0, then all are 0 and the parent does not point + // to a real parent, but to itself, since we cannot have a valid cut + // that leaves one size 0. + IntVar* length_is_zero = + solver->MakeIsEqualCstVar((*rectangle_length)[i], 0); + + solver->AddConstraint(solver->MakeEquality( + length_is_zero, + solver->MakeIsEqualCstVar((*rectangle_length)[i + 1], 0))); + + solver->AddConstraint(solver->MakeEquality( + length_is_zero, solver->MakeIsEqualCstVar((*rectangle_width)[i], 0))); + + solver->AddConstraint(solver->MakeEquality( + length_is_zero, + solver->MakeIsEqualCstVar((*rectangle_width)[i + 1], 0))); + + solver->AddConstraint(solver->MakeEquality( + length_is_zero, + solver->MakeIsEqualCstVar((*parent_index)[i / 2 + 1], i))); + + // Group 0-cuts together at the beginning. So after a normal cut there + // will not be any 0-cuts. + if (i > 1) { + solver->AddConstraint(solver->MakeLessOrEqual( + solver->MakeIsGreaterOrEqualCstVar((*rectangle_length)[i - 1], 1), + solver->MakeIsGreaterOrEqualCstVar((*rectangle_length)[i], 1))); + } + + // If it is an x-guillotine cut. + x_guillotine_cut.push_back(IsAGuillotineCut(i, *rectangle_length, + *rectangle_width, *parent_index, + piece_length, solver)); + + // If it is an y-guillotine cut. + y_guillotine_cut.push_back( + IsAGuillotineCut(i, *rectangle_width, *rectangle_length, *parent_index, + piece_width, solver)); + + // Every pair of rectangles should correspond to a guillotine cut + // on one of the axis or they could be 0 if there was no cut made. + solver->AddConstraint(solver->MakeEquality( + solver->MakeSum( + length_is_zero, + solver->MakeSum( + solver->MakeIsEqualCstVar(x_guillotine_cut[i / 2], 1), + solver->MakeIsEqualCstVar(y_guillotine_cut[i / 2], 1))), + 1)); + } +} + +void AddAdditionalConstraints(const std::vector& parent_index, + const std::vector& rectangle_length, + const std::vector& rectangle_width, + const std::vector& sizes_to_pieces, + const ConstrainedGuillotineCuttingData& data, + int maximum_elements, + std::vector* is_end_piece, + std::vector* was_cut, IntVar* value, + Solver* solver) { + CHECK(is_end_piece != nullptr); + CHECK(was_cut != nullptr); + CHECK(value != nullptr); + CHECK(solver != nullptr); + solver->MakeIntVarArray(maximum_elements, 0, 1, "", was_cut); + + for (int i = 0; i < maximum_elements; ++i) { + solver->AddConstraint(solver->MakeCount(parent_index, i, (*was_cut)[i])); + + is_end_piece->push_back( + solver + ->MakeConditionalExpression( + solver->MakeIsEqualCstVar((*was_cut)[i], 0), + solver->MakeElement( + sizes_to_pieces, + solver + ->MakeSum(solver->MakeProd(rectangle_length[i], + data.root_width()), + rectangle_width[i]) + ->Var()), + data.pieces().size()) + ->Var()); + } + + int index = 0; + std::vector values; + + for (const ConstrainedGuillotineCuttingData::Piece& piece : data.pieces()) { + // Number of appearances of every type should be less or equal + // to the maximum number of times a piece can appear. + IntVar* const appearances = + solver->MakeIntVar(0, (data.root_length() * data.root_width()) / + (piece.length * piece.width)); + + // The number of appearances of every piece should be equal + // to the number of times that piece appears in a path as an end piece. + solver->AddConstraint(solver->MakeCount(*is_end_piece, index, appearances)); + + // Values will contain for every piece: + // number_of_time_the_piece_appears * its value. + values.push_back( + solver + ->MakeProd(solver->MakeMin(appearances, piece.max_appearances), + piece.value) + ->Var()); + + index++; + } + + solver->AddConstraint(solver->MakeEquality(value, solver->MakeSum(values))); +} + +void CreateAdditionalMonitors(absl::Duration time_limit, + std::vector* monitors, + OptimizeVar* objective_value, Solver* solver) { + CHECK(monitors != nullptr); + CHECK(objective_value != nullptr); + CHECK(solver != nullptr); + monitors->push_back(objective_value); + + static const int kLogFrequency = 100000; + SearchMonitor* const log = + solver->MakeSearchLog(kLogFrequency, objective_value); + monitors->push_back(log); + + if (time_limit != absl::InfiniteDuration()) { + SearchLimit* const limit = solver->MakeTimeLimit(time_limit); + monitors->push_back(limit); + } +} + +DecisionBuilder* CreateDecisionBuilder( + const std::vector& parent_index, + const std::vector& rectangle_length, + const std::vector& rectangle_width, + const std::vector& was_cut, Solver* solver) { + CHECK(solver != nullptr); + std::vector decision_variables; + for (int i = 1; i < rectangle_length.size() / 2 + 1; ++i) { + decision_variables.push_back(parent_index[i]); + decision_variables.push_back(rectangle_length[2 * (i - 1) + 1]); + decision_variables.push_back(rectangle_width[2 * (i - 1) + 1]); + } + for (int i = 0; i < rectangle_length.size(); ++i) { + decision_variables.push_back(was_cut[i]); + } + + return solver->MakePhase(decision_variables, Solver::CHOOSE_FIRST_UNBOUND, + Solver::ASSIGN_MAX_VALUE); +} + +void FillSolution( + const std::vector& parent_index, + const std::vector& rectangle_length, + const std::vector& rectangle_width, + const SolutionCollector* collector, IntVar* value, int* maximum_value, + std::vector* solution) { + CHECK(collector != nullptr); + CHECK(value != nullptr); + CHECK(maximum_value != nullptr); + CHECK(solution != nullptr); + + int number_of_zero_cuts = 0; + int parent = -1; + for (int i = 0; i < rectangle_length.size(); ++i) { + if (!collector->Value(0, rectangle_length[i])) { + number_of_zero_cuts++; + continue; + } + + if (i % 2 == 1) { + parent = std::max( + collector->Value(0, parent_index[i / 2 + 1]) - number_of_zero_cuts, + int64_t{0}); + } + + solution->emplace_back(parent, collector->Value(0, rectangle_length[i]), + collector->Value(0, rectangle_width[i])); + } + + *maximum_value = collector->Value(0, value); +} + +void ValidateSolution(int num_pieces, int root_width, + const std::vector& parent_index, + const std::vector& rectangle_length, + const std::vector& rectangle_width, + const std::vector& is_end_piece, + absl::Span sizes_to_pieces, + const SolutionCollector* collector) { + CHECK(collector != nullptr); + absl::btree_set parent_ids; + for (int i = 0; i < parent_index.size(); ++i) { + parent_ids.insert(collector->Value(0, parent_index[i])); + // The rectangle from which the rectangles were cut needs to be + // <= current position. For every pair of rectangles we keep + // their parent index once. + CHECK(collector->Value(0, parent_index[i]) <= i * 2 - 1); + } + // Every piece should be cut just once. + CHECK_EQ(parent_ids.size(), parent_index.size()); + + bool guillotine_cut = false; + for (int i = 1; i < rectangle_length.size(); i += 2) { + const int parent = collector->Value(0, parent_index[i / 2 + 1]); + const int length_left_rectangle = collector->Value(0, rectangle_length[i]); + const int length_rigth_rectangle = + collector->Value(0, rectangle_length[i + 1]); + const int length_parent = collector->Value(0, rectangle_length[parent]); + const int width_parent = collector->Value(0, rectangle_width[parent]); + const int width_left_rectangle = collector->Value(0, rectangle_width[i]); + const int width_right_rectangle = + collector->Value(0, rectangle_width[i + 1]); + + const bool is_a_x_guillotine_cut = + length_left_rectangle + length_rigth_rectangle == length_parent && + length_left_rectangle && length_rigth_rectangle && + width_left_rectangle == width_right_rectangle && + width_left_rectangle == width_parent; + + const bool is_a_y_guillotine_cut = + width_left_rectangle + width_right_rectangle == width_parent && + width_left_rectangle && width_right_rectangle && + length_left_rectangle == length_rigth_rectangle && + length_left_rectangle == length_parent; + + const bool is_a_zero_cut = !length_left_rectangle && + !length_rigth_rectangle && + !width_left_rectangle && !width_right_rectangle; + + // Every cut is a guillotine cut or all elements are 0. + CHECK(is_a_x_guillotine_cut || is_a_y_guillotine_cut || is_a_zero_cut); + + // Check if it is a piece. + const int it_is_piece1 = + parent_ids.contains(i) + ? num_pieces + : sizes_to_pieces[length_left_rectangle * root_width + + width_left_rectangle]; + + const int it_is_piece2 = + parent_ids.contains(i + 1) + ? num_pieces + : sizes_to_pieces[length_rigth_rectangle * root_width + + width_right_rectangle]; + + CHECK_EQ(it_is_piece1, collector->Value(0, is_end_piece[i])); + CHECK_EQ(it_is_piece2, collector->Value(0, is_end_piece[i + 1])); + + // Check that all 0-cuts (both rectangles are 0x0) are grouped together. + CHECK_LE(guillotine_cut, is_a_x_guillotine_cut || is_a_y_guillotine_cut); + guillotine_cut |= is_a_x_guillotine_cut || is_a_y_guillotine_cut; + } +} + +} // namespace + +void ConstrainedGuillotineCutting::PrintSolution() const { + CHECK(solved_); + + absl::PrintF("Maximum value: %d\n", maximum_value_); + absl::PrintF("Main rectangle 0 sizes: %dx%d\n", data_->root_length(), + data_->root_width()); + for (int i = 1; i < solution_.size(); ++i) { + if (i % 2 == 1) { + absl::PrintF("\nRectangle %d was cut in: \n", solution_[i].parent_index); + } + absl::PrintF("Rectangle %d sizes: %dx%d\n", i, solution_[i].length, + solution_[i].width); + } +} + +void ConstrainedGuillotineCutting::Solve(absl::Duration time_limit) { + const std::vector& pieces = + data_->pieces(); + + // Depending on the size of a rectangle, it represents the index of + // the piece to which it corresponds. If it does not correspond to + // any piece, than it will remain pieces.size(). + std::vector sizes_to_pieces; + + // Depending on the length(width) of a rectangle, it will be 1 if + // there exists a piece that has that length(width). + std::vector piece_length; + std::vector piece_width; + + int maximum_value; + int maximum_elements; + SetInitialElements(*data_, &sizes_to_pieces, &piece_length, &piece_width, + &maximum_value, &maximum_elements); + + // For every pair of rectangles the index of the rectangle + // that the rectangles were cut of. + std::vector parent_index; + + // sizes of the rectangles + std::vector rectangle_length; + std::vector rectangle_width; + SetRectanglesVariablesAndAddConstraints( + piece_length, piece_width, maximum_elements, data_->root_length(), + data_->root_width(), &parent_index, &rectangle_length, &rectangle_width, + &solver_); + + // Contains the piece that this rectangle equals to if it is + // an end piece (it was not cut). + std::vector is_end_piece; + // For every piece it is true if the corresponding rectangle + // was cut. + std::vector was_cut; + IntVar* const value = solver_.MakeIntVar(0, maximum_value); + AddAdditionalConstraints(parent_index, rectangle_length, rectangle_width, + sizes_to_pieces, *data_, maximum_elements, + &is_end_piece, &was_cut, value, &solver_); + // Objective: maximize the value of the end pieces. + OptimizeVar* const objective_value = solver_.MakeMaximize(value, 1); + + DecisionBuilder* const db = CreateDecisionBuilder( + parent_index, rectangle_length, rectangle_width, was_cut, &solver_); + std::vector monitors; + + SolutionCollector* const collector = solver_.MakeLastSolutionCollector(); + collector->Add(parent_index); + collector->Add(rectangle_length); + collector->Add(rectangle_width); + collector->Add(is_end_piece); + collector->Add(value); + monitors.push_back(collector); + + CreateAdditionalMonitors(time_limit, &monitors, objective_value, &solver_); + + const int64_t start_time = solver_.wall_time(); + solver_.Solve(db, monitors); + const int64_t end_time = solver_.wall_time(); + + LOG(INFO) << "The process took: " << (end_time - start_time) / 1000.0 + << " seconds."; + + if (collector->solution_count()) { + ValidateSolution(pieces.size(), data_->root_width(), parent_index, + rectangle_length, rectangle_width, is_end_piece, + sizes_to_pieces, collector); + + solved_ = true; + FillSolution(parent_index, rectangle_length, rectangle_width, collector, + value, &maximum_value_, &solution_); + } +} + +} // namespace operations_research diff --git a/examples/cpp/cgc.h b/examples/cpp/cgc.h new file mode 100644 index 00000000000..63faed008cb --- /dev/null +++ b/examples/cpp/cgc.h @@ -0,0 +1,91 @@ +// 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. + +// Two-Dimensional Constrained Guillotine Cutting +// +// This file contains a solver for the Two-Dimensional Constrained +// Guillotine Cutting Problem. The problem requires cutting a plane +// rectangle into smaller rectangular pieces of given sizes and values +// in order to maximize the sum of the values of the cut pieces in which +// there is a constraint on the maximum number of each type of piece that +// is to be produced and all cuts go from one edge of the rectangle to be +// cut to the opposite edge. +// +// If cgc_time_limit_in_ms is defined, it provides the best value +// achieved in that amount of time. +// +// Example usage: +// +// std::unique_ptr +// data(new operations_research::ConstrainedGuillotineCuttingData()); +// data->LoadFromFile(file_path); +// operations_research::ConstrainedGuillotineCutting cgc(std::move(data)); +// cgc.Solve(absl::Milliseconds(absl::GetFlag(FLAGS_time_limit_in_ms))); +// if (cgc.Solved()) { +// cgc.PrintSolution(); +// } + +#ifndef ORTOOLS_EXAMPLES_CGC_H_ +#define ORTOOLS_EXAMPLES_CGC_H_ + +#include +#include +#include +#include +#include + +#include "examples/cpp/cgc_data.h" +#include "ortools/constraint_solver/constraint_solver.h" + +namespace operations_research { + +class ConstrainedGuillotineCutting { + public: + struct CutRectangle { + CutRectangle(int parent_index, int length, int width) + : parent_index(parent_index), length(length), width(width) {} + + int parent_index; + int length; + int width; + }; + + explicit ConstrainedGuillotineCutting( + std::unique_ptr data) + : data_(std::move(data)), + solver_("ConstrainedGuillotineCutting"), + solved_(false), + maximum_value_(0) {} + + int MaximumValue() const { + DCHECK(solved_); + return maximum_value_; + } + bool Solved() const { return solved_; } + + void PrintSolution() const; + void Solve(absl::Duration time_limit); + + private: + // Contains the problem parameters. + std::unique_ptr data_; + Solver solver_; + + bool solved_; + int maximum_value_; + std::vector solution_; +}; + +} // namespace operations_research + +#endif // ORTOOLS_EXAMPLES_CGC_H_ diff --git a/examples/cpp/cgc_data.h b/examples/cpp/cgc_data.h new file mode 100644 index 00000000000..34cb86e4e49 --- /dev/null +++ b/examples/cpp/cgc_data.h @@ -0,0 +1,70 @@ +// 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. + +// Two-Dimensional Constrained Guillotine Cutting +// +// The file contains code to load the problem, in the format detailed below. +// +// Input (on different lines): +// - number of pieces +// - length and width for the plane rectangle +// - for each piece (one line for every piece): +// - length +// - width +// - maximum number of pieces of that type that can be cut +// - value of the piece +// +// For more details and sample input (and format) see: +// - http://people.brunel.ac.uk/~mastjjb/jeb/orlib/cgcutinfo.html +// - //ortools/examples/testdata/cgc contains examples +// of input files. + +#ifndef ORTOOLS_EXAMPLES_CGC_DATA_H_ +#define ORTOOLS_EXAMPLES_CGC_DATA_H_ + +#include +#include + +namespace operations_research { + +class ConstrainedGuillotineCuttingData { + public: + // Each rectangular piece from the input is represented + // as an instance of this structure. + struct Piece { + int length; + int width; + int max_appearances; + int value; + }; + + ConstrainedGuillotineCuttingData() : root_length_(0), root_width_(0) {} + + bool LoadFromFile(const std::string& input_file); + + // Accessors for problem specification data + int root_length() const { return root_length_; } + int root_width() const { return root_width_; } + const std::vector& pieces() const { return pieces_; } + + private: + // main rectangle size + int root_length_; + int root_width_; + + std::vector pieces_; +}; + +} // namespace operations_research + +#endif // ORTOOLS_EXAMPLES_CGC_DATA_H_ diff --git a/examples/cpp/cgc_main.cc b/examples/cpp/cgc_main.cc new file mode 100644 index 00000000000..8ab189e3aaa --- /dev/null +++ b/examples/cpp/cgc_main.cc @@ -0,0 +1,81 @@ +// 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. + +// This file implements the main function for the Two-Dimensional +// Constrained Guillotine Cutting solver. It reads the problem +// specification from an input file specified via command-line flags, +// and prints the solution to standard output. +// +// Example usage: +// ./cgc_main --input_file=testdata/cgc/my_input_file.in +// Other examples of input files in testdata/cgc/. + +#include +#include +#include + +#include "absl/flags/flag.h" +#include "absl/strings/str_format.h" +#include "absl/time/time.h" +#include "examples/cpp/cgc.h" +#include "examples/cpp/cgc_data.h" +#include "ortools/base/init_google.h" +#include "ortools/base/logging.h" + +ABSL_FLAG(std::string, input_file, "", "Input data file"); +ABSL_FLAG(int, time_limit_in_ms, 0, + "Time limit in milliseconds. 0 means no time limit. " + "If different, the solver will provide the best solution " + "that was found in that amount of time."); +ABSL_FLAG(bool, print_maximum_value, false, + "If true, it prints the maximum value found."); +ABSL_FLAG(bool, print_solution, false, + "If true, it prints the maximum value and the cutting pattern."); + +using operations_research::ConstrainedGuillotineCutting; +using operations_research::ConstrainedGuillotineCuttingData; + +int main(int argc, char** argv) { + InitGoogle(argv[0], &argc, &argv, true); + + if (absl::GetFlag(FLAGS_input_file).empty()) { + LOG(QFATAL) << "Please supply an input file with --input_file="; + } + LOG(INFO) << "Processing file " << absl::GetFlag(FLAGS_input_file); + + auto data = std::make_unique(); + + if (!data->LoadFromFile(absl::GetFlag(FLAGS_input_file))) { + LOG(QFATAL) << "Input file " << absl::GetFlag(FLAGS_input_file) + << " was not loaded."; + } + + ConstrainedGuillotineCutting cgc(std::move(data)); + const absl::Duration time_limit = + absl::GetFlag(FLAGS_time_limit_in_ms) == 0 + ? absl::InfiniteDuration() + : absl::Milliseconds(absl::GetFlag(FLAGS_time_limit_in_ms)); + cgc.Solve(time_limit); + + if (cgc.Solved()) { + if (absl::GetFlag(FLAGS_print_solution)) { + cgc.PrintSolution(); + } else if (absl::GetFlag(FLAGS_print_maximum_value)) { + absl::PrintF("%d", cgc.MaximumValue()); + } else { + LOG(INFO) << "The maximum value found is: " << cgc.MaximumValue(); + } + } else { + absl::PrintF("There was no solution found in %v ms.\n", time_limit); + } +} diff --git a/examples/cpp/cgc_test_solution.bintest b/examples/cpp/cgc_test_solution.bintest new file mode 100644 index 00000000000..25189556b48 --- /dev/null +++ b/examples/cpp/cgc_test_solution.bintest @@ -0,0 +1,24 @@ +# Tests the Two-Dimensional Constrained Guillotine Cutting solver +# by loading example input files from the testdata directory. + +# The following tests are too big and don't converge within a second so we don't +# check for a particular solution. Instead we make sure that the value is +# greater than zero. + +RUN: $(cgc_main) --input_file=$(1.in) --time_limit_in_ms=1000 --print_maximum_value=true +CHECK: "@num(>0)" + +RUN: $(cgc_main) --input_file=$(2.in) --time_limit_in_ms=1000 --print_maximum_value=true +CHECK: "@num(>0)" + +RUN: $(cgc_main) --input_file=$(3.in) --time_limit_in_ms=1000 --print_maximum_value=true +CHECK: "@num(>0)" + +RUN: $(cgc_main) --input_file=$(cgcut1.in) --time_limit_in_ms=1000 --print_maximum_value=true +CHECK: "@num(>0)" + +RUN: $(cgc_main) --input_file=$(cgcut2.in) --time_limit_in_ms=1000 --print_maximum_value=true +CHECK: "@num(>0)" + +RUN: $(cgc_main) --input_file=$(cgcut3.in) --time_limit_in_ms=1000 --print_maximum_value=true +CHECK: "@num(>0)" diff --git a/examples/cpp/constraint_programming_cp_test.bintest b/examples/cpp/constraint_programming_cp_test.bintest new file mode 100644 index 00000000000..50a49955fa5 --- /dev/null +++ b/examples/cpp/constraint_programming_cp_test.bintest @@ -0,0 +1 @@ +RUN: $(constraint_programming_cp) diff --git a/examples/cpp/costas_array_sat_model1_test.bintest b/examples/cpp/costas_array_sat_model1_test.bintest new file mode 100644 index 00000000000..10978aa7c10 --- /dev/null +++ b/examples/cpp/costas_array_sat_model1_test.bintest @@ -0,0 +1 @@ +RUN: $(costas_array_sat) --minsize=6 --maxsize=6 --model=1 diff --git a/examples/cpp/costas_array_sat_model2_test.bintest b/examples/cpp/costas_array_sat_model2_test.bintest new file mode 100644 index 00000000000..279cbe8b28f --- /dev/null +++ b/examples/cpp/costas_array_sat_model2_test.bintest @@ -0,0 +1 @@ +RUN: $(costas_array_sat) --minsize=6 --maxsize=6 --model=2 diff --git a/examples/cpp/costas_array_sat_model3_test.bintest b/examples/cpp/costas_array_sat_model3_test.bintest new file mode 100644 index 00000000000..45de6bb2807 --- /dev/null +++ b/examples/cpp/costas_array_sat_model3_test.bintest @@ -0,0 +1 @@ +RUN: $(costas_array_sat) --minsize=6 --maxsize=6 --model=3 diff --git a/examples/cpp/course_scheduling.proto b/examples/cpp/course_scheduling.proto deleted file mode 100644 index 167c83523c4..00000000000 --- a/examples/cpp/course_scheduling.proto +++ /dev/null @@ -1,188 +0,0 @@ -// 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. - -syntax = "proto3"; - -package operations_research; - -// Information required to create a schedule for a school system. -message CourseSchedulingModel { - // Schedule name, used only for logging purposes. - string display_name = 1; - - // The number of days in a schedule rotation. If the school system uses a - // block schedule, this value should be 1. - int32 days_count = 2; - - // The number of time slots each day in a schedule rotation. If the school - // system uses a block schedule, this value is the number of blocks. - int32 daily_time_slot_count = 3; - - // List of courses that need to be scheduled. - repeated Course courses = 4; - - // List of teachers. - repeated Teacher teachers = 5; - - // List of students that need to be assigned to these courses. - repeated Student students = 6; - - // List of rooms that the courses can be assigned to. - repeated Room rooms = 7; -} - -// Holds the solution to the course scheduling problem. -message CourseSchedulingResult { - // Human readable message about the solver or given model. - string message = 1; - - // Status of the solver. - CourseSchedulingResultStatus solver_status = 2; - - // List of the time slot and room assignments for each section of a course. - repeated ClassAssignment class_assignments = 3; - - // List of course and section assignments for each student. - repeated StudentAssignment student_assignments = 4; -} - -message ClassAssignment { - // Index of the course in the CourseSchedulingModel. - int32 course_index = 1; - - // Specific section of the course in the CourseSchedulingModel. - int32 section_number = 2; - - // Time slots that this class has been assigned to in the - // CourseSchedulingModel. - repeated int32 time_slots = 3; - - // Indices of the rooms that the class is assigned to in the - // CourseSchedulingModel. If this is not empty, then the number of indices - // must match the number of time slots. - repeated int32 room_indices = 4; -} - -message StudentAssignment { - // Index of the student in the CourseSchedulingModel. - int32 student_index = 1; - - // Course indices in the CourseSchedulingModel that this student has been - // assigned to. The number of indices must match the number of section - // indices. - repeated int32 course_indices = 2; - - // Section indices for each Course in the CourseSchedulingModel this this - // student has been assigned to. The number of indices must match the number - // of course indices. - repeated int32 section_indices = 3; -} - -message Course { - // Course name, used only for logging purposes. - string display_name = 1; - - // The number of times each section of this course needs to meet during a - // schedule rotation. Each section of the course meets no more than once a - // day. If the school system uses a block schedule, then this value should - // be 1. - int32 meetings_count = 2; - - // The maximum number of students for this course. This value can be equal to - // +Infinity to encode a course has no maximum capacity. - int32 max_capacity = 3; - - // The minimum number of students for this course. - int32 min_capacity = 4; - - // The number of consecutive time slots that each section of this course needs - // to be scheduled for. This value can only be 1 or 2. If the value is 2, then - // 2 consecutive time slots in a day counts as 1 meeting time for the section. - int32 consecutive_slots_count = 5; - - // List of indices for the teachers of this course. We are assuming that each - // teacher teaches separately. Must have the same number of elements as the - // number of sections list. - repeated int32 teacher_indices = 6; - - // The number of sections each teacher teaches of this course. Must have the - // same number of elements as the teacher index list. - repeated int32 teacher_section_counts = 7; - - // List of the possible rooms that this course can be assigned to. This can - // be empty. - repeated int32 room_indices = 8; -} - -message Teacher { - // Teacher name, used only for logging purposes. - string display_name = 1; - - // List of time slots that the teacher cannot be scheduled for. These time - // slot values index to the accumulative number of time slots starting at 0. - // For example, if a schedule rotation has 5 days and 8 time slots per day, - // and a teacher cannot be scheduled for the last time slot of the fourth - // day, the number here would be 31. - repeated int32 restricted_time_slots = 2; -} - -message Student { - // Student name, used only for logging purposes. - string display_name = 1; - - // List of course indices that the student needs to be enrolled in. - repeated int32 course_indices = 2; -} - -message Room { - // Room name, used only for logging purposes. - string display_name = 1; - - // Maximum number of students that can fit into this room. - int32 capacity = 2; -} - -// Status returned by the solver. -enum CourseSchedulingResultStatus { - COURSE_SCHEDULING_RESULT_STATUS_UNSPECIFIED = 0; - - // The solver had enough time to find some solution that satisfies all - // constraints, but it did not prove optimality (which means it may or may - // not have reached the optimal). - // - // This can happen for large LP models (linear programming), and is a frequent - // response for time-limited MIPs (mixed integer programming). This is also - // what the CP (constraint programming) solver will return if there is no - // objective specified. - SOLVER_FEASIBLE = 1; - - // The solver found the proven optimal solution. - SOLVER_OPTIMAL = 2; - - // The model does not have any solution, according to the solver (which - // "proved" it, with the caveat that numerical proofs aren't actual proofs), - // or based on trivial considerations (eg. a variable whose lower bound is - // strictly greater than its upper bound). - SOLVER_INFEASIBLE = 3; - - // Model errors. These are always deterministic and repeatable. - // They should be accompanied with a string description of the error. - SOLVER_MODEL_INVALID = 4; - - // The model has not been solved in the given time or the solver was not able - // to solve the model given. - SOLVER_NOT_SOLVED = 5; - - // An error (either numerical or from a bug in the code) occurred. - ABNORMAL = 6; -} diff --git a/examples/cpp/cryptarithm_sat_test.bintest b/examples/cpp/cryptarithm_sat_test.bintest new file mode 100644 index 00000000000..b919bcc24c7 --- /dev/null +++ b/examples/cpp/cryptarithm_sat_test.bintest @@ -0,0 +1 @@ +RUN: $(cryptarithm_sat) diff --git a/examples/cpp/dimacs_assignment.cc b/examples/cpp/dimacs_assignment.cc index bcf43712abc..b8f1917f8c9 100644 --- a/examples/cpp/dimacs_assignment.cc +++ b/examples/cpp/dimacs_assignment.cc @@ -20,7 +20,7 @@ #include "absl/container/flat_hash_map.h" #include "absl/flags/flag.h" -#include "absl/strings/str_format.h" +#include "absl/strings/string_view.h" #include "examples/cpp/parse_dimacs_assignment.h" #include "examples/cpp/print_dimacs_assignment.h" #include "ortools/algorithms/hungarian.h" @@ -156,25 +156,18 @@ int SolveDimacsAssignment(int argc, char* argv[]) { } } // namespace operations_research -static const char* const kUsageTemplate = "usage: %s "; - using ::operations_research::ArcIndex; using ::operations_research::NodeIndex; using ::operations_research::SolveDimacsAssignment; int main(int argc, char* argv[]) { - std::string usage; - if (argc < 1) { - usage = absl::StrFormat(kUsageTemplate, "solve_dimacs_assignment"); - } else { - usage = absl::StrFormat(kUsageTemplate, argv[0]); - } - InitGoogle(usage.c_str(), &argc, &argv, true); + InitGoogle(argv[0], &argc, &argv, true); if (argc < 2) { - LOG(FATAL) << usage; + LOG(FATAL) << "Missing input file."; } + absl::SetStderrThreshold(absl::LogSeverityAtLeast::kInfo); if (absl::GetFlag(FLAGS_assignment_static_graph)) { return SolveDimacsAssignment<::util::StaticGraph>( argc, argv); diff --git a/examples/cpp/dimacs_assignment_max_cost_test.bintest b/examples/cpp/dimacs_assignment_max_cost_test.bintest new file mode 100644 index 00000000000..3352b7ad851 --- /dev/null +++ b/examples/cpp/dimacs_assignment_max_cost_test.bintest @@ -0,0 +1,2 @@ +RUN: $(dimacs_assignment) $(dimacs_example.txt) --assignment_maximize_cost 2>&1 +CHECK: "Cost of optimum assignment: -110" diff --git a/examples/cpp/dimacs_assignment_min_cost_test.bintest b/examples/cpp/dimacs_assignment_min_cost_test.bintest new file mode 100644 index 00000000000..0a816928ce8 --- /dev/null +++ b/examples/cpp/dimacs_assignment_min_cost_test.bintest @@ -0,0 +1,2 @@ +RUN: $(dimacs_assignment) $(dimacs_example.txt) 2>&1 +CHECK: "Cost of optimum assignment: 84" diff --git a/examples/cpp/dobble_ls_test.bintest b/examples/cpp/dobble_ls_test.bintest new file mode 100644 index 00000000000..65ba1b023b6 --- /dev/null +++ b/examples/cpp/dobble_ls_test.bintest @@ -0,0 +1 @@ +RUN: $(dobble_ls) --time_limit_in_ms=10000 diff --git a/examples/cpp/fap_parser.cc b/examples/cpp/fap_parser.cc index 4eb1a8f4237..5dc61e2b76e 100644 --- a/examples/cpp/fap_parser.cc +++ b/examples/cpp/fap_parser.cc @@ -21,6 +21,7 @@ #include "absl/strings/numbers.h" #include "absl/strings/str_split.h" #include "absl/strings/string_view.h" +#include "absl/types/span.h" #include "ortools/base/helpers.h" #include "ortools/base/map_util.h" @@ -94,7 +95,7 @@ void DomainParser::Parse() { } if (!domain.empty()) { - gtl::InsertOrUpdate(&domains_, key, domain); + domains_.insert_or_assign(key, domain); } } } @@ -198,7 +199,7 @@ void ParametersParser::Parse() { } // TODO(user): Make FindComponents linear instead of quadratic. -void FindComponents(const std::vector& constraints, +void FindComponents(absl::Span constraints, const absl::btree_map& variables, const int maximum_variable_id, absl::flat_hash_map* components) { @@ -216,20 +217,20 @@ void FindComponents(const std::vector& constraints, // Create a new one. FapComponent component; const int component_index = constraint_index; - gtl::InsertOrUpdate(&(component.variables), variable_id1, variable1); - gtl::InsertOrUpdate(&(component.variables), variable_id2, variable2); + (component.variables).insert_or_assign(variable_id1, variable1); + (component.variables).insert_or_assign(variable_id2, variable2); in_component[variable_id1] = component_index; in_component[variable_id2] = component_index; component.constraints.push_back(constraint); - gtl::InsertOrUpdate(components, component_index, component); + components->insert_or_assign(component_index, component); } else if (in_component[variable_id1] >= 0 && in_component[variable_id2] < 0) { // If variable1 belongs to an existing component, variable2 should // also be included in the same component. const int component_index = in_component[variable_id1]; CHECK(components->contains(component_index)); - gtl::InsertOrUpdate(&((*components)[component_index].variables), - variable_id2, variable2); + ((*components)[component_index].variables) + .insert_or_assign(variable_id2, variable2); in_component[variable_id2] = component_index; (*components)[component_index].constraints.push_back(constraint); } else if (in_component[variable_id1] < 0 && @@ -238,8 +239,8 @@ void FindComponents(const std::vector& constraints, // also be included in the same component. const int component_index = in_component[variable_id2]; CHECK(components->contains(component_index)); - gtl::InsertOrUpdate(&((*components)[component_index].variables), - variable_id1, variable1); + ((*components)[component_index].variables) + .insert_or_assign(variable_id1, variable1); in_component[variable_id1] = component_index; (*components)[component_index].constraints.push_back(constraint); } else { diff --git a/examples/cpp/fap_parser.h b/examples/cpp/fap_parser.h index 66e9f1706e9..acdb63e3b06 100644 --- a/examples/cpp/fap_parser.h +++ b/examples/cpp/fap_parser.h @@ -23,6 +23,7 @@ #include "absl/container/btree_map.h" #include "absl/container/flat_hash_map.h" #include "absl/strings/string_view.h" +#include "absl/types/span.h" namespace operations_research { @@ -214,7 +215,7 @@ class ParametersParser { }; // Function that finds the disjoint sub-graphs of the graph of the instance. -void FindComponents(const std::vector& constraints, +void FindComponents(absl::Span constraints, const absl::btree_map& variables, int maximum_variable_id, absl::flat_hash_map* components); diff --git a/examples/cpp/flow_api_test.bintest b/examples/cpp/flow_api_test.bintest new file mode 100644 index 00000000000..6bca712df4c --- /dev/null +++ b/examples/cpp/flow_api_test.bintest @@ -0,0 +1 @@ +RUN: $(flow_api) diff --git a/examples/cpp/golomb_sat_test.bintest b/examples/cpp/golomb_sat_test.bintest new file mode 100644 index 00000000000..7fd6596d7a1 --- /dev/null +++ b/examples/cpp/golomb_sat_test.bintest @@ -0,0 +1 @@ +RUN: $(golomb_sat) --size 5 diff --git a/examples/cpp/integer_programming_test.bintest b/examples/cpp/integer_programming_test.bintest new file mode 100644 index 00000000000..bad463547b3 --- /dev/null +++ b/examples/cpp/integer_programming_test.bintest @@ -0,0 +1 @@ +RUN: $(integer_programming) diff --git a/examples/cpp/jobshop_sat_ft06.bintest b/examples/cpp/jobshop_sat_ft06.bintest new file mode 100644 index 00000000000..366a359b4ee --- /dev/null +++ b/examples/cpp/jobshop_sat_ft06.bintest @@ -0,0 +1 @@ +RUN: $(jobshop_sat) --input $(ft06) diff --git a/examples/cpp/knapsack_2d_sat_class01_instance2_test.bintest b/examples/cpp/knapsack_2d_sat_class01_instance2_test.bintest new file mode 100644 index 00000000000..a7695406e71 --- /dev/null +++ b/examples/cpp/knapsack_2d_sat_class01_instance2_test.bintest @@ -0,0 +1 @@ +RUN: $(knapsack_2d_sat) --input $(Class_01.2bp) --instance 2 diff --git a/examples/cpp/linear_assignment_api_test.bintest b/examples/cpp/linear_assignment_api_test.bintest new file mode 100644 index 00000000000..3cf36e00c4e --- /dev/null +++ b/examples/cpp/linear_assignment_api_test.bintest @@ -0,0 +1 @@ +RUN: $(linear_assignment_api) diff --git a/examples/cpp/linear_programming_test.bintest b/examples/cpp/linear_programming_test.bintest new file mode 100644 index 00000000000..dc7d4189d75 --- /dev/null +++ b/examples/cpp/linear_programming_test.bintest @@ -0,0 +1 @@ +RUN: $(linear_programming) diff --git a/examples/cpp/linear_solver_protocol_buffers_test.bintest b/examples/cpp/linear_solver_protocol_buffers_test.bintest new file mode 100644 index 00000000000..a8997d98780 --- /dev/null +++ b/examples/cpp/linear_solver_protocol_buffers_test.bintest @@ -0,0 +1 @@ +RUN: $(linear_solver_protocol_buffers) diff --git a/examples/cpp/magic_sequence_sat_test.bintest b/examples/cpp/magic_sequence_sat_test.bintest new file mode 100644 index 00000000000..0e9b6973889 --- /dev/null +++ b/examples/cpp/magic_sequence_sat_test.bintest @@ -0,0 +1 @@ +RUN: $(magic_sequence_sat) diff --git a/examples/cpp/magic_square_sat_test.bintest b/examples/cpp/magic_square_sat_test.bintest new file mode 100644 index 00000000000..4830bec5343 --- /dev/null +++ b/examples/cpp/magic_square_sat_test.bintest @@ -0,0 +1 @@ +RUN: $(magic_square_sat) diff --git a/examples/cpp/max_flow_test.bintest b/examples/cpp/max_flow_test.bintest new file mode 100644 index 00000000000..530459b6823 --- /dev/null +++ b/examples/cpp/max_flow_test.bintest @@ -0,0 +1 @@ +RUN: $(max_flow) diff --git a/examples/cpp/min_cost_flow_test.bintest b/examples/cpp/min_cost_flow_test.bintest new file mode 100644 index 00000000000..322e309acf8 --- /dev/null +++ b/examples/cpp/min_cost_flow_test.bintest @@ -0,0 +1 @@ +RUN: $(min_cost_flow) diff --git a/examples/cpp/mps_driver_test.bintest b/examples/cpp/mps_driver_test.bintest new file mode 100644 index 00000000000..37e2f8c20de --- /dev/null +++ b/examples/cpp/mps_driver_test.bintest @@ -0,0 +1 @@ +RUN: $(mps_driver) --input $(maximization.mps) diff --git a/examples/cpp/multi_knapsack_sat_test.bintest b/examples/cpp/multi_knapsack_sat_test.bintest new file mode 100644 index 00000000000..1a51c1921b8 --- /dev/null +++ b/examples/cpp/multi_knapsack_sat_test.bintest @@ -0,0 +1 @@ +RUN: $(multi_knapsack_sat) diff --git a/examples/cpp/network_routing_sat_test.bintest b/examples/cpp/network_routing_sat_test.bintest new file mode 100644 index 00000000000..37387090b09 --- /dev/null +++ b/examples/cpp/network_routing_sat_test.bintest @@ -0,0 +1 @@ +RUN: $(network_routing_sat) --clients=10 --backbones=5 --demands=10 --traffic_min=5 --traffic_max=10 --min_client_degree=2 --max_client_degree=5 --min_backbone_degree=3 --max_backbone_degree=5 --max_capacity=20 --fixed_charge_cost=10 diff --git a/examples/cpp/nqueens_test.bintest b/examples/cpp/nqueens_test.bintest new file mode 100644 index 00000000000..d0c8332ca3b --- /dev/null +++ b/examples/cpp/nqueens_test.bintest @@ -0,0 +1 @@ +RUN: $(nqueens) diff --git a/examples/cpp/pdptw.cc b/examples/cpp/pdptw.cc index fde4d38ae8b..85fd5cbc5c1 100644 --- a/examples/cpp/pdptw.cc +++ b/examples/cpp/pdptw.cc @@ -127,17 +127,18 @@ double ComputeScalingFactorFromCallback(const C& callback, int size) { return max_scaled_distance / max_value; } -void SetupModel(const LiLimParser& parser, const RoutingIndexManager& manager, - RoutingModel* model, +void SetupModel(const routing::LiLimParser& parser, + const RoutingIndexManager& manager, RoutingModel* model, RoutingSearchParameters* search_parameters) { const int64_t kPenalty = 100000000; const int64_t kFixedCost = 100000; const int num_nodes = parser.NumberOfNodes(); const int64_t horizon = - absl::c_max_element( - parser.time_windows(), - [](const SimpleTimeWindow& a, - const SimpleTimeWindow& b) { return a.end < b.end; }) + absl::c_max_element(parser.time_windows(), + [](const routing::SimpleTimeWindow& a, + const routing::SimpleTimeWindow& b) { + return a.end < b.end; + }) ->end; const double scaling_factor = ComputeScalingFactorFromCallback( [&parser](int64_t i, int64_t j) -> double { @@ -199,7 +200,8 @@ void SetupModel(const LiLimParser& parser, const RoutingIndexManager& manager, model->AddPickupAndDelivery(index, delivery_index); } IntVar* const cumul = time_dimension.CumulVar(index); - const SimpleTimeWindow& window = parser.time_windows()[node]; + const routing::SimpleTimeWindow& window = + parser.time_windows()[node]; cumul->SetMin(MathUtil::Round(scaling_factor * window.start)); cumul->SetMax(MathUtil::Round(scaling_factor * window.end)); } @@ -244,7 +246,8 @@ void SetupModel(const LiLimParser& parser, const RoutingIndexManager& manager, std::string VerboseOutput(const RoutingModel& model, const RoutingIndexManager& manager, const Assignment& assignment, - const LiLimParser& parser, double scaling_factor) { + const routing::LiLimParser& parser, + double scaling_factor) { std::string output; const RoutingDimension& time_dimension = model.GetDimensionOrDie("time"); const RoutingDimension& load_dimension = model.GetDimensionOrDie("demand"); @@ -298,7 +301,7 @@ std::string VerboseOutput(const RoutingModel& model, bool LoadAndSolve(absl::string_view pdp_file, const RoutingModelParameters& model_parameters, RoutingSearchParameters& search_parameters) { - LiLimParser parser; + routing::LiLimParser parser; if (!parser.LoadFile(pdp_file)) { return false; } diff --git a/examples/cpp/pdptw_non_homogenous_fleet_test.bintest b/examples/cpp/pdptw_non_homogenous_fleet_test.bintest new file mode 100644 index 00000000000..96762b4ccf0 --- /dev/null +++ b/examples/cpp/pdptw_non_homogenous_fleet_test.bintest @@ -0,0 +1,2 @@ +RUN: $(pdptw) --pdp_file=$(lc102.txt) --reduce_vehicle_cost_model=false --routing_search_parameters=first_solution_strategy:BEST_INSERTION 2>&1 +CHECK: "Cost: 1000828.936870" diff --git a/examples/cpp/pdptw_test.bintest b/examples/cpp/pdptw_test.bintest new file mode 100644 index 00000000000..d3a23a46c78 --- /dev/null +++ b/examples/cpp/pdptw_test.bintest @@ -0,0 +1,2 @@ +RUN: $(pdptw) --pdp_file=$(lc102.txt) 2>&1 +CHECK: "Cost: 1000828.936870" diff --git a/examples/cpp/pdptw_with_alternatives.cc b/examples/cpp/pdptw_with_alternatives.cc new file mode 100644 index 00000000000..1d485ede2cd --- /dev/null +++ b/examples/cpp/pdptw_with_alternatives.cc @@ -0,0 +1,377 @@ +// 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. + +// Pickup and Delivery Problem with Time Windows and Alternatives. +// This is a variant of the mode in pdptw.cc (see that file for more details +// on pickup and delivery models). In this model both pickups and deliveries +// have alternative locations, of which one of each has to be selected. As in +// the standard pickup and delivery problem, pickups must happen before +// deliveries and must be on the same route. + +#include +#include +#include +#include +#include +#include + +#include "absl/base/log_severity.h" +#include "absl/flags/flag.h" +#include "absl/functional/bind_front.h" +#include "absl/log/check.h" +#include "absl/log/globals.h" +#include "absl/log/log.h" +#include "absl/strings/numbers.h" +#include "absl/strings/str_format.h" +#include "absl/strings/str_split.h" +#include "absl/strings/string_view.h" +#include "google/protobuf/text_format.h" +#include "ortools/base/helpers.h" +#include "ortools/base/init_google.h" +#include "ortools/base/options.h" +#include "ortools/constraint_solver/constraint_solver.h" +#include "ortools/routing/index_manager.h" +#include "ortools/routing/parameters.h" +#include "ortools/routing/parameters.pb.h" +#include "ortools/routing/routing.h" + +ABSL_FLAG(std::string, pdp_file, "", + "File containing the Pickup and Delivery Problem to solve."); +ABSL_FLAG(int, pdp_force_vehicles, 0, + "Force the number of vehicles used (maximum number of routes."); +ABSL_FLAG(bool, reduce_vehicle_cost_model, true, + "Overrides the homonymous field of " + "DefaultRoutingModelParameters()."); +ABSL_FLAG(std::string, routing_search_parameters, + "first_solution_strategy:ALL_UNPERFORMED " + "local_search_operators { use_node_pair_swap_active:BOOL_FALSE }", + "Text proto RoutingSearchParameters (possibly partial) that will " + "override the DefaultRoutingSearchParameters()"); + +using ::absl::bind_front; + +namespace operations_research::routing { + +// Scaling factor used to scale up distances, allowing a bit more precision +// from Euclidean distances. +const int64_t kScalingFactor = 1000; + +// Vector of (x,y) node coordinates, *unscaled*, in some imaginary planar, +// metric grid. +typedef std::vector> Coordinates; + +// Returns the scaled Euclidean distance between two nodes, coords holding the +// coordinates of the nodes. +int64_t Travel(const Coordinates* const coords, + RoutingIndexManager::NodeIndex from, + RoutingIndexManager::NodeIndex to) { + DCHECK(coords != nullptr); + const int xd = coords->at(from.value()).first - coords->at(to.value()).first; + const int yd = + coords->at(from.value()).second - coords->at(to.value()).second; + return static_cast(kScalingFactor * + std::sqrt(1.0L * xd * xd + yd * yd)); +} + +// Returns the scaled service time at a given node, service_times holding the +// service times. +int64_t ServiceTime(const std::vector* const service_times, + RoutingIndexManager::NodeIndex node) { + return kScalingFactor * service_times->at(node.value()); +} + +// Returns the scaled (distance plus service time) between two indices, coords +// holding the coordinates of the nodes and service_times holding the service +// times. +// The service time is the time spent to execute a delivery or a pickup. +int64_t TravelPlusServiceTime(const RoutingIndexManager& manager, + const Coordinates* const coords, + const std::vector* const service_times, + int64_t from_index, int64_t to_index) { + const RoutingIndexManager::NodeIndex from = manager.IndexToNode(from_index); + const RoutingIndexManager::NodeIndex to = manager.IndexToNode(to_index); + return ServiceTime(service_times, from) + Travel(coords, from, to); +} + +// Returns the demand (quantity picked up or delivered) of an index, demands +// holds the demand of each node. +int64_t Demand(const RoutingIndexManager& manager, + const std::vector* const demands, int64_t from_index, + int64_t to_index) { + (void)to_index; + return demands->at(manager.IndexToNode(from_index).value()); +} + +// Outputs a solution to the current model in a string. +std::string VerboseOutput(const RoutingModel& routing, + const RoutingIndexManager& manager, + const Assignment& assignment, + const Coordinates& coords, + const std::vector& service_times) { + std::string output; + const RoutingDimension& time_dimension = routing.GetDimensionOrDie("time"); + const RoutingDimension& load_dimension = routing.GetDimensionOrDie("demand"); + for (int i = 0; i < routing.vehicles(); ++i) { + absl::StrAppendFormat(&output, "Vehicle %d: ", i); + int64_t index = routing.Start(i); + if (routing.IsEnd(assignment.Value(routing.NextVar(index)))) { + output.append("empty"); + } else { + while (!routing.IsEnd(index)) { + absl::StrAppendFormat(&output, "%d ", + manager.IndexToNode(index).value()); + const IntVar* vehicle = routing.VehicleVar(index); + absl::StrAppendFormat(&output, "Vehicle(%d) ", + assignment.Value(vehicle)); + const IntVar* arrival = time_dimension.CumulVar(index); + absl::StrAppendFormat(&output, "Time(%d..%d) ", assignment.Min(arrival), + assignment.Max(arrival)); + const IntVar* load = load_dimension.CumulVar(index); + absl::StrAppendFormat(&output, "Load(%d..%d) ", assignment.Min(load), + assignment.Max(load)); + const int64_t next_index = assignment.Value(routing.NextVar(index)); + absl::StrAppendFormat( + &output, "Transit(%d) ", + TravelPlusServiceTime(manager, &coords, &service_times, index, + next_index)); + index = next_index; + } + output.append("Route end "); + const IntVar* vehicle = routing.VehicleVar(index); + absl::StrAppendFormat(&output, "Vehicle(%d) ", assignment.Value(vehicle)); + const IntVar* arrival = time_dimension.CumulVar(index); + absl::StrAppendFormat(&output, "Time(%d..%d) ", assignment.Min(arrival), + assignment.Max(arrival)); + const IntVar* load = load_dimension.CumulVar(index); + absl::StrAppendFormat(&output, "Load(%d..%d) ", assignment.Min(load), + assignment.Max(load)); + } + output.append("\n"); + } + return output; +} + +namespace { +// An inefficient but convenient method to parse a whitespace-separated list +// of integers. Returns true iff the input string was entirely valid and parsed. +bool SafeParseInt64Array(const std::string& str, + std::vector* parsed_int) { + static const char kWhiteSpaces[] = " \t\n\v\f\r"; + parsed_int->clear(); + for (absl::string_view token : + absl::StrSplit(str, absl::ByAnyChar(kWhiteSpaces), absl::SkipEmpty())) { + int value; + if (!absl::SimpleAtoi(token, &value)) return false; + parsed_int->push_back(value); + } + return true; +} +} // namespace + +// Builds and solves a model from a file in the format defined by Li & Lim +// (https://www.sintef.no/projectweb/top/pdptw/li-lim-benchmark/documentation/). +bool LoadAndSolve(const std::string& pdp_file, + const RoutingModelParameters& model_parameters, + const RoutingSearchParameters& search_parameters) { + // Load all the lines of the file in RAM (it shouldn't be too large anyway). + std::vector lines; + { + std::string contents; + CHECK_OK(file::GetContents(pdp_file, &contents, file::Defaults())); + const int64_t kMaxInputFileSize = 1 << 30; // 1GB + if (contents.size() >= kMaxInputFileSize) { + LOG(WARNING) << "Input file '" << pdp_file << "' is too large (>" + << kMaxInputFileSize << " bytes)."; + return false; + } + lines = absl::StrSplit(contents, '\n', absl::SkipEmpty()); + } + // Reading header. + if (lines.empty()) { + LOG(WARNING) << "Empty file: " << pdp_file; + return false; + } + // Parse file header. + std::vector parsed_int; + if (!SafeParseInt64Array(lines[0], &parsed_int) || parsed_int.size() != 3 || + parsed_int[0] < 0 || parsed_int[1] < 0 || parsed_int[2] < 0) { + LOG(WARNING) << "Malformed header: " << lines[0]; + return false; + } + const int num_vehicles = absl::GetFlag(FLAGS_pdp_force_vehicles) > 0 + ? absl::GetFlag(FLAGS_pdp_force_vehicles) + : parsed_int[0]; + const int64_t capacity = parsed_int[1]; + // We do not care about the 'speed' field, in third position. + + // Parse order data. + std::vector customer_ids; + std::vector> coords; + std::vector demands; + std::vector open_times; + std::vector close_times; + std::vector service_times; + std::vector pickups; + std::vector deliveries; + int64_t horizon = 0; + RoutingIndexManager::NodeIndex depot(0); + for (int line_index = 1; line_index < lines.size(); ++line_index) { + if (!SafeParseInt64Array(lines[line_index], &parsed_int) || + parsed_int.size() != 9 || parsed_int[0] < 0 || parsed_int[4] < 0 || + parsed_int[5] < 0 || parsed_int[6] < 0 || parsed_int[7] < 0 || + parsed_int[8] < 0) { + LOG(WARNING) << "Malformed line #" << line_index << ": " + << lines[line_index]; + return false; + } + const int customer_id = parsed_int[0]; + const int x = parsed_int[1]; + const int y = parsed_int[2]; + const int64_t demand = parsed_int[3]; + const int64_t open_time = parsed_int[4]; + const int64_t close_time = parsed_int[5]; + const int64_t service_time = parsed_int[6]; + const int pickup = parsed_int[7]; + const int delivery = parsed_int[8]; + customer_ids.push_back(customer_id); + coords.push_back(std::make_pair(x, y)); + demands.push_back(demand); + open_times.push_back(open_time); + close_times.push_back(close_time); + service_times.push_back(service_time); + pickups.push_back(RoutingIndexManager::NodeIndex(pickup)); + deliveries.push_back(RoutingIndexManager::NodeIndex(delivery)); + if (pickup == 0 && delivery == 0) { + depot = RoutingIndexManager::NodeIndex(pickups.size() - 1); + } + horizon = std::max(horizon, close_time); + } + + // Build pickup and delivery model. + const int num_nodes = customer_ids.size(); + RoutingIndexManager manager(num_nodes, num_vehicles, depot); + RoutingModel routing(manager, model_parameters); + const int vehicle_cost = routing.RegisterTransitCallback( + [&coords, &manager](int64_t i, int64_t j) { + return Travel(const_cast(&coords), + manager.IndexToNode(i), manager.IndexToNode(j)); + }); + routing.SetArcCostEvaluatorOfAllVehicles(vehicle_cost); + routing.AddDimension( + routing.RegisterTransitCallback(absl::bind_front( + TravelPlusServiceTime, manager, + const_cast(&coords), + const_cast*>(&service_times))), + kScalingFactor * horizon, kScalingFactor * horizon, + /*fix_start_cumul_to_zero=*/true, "time"); + const RoutingDimension& time_dimension = routing.GetDimensionOrDie("time"); + Solver* const solver = routing.solver(); + + // Collect pickup and delivery pairs and set time windows. + std::vector> pickup_delivery_pairs; + for (RoutingIndexManager::NodeIndex order(0); order < routing.nodes(); + ++order) { + const int64_t index = manager.NodeToIndex(order); + IntVar* const cumul = time_dimension.CumulVar(index); + cumul->SetMin(kScalingFactor * open_times[order.value()]); + cumul->SetMax(kScalingFactor * close_times[order.value()]); + RoutingIndexManager::NodeIndex delivery = deliveries[order.value()]; + if (pickups[order.value()] == 0 && delivery != 0) { + pickup_delivery_pairs.push_back({index, manager.NodeToIndex(delivery)}); + } + } + + // Build groups of pickup and delivery pairs representing the alternatives of + // pickup and delivery locations for a given shipment, and add the + // corresponding constraints. + const int kGroupSize = 4; + const int64_t kPenalty = 10000000; + // Collecting demands per group computed as the average demand for the group. + std::vector group_demands(demands.size()); + for (int pair_index = 0; pair_index < pickup_delivery_pairs.size();) { + std::vector pickup_indices; + std::vector delivery_indices; + std::vector pickup_vehicle_variables; + std::vector delivery_vehicle_variables; + int64_t demand_sum = 0; + int pair_start = pair_index; + for (int i = 0; i < kGroupSize && pair_index < pickup_delivery_pairs.size(); + ++i, ++pair_index) { + const int64_t pickup = pickup_delivery_pairs[pair_index].first; + const int64_t delivery = pickup_delivery_pairs[pair_index].second; + pickup_indices.push_back(pickup); + delivery_indices.push_back(delivery); + pickup_vehicle_variables.push_back(routing.VehicleVar(pickup)); + delivery_vehicle_variables.push_back(routing.VehicleVar(delivery)); + demand_sum += demands[manager.IndexToNode(pickup).value()]; + } + // Computing demand average. + int64_t demand_avg = demand_sum / (pair_index - pair_start); + for (int i = pair_start; i < pair_index; ++i) { + group_demands[pickup_delivery_pairs[i].first] = demand_avg; + group_demands[pickup_delivery_pairs[i].second] = -demand_avg; + } + // Unperformed pickups or deliveries will have their vehicle variable set + // to -1. Therefore the vehicle performing the performed pickup (resp. the + // performed delivery) is the maximum of the vehicle variables of the + // pickups (resp. deliveries). Using this to ensure the performed pickup + // and delivery are on the same route. + solver->AddConstraint( + solver->MakeEquality(solver->MakeMax(pickup_vehicle_variables), + solver->MakeMax(delivery_vehicle_variables))); + // Only one pickup and one delivery must be performed and notify the solver + // about the pickup and delivery alternatives. + routing.AddPickupAndDeliverySets( + routing.AddDisjunction(pickup_indices, kPenalty), + routing.AddDisjunction(delivery_indices, kPenalty)); + } + // Add demand dimension where the demand corresponds to the average demand + // of the group. + routing.AddDimension( + routing.RegisterTransitCallback(absl::bind_front( + Demand, manager, + const_cast*>(&group_demands))), + 0, capacity, /*fix_start_cumul_to_zero=*/true, "demand"); + + // Solve pickup and delivery problem. + const Assignment* assignment = routing.SolveWithParameters(search_parameters); + LOG(INFO) << routing.solver()->LocalSearchProfile(); + if (nullptr != assignment) { + LOG(INFO) << "Cost: " << assignment->ObjectiveValue(); + LOG(INFO) << VerboseOutput(routing, manager, *assignment, coords, + service_times); + return true; + } + return false; +} + +} // namespace operations_research::routing + +int main(int argc, char** argv) { + absl::SetStderrThreshold(absl::LogSeverityAtLeast::kInfo); + InitGoogle(argv[0], &argc, &argv, true); + // Set up model and search parameters. + operations_research::routing::RoutingModelParameters model_parameters = + operations_research::routing::DefaultRoutingModelParameters(); + model_parameters.set_reduce_vehicle_cost_model( + absl::GetFlag(FLAGS_reduce_vehicle_cost_model)); + operations_research::routing::RoutingSearchParameters search_parameters = + operations_research::routing::DefaultRoutingSearchParameters(); + CHECK(google::protobuf::TextFormat::MergeFromString( + absl::GetFlag(FLAGS_routing_search_parameters), &search_parameters)); + if (!operations_research::routing::LoadAndSolve( + absl::GetFlag(FLAGS_pdp_file), model_parameters, search_parameters)) { + LOG(INFO) << "Error solving " << absl::GetFlag(FLAGS_pdp_file); + } + return 0; +} diff --git a/examples/cpp/pdptw_with_alternatives_non_homogenous_fleet_test.bintest b/examples/cpp/pdptw_with_alternatives_non_homogenous_fleet_test.bintest new file mode 100644 index 00000000000..54406d4f3c1 --- /dev/null +++ b/examples/cpp/pdptw_with_alternatives_non_homogenous_fleet_test.bintest @@ -0,0 +1,2 @@ +RUN: $(pdptw_with_alternatives) --pdp_file=$(lc102.txt) --reduce_vehicle_cost_model=false --routing_search_parameters='first_solution_strategy:BEST_INSERTION local_search_operators { use_node_pair_swap_active:BOOL_FALSE }' 2>&1 +CHECK: "Cost: 362934" diff --git a/examples/cpp/pdptw_with_alternatives_test.bintest b/examples/cpp/pdptw_with_alternatives_test.bintest new file mode 100644 index 00000000000..09fb54c4258 --- /dev/null +++ b/examples/cpp/pdptw_with_alternatives_test.bintest @@ -0,0 +1,2 @@ +RUN: $(pdptw_with_alternatives) --pdp_file=$(lc102.txt) 2>&1 +CHECK: "Cost: 361237" diff --git a/examples/cpp/shift_minimization_sat_test.bintest b/examples/cpp/shift_minimization_sat_test.bintest new file mode 100644 index 00000000000..3694cdf27c3 --- /dev/null +++ b/examples/cpp/shift_minimization_sat_test.bintest @@ -0,0 +1 @@ +RUN: $(shift_minimization_sat) --input $(shift_minimization.dat) diff --git a/examples/cpp/slitherlink_sat_test.bintest b/examples/cpp/slitherlink_sat_test.bintest new file mode 100644 index 00000000000..3561a08a6c1 --- /dev/null +++ b/examples/cpp/slitherlink_sat_test.bintest @@ -0,0 +1 @@ +RUN: $(slitherlink_sat) diff --git a/examples/cpp/sports_scheduling_sat_test.bintest b/examples/cpp/sports_scheduling_sat_test.bintest new file mode 100644 index 00000000000..8cb0012f867 --- /dev/null +++ b/examples/cpp/sports_scheduling_sat_test.bintest @@ -0,0 +1 @@ +RUN: $(sports_scheduling_sat) diff --git a/examples/cpp/strawberry_fields_with_column_generation_test.bintest b/examples/cpp/strawberry_fields_with_column_generation_test.bintest new file mode 100644 index 00000000000..6668965b9c7 --- /dev/null +++ b/examples/cpp/strawberry_fields_with_column_generation_test.bintest @@ -0,0 +1 @@ +RUN: $(strawberry_fields_with_column_generation) --colgen_instance=4 diff --git a/examples/cpp/testdata/cgc/1.in b/examples/cpp/testdata/cgc/1.in new file mode 100644 index 00000000000..00776d701ab --- /dev/null +++ b/examples/cpp/testdata/cgc/1.in @@ -0,0 +1,6 @@ +4 +4 4 +1 2 6 100 +1 3 2 2 +1 4 2 2 +3 4 2 10 diff --git a/examples/cpp/testdata/cgc/2.in b/examples/cpp/testdata/cgc/2.in new file mode 100644 index 00000000000..b009f8917f5 --- /dev/null +++ b/examples/cpp/testdata/cgc/2.in @@ -0,0 +1,6 @@ +4 +4 4 +1 1 1 1 +2 1 1 1 +3 1 1 1 +4 1 1 1 diff --git a/examples/cpp/testdata/cgc/3.in b/examples/cpp/testdata/cgc/3.in new file mode 100644 index 00000000000..184b23bfbcc --- /dev/null +++ b/examples/cpp/testdata/cgc/3.in @@ -0,0 +1,3 @@ +1 +4 4 +2 2 2 10 diff --git a/examples/cpp/testdata/cgc/cgcut1.in b/examples/cpp/testdata/cgc/cgcut1.in new file mode 100644 index 00000000000..9a3483b2198 --- /dev/null +++ b/examples/cpp/testdata/cgc/cgcut1.in @@ -0,0 +1,9 @@ +7 +15 10 +8 4 2 66 +3 7 1 35 +8 2 3 24 +3 4 5 17 +3 3 2 11 +3 2 2 8 +2 1 1 2 diff --git a/examples/cpp/testdata/cgc/cgcut2.in b/examples/cpp/testdata/cgc/cgcut2.in new file mode 100644 index 00000000000..3929f108177 --- /dev/null +++ b/examples/cpp/testdata/cgc/cgcut2.in @@ -0,0 +1,12 @@ +10 +40 70 +21 22 1 582 +31 13 1 403 +9 35 3 315 +9 24 3 216 +30 7 2 210 +11 13 3 143 +10 14 1 140 +14 8 3 110 +12 8 3 94 +13 7 3 90 diff --git a/examples/cpp/testdata/cgc/cgcut3.in b/examples/cpp/testdata/cgc/cgcut3.in new file mode 100644 index 00000000000..30d4d75c0e4 --- /dev/null +++ b/examples/cpp/testdata/cgc/cgcut3.in @@ -0,0 +1,22 @@ +20 +40 70 +31 43 4 500 +30 41 2 480 +29 39 4 460 +28 38 4 440 +27 37 3 420 +26 36 4 410 +25 35 3 400 +24 34 4 380 +33 23 4 360 +22 32 3 340 +31 21 3 320 +29 18 3 300 +17 27 2 280 +15 24 2 240 +16 25 4 260 +15 24 1 240 +23 14 4 220 +21 12 3 180 +19 11 4 160 +9 17 1 140 diff --git a/examples/cpp/testdata/dimacs_example.txt b/examples/cpp/testdata/dimacs_example.txt new file mode 100644 index 00000000000..09f209c3fd4 --- /dev/null +++ b/examples/cpp/testdata/dimacs_example.txt @@ -0,0 +1,25 @@ +c Simple example file demonstrating the DIMACS data format, and for testing. +c Source: Invented from scratch by viger@google.com on 2019-05-22. +c Lines starting with 'c' (like this one) are comments. +c +c Graph description: (number of nodes) (number of arcs) +p asn 6 9 +c Note that the problems are 'perfect assignment' problems, where +c there are as many 'left' nodes as 'right' nodes, and we want to assign +c each 'left' node to exactly one 'right' node. +c +c "Left" nodes. +n 1 +n 2 +n 3 +c +c Arcs: left node, right node, cost of assigning left to right. +a 1 4 12 +a 1 5 53 +a 1 6 36 +a 2 4 14 +a 2 5 37 +a 2 6 46 +a 3 4 11 +a 3 5 52 +a 3 6 35 diff --git a/examples/cpp/testdata/lc102.txt b/examples/cpp/testdata/lc102.txt new file mode 100644 index 00000000000..0fde4b188d7 --- /dev/null +++ b/examples/cpp/testdata/lc102.txt @@ -0,0 +1,108 @@ +25 200 1 +0 40 50 0 0 1236 0 0 0 +1 45 68 10 0 1127 90 0 75 +2 45 70 -20 0 1125 90 8 0 +3 42 66 10 0 1129 90 0 10 +4 42 68 -20 727 782 90 6 0 +5 42 65 10 0 1130 90 0 9 +6 40 69 20 621 702 90 0 4 +7 40 66 20 0 1130 90 0 11 +8 38 68 20 255 324 90 0 2 +9 38 70 -10 534 605 90 5 0 +10 35 66 -10 357 410 90 3 0 +11 35 69 -20 448 505 90 7 0 +12 25 85 -30 0 1107 90 13 0 +13 22 75 30 30 92 90 0 12 +14 22 85 -40 567 620 90 16 0 +15 20 80 -20 384 429 90 18 0 +16 20 85 40 475 528 90 0 14 +17 18 75 20 99 148 90 0 19 +18 15 75 20 179 254 90 0 15 +19 15 80 -20 278 345 90 17 0 +20 30 50 10 10 73 90 0 22 +21 30 52 -10 0 1135 90 23 0 +22 28 52 -10 812 883 90 20 0 +23 28 55 10 732 777 90 0 21 +24 25 50 10 65 144 90 0 25 +25 25 52 -10 169 224 90 24 0 +26 25 55 -10 0 1130 90 29 0 +27 23 52 10 261 316 90 0 30 +28 23 55 20 546 593 0 0 103 +29 20 50 10 358 405 90 0 26 +30 20 55 -10 449 504 90 27 0 +31 10 35 20 0 1112 90 0 35 +32 10 40 30 31 100 90 0 33 +33 8 40 -30 87 158 90 32 0 +34 8 45 -20 0 1113 90 37 0 +35 5 35 -20 283 344 90 31 0 +36 5 45 -30 665 716 90 38 0 +37 2 40 20 0 1106 90 0 34 +38 0 40 30 479 522 90 0 36 +39 0 45 20 567 624 0 0 104 +40 35 30 -10 264 321 90 43 0 +41 35 32 10 166 235 90 0 51 +42 33 32 20 68 149 90 0 48 +43 33 35 10 16 80 90 0 40 +44 32 30 10 359 412 90 0 47 +45 30 30 -30 541 600 90 46 0 +46 30 32 30 448 509 90 0 45 +47 30 35 -10 1054 1127 90 44 0 +48 28 30 -20 0 1122 90 42 0 +49 28 35 -10 1001 1066 90 52 0 +50 26 32 10 0 1123 0 0 106 +51 25 30 -10 725 786 90 41 0 +52 25 35 10 0 1124 90 0 49 +53 44 5 -10 286 347 90 55 0 +54 42 10 40 186 257 90 0 56 +55 42 15 10 95 158 90 0 53 +56 40 5 -40 385 436 90 54 0 +57 40 15 40 35 87 90 0 60 +58 38 5 30 471 534 90 0 59 +59 38 15 -30 0 1110 90 58 0 +60 35 5 -40 562 629 90 57 0 +61 50 30 -10 531 610 90 67 0 +62 50 35 20 262 317 90 0 66 +63 50 40 50 171 218 90 0 69 +64 48 30 -50 632 693 90 74 0 +65 48 40 10 76 129 90 0 72 +66 47 35 -20 826 875 90 62 0 +67 47 40 10 12 77 90 0 61 +68 45 30 10 734 777 0 0 102 +69 45 35 -50 916 969 90 63 0 +70 95 30 -30 387 456 90 81 0 +71 95 35 20 293 360 90 0 77 +72 53 30 -10 0 1122 90 65 0 +73 92 30 -10 478 551 90 76 0 +74 53 35 50 353 412 90 0 64 +75 45 65 -10 0 1130 90 1 0 +76 90 35 10 203 260 90 0 73 +77 88 30 -20 574 643 90 71 0 +78 88 35 20 109 170 0 0 105 +79 87 30 10 668 731 90 0 80 +80 85 25 -10 769 820 90 79 0 +81 85 35 30 47 124 90 0 70 +82 75 55 20 0 1110 90 0 85 +83 72 55 10 0 1113 90 0 84 +84 70 58 -10 458 523 90 83 0 +85 68 60 -20 0 1116 90 82 0 +86 66 55 -10 173 238 90 90 0 +87 65 55 20 85 144 90 0 89 +88 65 60 30 645 708 90 0 91 +89 63 58 -20 737 802 90 87 0 +90 60 55 10 20 84 90 0 86 +91 60 60 -30 0 1123 90 88 0 +92 67 85 -10 368 441 90 96 0 +93 65 85 40 475 518 90 0 99 +94 65 82 -20 0 1105 90 98 0 +95 62 80 30 0 1108 90 0 100 +96 60 80 10 0 1109 90 0 92 +97 60 85 30 561 622 0 0 101 +98 58 75 20 0 1115 90 0 94 +99 55 80 -40 743 820 90 93 0 +100 55 85 -30 647 726 90 95 0 +101 60 85 -30 561 622 90 97 0 +102 45 30 -10 734 777 90 68 0 +103 23 55 -20 546 593 90 28 0 +104 0 45 -20 567 624 90 39 0 +105 88 35 -20 109 170 90 78 0 +106 26 32 -10 0 1123 90 50 0 diff --git a/examples/cpp/testdata/shift_minimization.dat b/examples/cpp/testdata/shift_minimization.dat new file mode 100644 index 00000000000..ddb76610469 --- /dev/null +++ b/examples/cpp/testdata/shift_minimization.dat @@ -0,0 +1,69 @@ +# Randomly generated data for apersonnel scheduling problem +# ./datagen tightness = 90 Multi-skilling level = 66 +# Random number generator seed = 0 +Type = 1 +Jobs = 40 + 43 516 + 164 746 + 75 591 + 230 718 + 839 1354 + 96 637 + 1 593 + 179 713 + 130 765 + 119 688 + 194 783 + 270 818 + 102 618 + 774 1291 + 28 550 + 56 630 + 758 1350 + 804 1320 + 16 557 + 16 565 + 30 536 + 186 752 + 677 1259 + 739 1244 + 834 1313 + 724 1346 + 761 1304 + 823 1396 + 569 1040 + 804 1340 + 125 740 + 764 1256 + 159 656 + 712 1278 + 726 1291 + 651 1235 + 750 1278 + 725 1363 + 867 1376 + 844 1319 +Qualifications = 23 + 26: 6 13 0 1 2 3 4 8 11 12 14 15 16 17 20 21 23 24 25 26 29 30 31 33 37 39 + 24: 14 16 1 4 6 8 12 15 17 18 19 20 22 23 25 27 28 31 34 35 36 37 38 39 + 28: 30 36 1 2 3 4 6 9 10 11 12 13 15 16 17 18 21 22 23 24 26 27 32 33 34 35 38 39 + 24: 7 25 0 2 4 5 6 10 13 15 16 17 21 23 26 27 29 30 31 32 34 35 36 38 + 29: 8 29 0 1 2 6 7 10 12 13 14 15 17 18 20 21 22 24 25 28 30 31 32 33 34 35 36 37 39 + 32: 5 37 0 1 2 3 4 7 9 10 11 12 13 14 15 16 19 20 22 23 24 25 26 27 28 31 32 33 35 36 38 39 + 26: 18 28 0 1 2 3 4 6 8 10 11 15 16 17 19 20 21 22 23 24 25 29 30 31 36 38 + 29: 0 26 1 2 3 4 6 7 8 10 12 13 14 15 16 17 18 19 20 23 24 27 28 31 32 34 35 37 38 + 30: 11 39 2 4 6 7 8 9 10 13 14 15 16 17 18 20 21 23 25 26 27 29 30 31 32 34 35 36 37 38 + 24: 19 24 0 1 2 5 8 9 10 11 15 17 20 21 26 27 28 29 30 31 34 36 37 38 + 30: 3 34 0 2 4 6 7 8 9 10 11 13 14 15 16 17 19 23 24 25 27 29 31 32 33 35 36 37 38 39 + 28: 21 31 0 1 3 5 8 9 12 13 16 17 18 19 20 22 24 25 26 27 28 30 32 34 35 36 37 39 + 29: 32 33 0 1 2 3 4 5 6 7 9 10 11 12 13 16 17 18 20 21 23 24 28 29 31 34 35 36 37 + 35: 9 23 1 2 4 5 6 7 8 10 11 12 14 15 16 17 18 19 20 22 24 25 26 27 28 29 30 31 32 33 34 35 36 37 39 + 31: 20 27 1 2 3 4 5 6 8 9 10 12 14 15 16 17 18 19 21 24 25 26 29 31 33 34 35 36 37 38 39 + 23: 15 38 0 1 4 5 7 11 12 13 14 16 17 18 19 20 21 25 27 31 33 35 37 + 24: 1 4 0 2 7 8 11 13 16 17 21 22 23 24 25 27 28 29 31 33 34 36 38 39 + 28: 2 22 0 1 4 5 6 7 8 10 11 12 13 15 16 18 20 21 24 26 27 30 31 33 35 37 38 39 + 29: 12 35 0 1 2 3 5 6 7 8 11 13 15 16 17 18 19 20 22 23 25 26 30 31 33 36 37 38 39 + 28: 10 17 0 2 3 5 7 8 9 13 14 15 16 19 20 22 23 24 25 27 29 30 31 32 35 36 38 39 + 33: 18 29 26 27 23 31 12 2 1 35 10 19 30 14 11 13 17 21 3 0 33 22 7 15 16 25 4 32 28 39 34 20 5 + 31: 6 5 10 30 23 34 36 20 12 8 39 16 0 18 3 17 22 21 35 25 15 37 13 2 27 29 31 11 38 33 32 + 33: 11 7 17 19 6 32 1 15 8 18 24 35 16 5 26 28 34 33 38 37 14 0 3 12 22 21 13 39 25 2 36 10 4 diff --git a/examples/cpp/testdata/wt40.txt b/examples/cpp/testdata/wt40.txt new file mode 100644 index 00000000000..c4dbb7ab177 --- /dev/null +++ b/examples/cpp/testdata/wt40.txt @@ -0,0 +1,751 @@ + 26 24 79 46 32 35 73 74 14 67 86 46 78 40 29 94 64 27 90 55 + 35 52 36 69 85 95 14 78 37 86 44 28 39 12 30 68 70 9 49 50 + 1 10 9 10 10 4 3 2 10 3 7 3 1 3 10 4 7 7 4 7 + 5 3 5 4 9 5 2 8 10 4 7 4 9 5 7 7 5 10 1 3 + 1588 1620 1731 1773 1694 1487 1566 1844 1727 1636 1599 1539 1855 1645 1709 1660 1582 1836 1484 1559 + 1772 1510 1512 1795 1522 1509 1598 1658 1826 1628 1650 1833 1627 1528 1541 1497 1481 1446 1579 1814 + 56 25 76 35 28 52 21 32 64 67 48 100 94 87 39 18 78 80 56 72 + 4 70 36 46 85 31 96 30 66 92 33 18 19 34 18 4 42 94 4 89 + 1 9 9 9 5 1 4 3 2 8 2 3 7 5 3 5 2 2 2 4 + 9 9 6 8 9 7 5 2 1 6 4 6 1 2 10 10 6 5 4 3 + 1687 1738 1663 1480 1504 1826 1722 1660 1594 1445 1704 1660 1715 1701 1679 1516 1658 1611 1502 1685 + 1614 1647 1689 1615 1524 1800 1654 1752 1456 1452 1801 1713 1761 1513 1759 1484 1821 1448 1666 1611 + 1 49 35 83 75 64 20 84 31 88 27 88 21 32 12 20 26 64 6 11 + 54 2 21 94 44 19 45 6 61 41 45 86 98 45 66 77 76 64 31 25 + 10 4 7 3 6 7 5 10 5 10 2 1 7 7 2 8 3 8 8 8 + 10 1 1 3 6 7 2 4 6 5 7 4 4 9 5 6 1 9 9 4 + 1452 1565 1588 1319 1436 1434 1573 1427 1593 1432 1428 1549 1565 1312 1614 1362 1643 1536 1372 1490 + 1631 1338 1336 1487 1361 1363 1583 1652 1396 1376 1319 1369 1341 1434 1319 1296 1644 1418 1421 1338 + 71 58 89 62 8 31 74 52 71 85 1 77 35 30 96 12 4 29 64 34 + 8 98 86 22 6 6 24 61 86 76 17 36 63 83 81 37 80 56 11 57 + 6 3 9 3 8 8 3 8 10 4 7 8 10 5 5 6 5 10 3 6 + 9 4 3 2 7 10 6 10 9 3 2 6 6 9 10 9 7 5 5 6 + 1419 1682 1683 1617 1703 1549 1741 1634 1580 1588 1694 1574 1548 1730 1535 1438 1501 1504 1587 1687 + 1472 1507 1389 1454 1404 1522 1526 1681 1506 1584 1720 1767 1621 1677 1487 1513 1591 1620 1771 1712 + 7 70 52 52 86 66 70 60 65 70 27 41 42 88 21 15 40 80 28 7 + 13 58 4 43 41 89 80 54 34 92 66 72 29 40 53 35 91 58 6 82 + 5 10 1 2 2 8 8 5 6 4 8 4 1 1 6 5 10 7 2 7 + 5 8 7 7 6 2 6 6 1 8 8 5 7 6 6 4 8 6 5 7 + 1471 1794 1514 1473 1702 1583 1640 1683 1491 1519 1702 1527 1701 1675 1421 1718 1639 1742 1749 1653 + 1441 1722 1691 1612 1424 1615 1809 1533 1648 1702 1450 1614 1675 1435 1441 1485 1746 1732 1727 1612 + 76 54 14 32 100 37 69 36 27 7 39 52 74 67 93 49 89 73 79 98 + 45 36 24 71 47 19 32 47 25 37 86 5 37 60 46 28 72 11 52 4 + 1 7 6 3 10 7 2 8 1 8 8 10 4 3 1 10 10 8 2 10 + 6 7 3 3 8 10 1 2 3 2 8 2 3 5 6 9 10 6 4 9 + 1145 1091 1222 1290 1047 1011 1058 1313 1351 1076 1163 1004 1141 1061 1259 1306 985 1173 1154 1325 + 1352 1169 1189 1241 1210 1209 1331 1128 1102 983 1232 1131 1358 1339 1260 1074 1283 1068 1008 1023 + 73 32 44 87 67 63 13 10 94 11 50 90 93 79 96 39 33 20 72 77 + 11 40 75 24 52 100 83 4 7 12 49 45 59 42 6 75 16 10 65 29 + 8 8 10 1 2 9 3 6 1 10 7 10 2 3 10 9 2 3 4 8 + 6 4 3 4 6 4 7 5 4 7 7 10 10 5 9 8 3 3 1 1 + 1034 1260 1317 1095 1026 1187 1138 1281 1063 1006 982 1169 1051 1310 1189 1125 1097 1212 1091 1063 + 1084 1336 1016 1025 1219 1186 990 1167 1216 977 1184 1155 1235 1335 1008 1329 1064 1242 1108 1328 + 22 83 90 4 33 16 21 17 34 54 51 33 88 93 94 13 85 84 41 21 + 43 30 64 25 10 89 10 96 59 70 5 8 93 37 54 37 44 4 39 65 + 7 7 10 1 5 7 1 8 10 1 6 7 6 1 6 8 6 2 4 1 + 1 6 6 6 10 10 2 6 2 7 6 3 3 9 5 1 8 9 9 5 + 1192 1284 1086 1269 1008 1002 1202 943 1153 960 1201 1204 1074 983 1115 1066 1034 981 1199 1174 + 965 1222 1242 1020 993 1298 1186 1148 1032 1132 1182 1115 1299 1062 1094 949 1175 993 1293 959 + 75 33 77 73 71 24 90 94 44 57 11 67 77 27 27 100 83 81 1 5 + 3 20 75 97 4 98 70 92 84 20 83 12 67 48 90 80 5 94 47 24 + 8 2 8 1 7 6 8 5 3 7 10 6 7 3 5 7 7 9 9 2 + 3 6 9 6 10 10 6 5 2 5 7 6 3 8 9 7 4 5 4 6 + 1472 1343 1218 1338 1495 1463 1444 1249 1121 1325 1366 1368 1186 1545 1396 1517 1141 1538 1405 1508 + 1238 1175 1238 1351 1376 1324 1450 1237 1504 1498 1148 1307 1353 1291 1404 1188 1292 1140 1315 1540 + 12 67 9 86 83 64 94 80 46 97 77 78 5 54 14 36 96 82 41 12 + 79 77 71 68 47 89 97 67 16 22 89 78 34 34 94 72 75 33 39 22 + 10 6 10 6 2 1 3 7 10 10 8 1 1 10 9 4 1 4 1 4 + 4 4 6 1 3 4 5 5 9 7 6 5 2 10 8 5 10 6 5 7 + 1366 1541 1552 1418 1301 1363 1194 1349 1480 1311 1631 1335 1378 1443 1181 1258 1205 1521 1412 1251 + 1398 1347 1620 1449 1410 1327 1466 1510 1539 1412 1515 1297 1239 1201 1213 1332 1416 1205 1285 1614 + 41 18 66 42 100 71 89 19 92 3 75 57 46 2 53 57 17 9 30 25 + 90 19 93 69 76 79 5 100 16 89 7 32 78 4 21 85 60 29 43 77 + 9 10 5 5 3 1 5 9 1 8 1 10 5 8 9 1 9 4 6 3 + 2 5 4 1 5 6 7 7 5 7 3 6 8 6 4 6 9 4 6 2 + 928 623 690 630 796 811 728 670 618 788 609 629 984 841 918 809 613 644 724 764 + 667 713 797 663 951 920 716 892 677 774 894 652 988 696 872 713 971 719 956 836 + 98 77 8 38 79 28 4 57 63 57 7 96 2 48 18 3 72 39 17 31 + 80 21 60 33 60 38 84 69 73 88 2 1 11 22 6 92 92 42 66 10 + 10 9 5 9 3 10 8 2 5 5 6 10 5 2 4 9 4 1 2 4 + 7 1 3 4 2 6 3 5 2 5 2 8 10 5 2 2 8 7 2 8 + 754 682 557 827 647 880 631 783 875 806 619 648 631 540 590 658 876 696 739 570 + 738 886 724 548 870 882 619 789 553 860 606 622 709 771 861 640 646 598 890 722 + 48 83 74 78 6 75 51 45 67 95 57 93 3 25 64 18 13 100 25 19 + 25 8 69 99 94 90 3 61 6 40 13 15 72 5 62 72 32 86 93 46 + 7 6 5 6 1 9 10 1 8 2 10 9 4 9 3 9 5 2 1 3 + 4 8 6 1 6 10 8 5 9 6 8 8 4 1 10 5 8 7 3 10 + 832 907 759 997 708 929 831 1001 763 891 871 686 838 624 690 635 630 997 724 707 + 870 961 874 637 648 881 777 836 620 803 680 955 739 945 892 687 624 774 636 765 + 82 33 79 67 96 3 16 33 40 6 82 46 7 19 22 48 18 76 59 84 + 24 59 96 5 2 9 10 61 59 15 59 8 28 23 80 5 71 29 85 12 + 3 8 6 8 7 6 1 1 6 4 7 6 3 6 1 7 1 3 7 7 + 9 10 1 3 8 10 2 1 4 1 3 7 4 6 10 9 1 5 3 8 + 756 774 770 620 720 667 808 562 728 743 794 657 817 611 562 599 799 748 517 673 + 567 749 691 590 554 724 634 790 716 540 780 627 664 556 691 660 622 541 752 503 + 23 75 17 14 92 58 65 79 46 30 21 58 100 68 1 42 97 100 1 22 + 9 8 93 95 36 26 29 60 6 42 38 18 74 98 29 75 25 88 85 39 + 8 9 9 7 7 3 6 8 2 3 1 2 3 3 5 5 8 2 1 6 + 5 6 8 4 9 5 6 9 5 7 9 4 3 4 6 9 7 5 8 9 + 872 724 826 789 878 974 680 847 796 662 639 800 717 952 742 884 735 900 987 680 + 919 909 609 884 674 830 710 924 688 649 760 724 712 966 836 689 719 905 893 712 + 17 8 61 32 63 36 43 41 77 64 90 36 96 65 89 53 85 63 9 32 + 38 38 93 16 76 22 65 51 49 99 26 84 35 7 56 70 40 38 11 62 + 10 9 10 6 7 9 2 10 9 7 3 7 2 7 5 9 2 7 5 7 + 3 1 10 5 7 9 5 8 4 7 4 1 4 8 8 1 3 9 9 3 + 602 476 347 346 321 320 277 473 492 244 595 307 293 221 208 249 406 521 497 259 + 539 384 509 450 415 541 472 269 532 264 235 304 306 288 345 244 465 270 486 290 + 44 88 60 35 10 90 72 81 55 54 83 87 38 52 53 37 47 6 21 17 + 96 90 4 17 30 34 98 30 19 19 75 51 63 80 86 78 91 5 61 16 + 8 4 9 8 1 8 9 4 6 10 4 4 8 1 6 4 7 8 3 1 + 5 5 6 2 9 1 8 6 7 1 8 1 5 7 7 9 6 4 8 1 + 442 276 351 442 237 620 484 503 402 408 473 293 252 381 379 331 506 370 377 507 + 354 422 414 276 511 480 475 293 301 259 237 545 286 582 578 338 590 371 335 556 + 6 22 44 42 58 22 52 64 28 3 72 24 97 30 36 53 75 23 54 83 + 99 54 16 51 33 49 89 97 72 86 16 63 37 97 23 95 78 67 9 42 + 6 4 9 10 1 1 3 9 4 1 10 6 6 3 5 9 5 4 5 10 + 8 7 5 10 10 8 8 1 5 4 5 7 5 3 8 3 10 3 3 3 + 498 605 344 361 429 599 436 351 308 263 570 415 274 225 586 311 501 237 518 217 + 334 289 245 523 513 292 587 581 255 371 487 538 303 541 575 510 415 600 346 355 + 37 71 18 74 62 92 61 59 73 63 7 63 72 48 60 62 90 62 2 38 + 88 75 94 73 51 9 74 54 96 39 61 71 65 95 48 15 31 57 9 84 + 1 7 6 6 8 6 2 5 9 6 2 6 10 9 3 1 6 9 5 5 + 10 7 1 4 4 2 10 4 8 6 2 3 1 9 1 10 10 5 5 3 + 655 263 510 495 668 392 574 325 588 554 666 634 397 356 649 241 429 290 687 533 + 410 686 402 633 562 431 548 601 643 521 332 267 586 482 466 600 468 541 489 247 + 100 47 68 56 6 8 57 36 94 43 17 20 88 11 25 30 41 25 36 95 + 34 52 81 43 76 10 71 8 5 71 96 27 85 62 22 39 10 61 93 87 + 3 8 10 3 2 3 6 3 7 7 2 9 2 8 5 8 8 1 7 9 + 6 6 4 1 2 10 4 6 5 4 10 4 9 8 8 9 8 6 6 3 + 197 314 578 420 325 474 260 200 227 456 435 438 369 504 493 483 234 469 535 520 + 219 481 275 206 242 252 454 202 484 517 202 420 383 415 367 405 529 469 500 281 + 82 18 55 14 1 36 73 72 26 3 8 18 2 77 11 26 5 66 7 68 + 37 35 100 21 29 98 73 67 41 26 87 87 59 41 81 69 99 17 71 2 + 5 5 4 9 10 2 1 5 3 1 6 2 4 8 8 2 1 7 1 7 + 10 8 2 1 6 6 1 9 2 2 3 7 1 4 4 10 2 5 5 7 + 0 0 123 0 5 168 104 41 0 0 0 62 170 61 163 59 0 113 76 0 + 30 55 0 136 68 179 0 0 54 0 0 0 0 0 154 33 0 28 130 0 + 1 3 22 81 86 90 22 42 28 57 66 82 96 55 73 20 86 92 43 76 + 22 28 52 75 58 76 53 43 75 2 79 11 81 25 42 11 14 17 29 81 + 5 3 5 3 9 8 5 3 10 2 4 1 5 1 4 1 9 9 1 2 + 5 3 1 1 4 10 9 6 6 8 8 7 10 4 8 6 2 1 2 2 + 0 103 0 44 0 0 38 166 0 0 57 1 0 0 159 0 71 0 0 87 + 193 0 0 0 0 0 146 148 0 159 165 36 11 19 83 0 0 0 54 0 + 4 75 87 90 29 42 96 27 92 70 52 38 81 9 47 87 17 64 52 41 + 45 90 14 71 40 97 60 51 5 50 94 59 71 62 98 74 97 5 34 80 + 3 4 3 3 2 10 9 1 8 8 9 3 2 5 8 10 7 3 2 2 + 2 9 10 1 4 7 6 1 1 6 7 4 7 1 4 7 6 5 10 3 + 88 167 228 0 0 0 56 205 0 66 0 0 0 0 0 0 0 117 0 146 + 0 0 0 77 69 185 0 105 0 0 159 0 52 66 0 0 113 20 0 179 + 63 61 47 77 25 14 63 13 33 64 7 18 98 57 45 4 60 94 17 86 + 89 30 43 81 80 69 23 10 59 73 31 97 78 55 23 70 18 80 31 57 + 4 6 1 1 4 4 8 3 7 6 9 7 2 5 8 9 7 4 4 2 + 9 7 8 3 2 9 5 9 9 7 4 7 1 7 7 6 8 7 9 7 + 154 159 70 34 60 142 172 127 0 0 163 2 0 144 0 0 189 123 95 0 + 0 0 34 0 177 0 187 0 0 13 85 0 0 0 161 81 0 0 188 47 + 81 39 78 84 99 82 71 85 98 10 52 56 12 67 58 53 5 51 1 40 + 65 11 75 80 11 52 48 41 91 31 70 94 78 57 66 13 76 92 40 75 + 4 10 6 8 5 4 1 5 5 6 2 3 9 2 6 2 2 10 1 10 + 5 7 1 7 2 4 3 5 10 8 7 4 8 5 7 9 3 2 9 2 + 0 179 19 2 119 0 40 99 24 0 0 69 0 0 0 0 151 128 171 0 + 12 147 0 0 0 0 29 145 0 110 227 45 189 0 0 0 0 10 0 0 + 87 43 33 53 1 76 44 34 60 36 82 88 21 63 54 18 68 53 46 33 + 12 52 21 45 95 60 21 69 85 32 66 21 78 75 55 23 99 47 64 98 + 5 7 10 7 6 6 1 3 9 6 4 2 6 10 6 5 9 7 9 5 + 6 2 8 8 6 4 10 6 4 2 8 8 6 8 6 8 3 9 3 6 + 1267 1914 1785 1385 1653 1344 1888 2075 1804 1297 1990 1709 1561 1719 1947 2024 2095 2021 1591 1836 + 1616 1683 1703 1655 1612 1285 2041 1964 1720 1424 1465 1940 1747 2059 1868 1946 1597 1424 1564 1404 + 41 56 43 30 12 77 87 53 22 19 74 54 47 21 82 84 95 73 70 99 + 99 21 72 58 21 60 4 20 51 41 55 52 9 69 98 20 40 100 79 96 + 1 1 5 9 5 6 2 5 4 9 9 4 6 4 7 2 8 7 8 7 + 7 2 9 7 6 10 8 1 9 6 10 9 7 3 1 1 9 6 1 1 + 1494 1558 1707 1373 1538 1603 1783 2142 1765 1811 1878 1852 1675 1785 1921 1456 1879 1505 1979 1990 + 1387 1958 1519 1470 1475 1815 1739 1907 2090 1578 2190 1797 2202 2017 1352 1372 1485 2162 2054 2134 + 27 19 18 66 40 22 81 39 12 94 80 99 43 70 67 44 60 39 70 76 + 20 41 99 1 6 70 37 26 30 75 50 64 33 54 78 29 84 63 42 38 + 10 3 8 1 9 2 2 9 2 4 10 1 6 10 2 10 5 6 2 5 + 10 5 10 10 2 7 5 7 2 6 3 10 6 3 2 4 10 5 3 6 + 1829 1980 1263 1532 1527 1377 1304 1295 1535 1844 1586 1958 1596 1809 1815 1897 1413 1694 1977 1622 + 1381 1288 1656 1747 1310 1524 1365 1364 1946 1848 1869 1620 1300 2001 1507 1693 1773 1790 1920 1566 + 60 22 47 13 21 98 85 91 90 44 32 68 89 93 11 88 66 28 23 3 + 99 88 47 62 95 65 41 21 88 2 40 40 40 97 59 78 41 100 89 1 + 1 10 2 7 2 2 6 4 7 6 9 9 7 2 5 6 4 7 8 2 + 7 2 9 2 2 6 10 4 6 1 7 5 2 9 1 5 4 5 5 3 + 1442 1639 1483 2221 2264 1743 2047 1442 2193 2139 2239 1543 1725 1378 2034 1385 1939 2135 1892 1846 + 1690 1896 1993 1711 1610 2215 1922 2179 2199 2115 1718 1850 1487 1917 1566 2132 2188 2070 1935 2228 + 83 11 94 26 20 48 38 11 42 9 40 10 92 24 97 15 41 73 80 23 + 89 93 42 31 64 70 12 42 22 46 96 62 47 16 82 98 51 26 32 61 + 3 6 9 3 9 7 7 10 7 6 5 9 5 2 5 9 10 10 6 10 + 8 2 8 10 1 3 7 3 4 8 9 9 7 5 7 2 8 9 4 6 + 1756 1615 1772 1197 1866 1894 1703 1596 1847 1700 1347 1676 1734 1532 1441 1741 1818 1588 1715 1927 + 1794 1548 1811 1788 1861 1520 1781 1838 1863 1825 1437 1566 1608 1607 1644 1763 1581 1734 1313 1758 + 66 83 4 64 29 66 54 6 82 80 92 79 88 52 84 24 44 60 75 83 + 68 36 88 2 13 64 25 29 54 84 65 17 99 85 65 22 81 11 62 100 + 9 8 7 5 2 4 6 8 3 1 7 9 5 4 5 8 4 10 10 1 + 1 7 4 1 6 3 3 4 9 9 6 6 7 3 7 5 5 9 2 7 + 1480 1684 1474 1438 1532 931 1041 1110 1219 1826 1694 998 1607 1400 1174 1018 1033 961 1708 1405 + 1424 1264 1014 1314 1350 1797 1122 1531 1424 1441 1208 1252 1022 1410 938 1703 933 1338 1518 1090 + 9 90 99 90 69 100 29 84 51 3 53 30 43 20 10 17 61 27 16 32 + 46 54 66 70 72 81 34 25 37 43 71 97 70 29 6 87 27 80 47 1 + 5 1 8 3 2 8 5 1 10 3 10 3 1 1 10 3 6 7 7 1 + 3 10 4 4 9 4 8 2 10 2 1 4 9 9 7 3 9 10 5 1 + 1503 881 1491 888 1415 1099 1158 1567 1376 1157 824 1580 1318 850 1077 1164 936 1348 1000 1277 + 1198 1023 1454 811 1510 1152 861 1300 1213 955 851 978 1493 931 1252 1068 1350 1048 1010 1416 + 37 35 80 69 88 52 35 57 22 74 68 23 49 21 90 100 6 66 91 24 + 59 35 36 35 100 4 71 76 51 8 18 93 63 29 80 34 12 7 36 51 + 2 6 5 5 6 9 10 2 5 1 2 8 5 1 10 5 5 9 8 4 + 10 5 10 7 2 2 6 7 5 1 9 10 9 8 3 10 2 4 10 4 + 833 940 1046 1433 1128 829 1582 1431 1398 1217 1180 1109 1076 994 1096 921 1488 1278 1226 903 + 1028 1217 1018 1047 1027 1341 1586 1273 920 958 1066 1013 953 1499 1494 1561 1308 1536 984 894 + 7 77 25 68 63 75 21 41 93 45 59 18 26 5 20 81 23 22 66 13 + 70 20 35 1 82 74 44 33 4 12 88 2 6 16 41 5 89 22 53 40 + 2 10 8 1 4 1 10 4 1 1 3 4 8 6 7 6 8 8 7 6 + 4 4 10 10 6 7 5 6 2 6 8 2 9 7 6 7 3 1 10 2 + 1137 1078 737 846 972 844 691 1087 852 955 895 1209 955 994 746 1032 873 1092 1208 1034 + 824 1117 1104 743 643 811 708 1259 985 880 1203 1109 1052 1234 666 667 1068 803 1135 1180 + 100 25 90 60 100 77 16 53 90 21 25 82 23 71 71 74 81 93 85 60 + 72 5 80 72 34 81 42 47 32 45 41 25 59 77 19 48 37 7 2 62 + 4 5 6 1 10 6 8 2 10 2 8 8 8 8 8 10 1 3 2 5 + 9 5 1 5 9 6 5 2 5 4 2 8 1 3 9 7 10 3 10 2 + 1009 1009 1062 897 1643 1700 1012 1627 1509 1341 1380 1530 1392 1639 1415 1246 1002 1617 940 1067 + 1506 1738 1359 1345 1528 1473 999 1593 1422 1677 1351 891 1015 1285 1535 1349 1262 1433 1055 1542 + 29 43 10 19 31 18 27 15 68 59 46 82 17 75 93 93 80 29 71 25 + 87 38 64 57 97 70 32 75 99 48 13 63 7 48 26 11 65 98 64 13 + 5 10 5 8 9 3 1 2 5 2 5 1 2 1 10 9 7 9 4 8 + 5 3 6 5 8 2 10 3 8 5 5 10 6 5 9 4 1 1 9 3 + 747 1111 1041 1032 401 1148 906 789 747 893 629 996 1100 700 893 556 1138 918 656 906 + 832 858 478 780 766 577 649 999 461 649 505 832 1106 740 563 1186 673 983 1040 963 + 32 47 97 22 17 12 39 52 12 68 78 90 13 23 14 56 45 35 23 73 + 62 19 60 69 43 40 56 75 98 17 22 22 65 23 47 48 8 84 99 92 + 2 9 3 4 3 4 9 1 4 2 5 4 8 7 9 9 10 7 7 5 + 10 5 1 10 8 4 9 5 4 8 7 9 3 1 8 4 1 1 9 3 + 538 436 960 472 537 888 1115 1066 574 817 643 599 663 834 448 886 923 831 701 509 + 755 531 960 464 660 401 665 1115 657 693 772 1029 587 843 467 745 803 780 1062 851 + 77 9 44 50 48 26 13 92 31 13 56 81 44 75 42 85 33 68 21 74 + 74 3 17 23 70 41 37 9 50 99 61 11 75 46 50 14 32 5 45 3 + 10 1 2 3 5 8 4 6 4 2 4 7 9 4 9 6 9 2 9 5 + 2 10 7 7 5 6 1 6 2 7 10 6 8 4 10 3 3 5 6 4 + 591 869 702 434 766 972 820 735 547 426 436 623 989 657 358 824 935 380 850 873 + 440 989 1044 831 357 422 792 566 874 645 777 778 404 827 876 887 403 632 968 751 + 60 49 18 87 42 79 92 64 65 11 92 31 71 47 62 4 27 40 20 99 + 29 96 66 37 4 77 75 43 3 94 2 43 8 15 79 68 28 57 17 50 + 1 5 7 8 3 2 3 6 2 5 8 4 8 5 10 6 4 8 1 9 + 7 6 5 9 10 9 1 5 3 1 1 5 7 3 1 8 10 10 9 6 + 1159 986 1117 761 428 909 599 1123 1070 960 780 891 464 484 683 507 1064 521 431 1069 + 981 473 585 811 530 612 1098 526 601 1077 660 817 634 495 1104 516 890 426 512 939 + 5 46 67 20 44 44 93 3 82 31 78 23 83 3 34 40 36 12 55 21 + 60 61 11 32 1 7 49 44 35 59 95 32 63 80 55 71 17 88 11 43 + 7 1 6 7 5 6 7 1 3 3 1 8 5 9 7 7 4 3 8 9 + 10 4 2 3 4 10 8 2 3 7 9 9 4 1 3 7 1 10 1 1 + 732 933 691 670 499 629 561 816 810 910 964 509 546 494 490 412 802 1034 911 780 + 677 691 363 404 1008 520 728 736 854 812 819 684 539 553 983 733 895 691 446 515 + 63 27 17 40 27 48 15 46 78 96 81 39 99 65 2 26 2 3 40 19 + 28 10 38 33 44 39 15 85 49 99 70 96 66 47 79 90 48 98 97 94 + 7 8 1 4 9 8 9 1 10 10 6 4 2 3 5 8 3 1 9 7 + 3 5 8 5 6 2 2 8 8 4 7 3 1 1 2 9 7 6 7 3 + 625 448 143 797 452 717 316 387 491 464 203 778 536 518 462 512 296 316 756 578 + 131 129 691 685 575 431 369 57 607 17 505 289 453 292 509 382 690 111 254 425 + 14 75 70 39 94 69 32 33 75 82 51 66 63 4 83 94 91 81 64 74 + 21 9 9 67 45 39 8 39 80 75 32 58 72 73 30 93 98 43 26 18 + 1 10 8 8 8 10 5 7 3 10 3 8 5 6 4 9 8 2 5 7 + 7 9 6 2 4 1 9 4 2 2 1 4 9 7 3 3 4 2 8 4 + 513 228 419 398 232 147 101 102 756 38 39 135 116 555 465 823 347 49 714 338 + 411 440 230 354 50 419 16 773 580 437 149 139 874 291 353 852 773 606 132 835 + 66 26 8 14 75 40 99 7 27 18 11 96 49 48 75 81 99 70 98 68 + 58 79 65 66 41 22 41 85 88 1 67 40 65 67 63 50 72 29 28 33 + 5 6 5 7 7 3 7 2 4 10 4 5 1 6 9 9 10 4 2 4 + 1 1 2 4 5 4 6 3 9 3 1 7 9 4 9 10 9 10 5 1 + 259 201 384 841 310 629 132 431 815 125 254 707 381 705 201 296 405 481 414 469 + 722 251 748 425 813 273 177 672 570 21 348 17 650 81 159 661 683 206 166 59 + 56 68 92 64 27 47 46 15 12 88 66 21 60 74 53 28 49 46 87 91 + 85 93 26 63 2 59 4 3 62 43 36 72 56 94 14 36 2 17 7 61 + 5 4 6 9 7 10 8 9 10 5 3 4 5 7 6 10 1 7 6 3 + 5 4 8 10 3 10 6 9 9 7 1 10 6 9 5 3 5 8 6 8 + 355 429 511 168 486 286 141 481 493 478 421 411 77 117 635 178 447 236 337 512 + 394 488 233 703 456 83 383 120 534 657 320 108 205 276 322 115 193 149 768 364 + 48 43 58 92 38 28 83 7 39 25 96 37 25 17 71 78 94 38 59 30 + 75 76 88 95 22 72 67 76 21 21 82 84 84 70 95 29 51 20 49 26 + 3 4 4 6 8 2 5 2 9 5 10 2 1 8 5 4 2 1 2 10 + 4 3 5 10 1 6 3 7 2 9 6 3 8 6 9 3 6 9 1 2 + 786 140 199 360 561 183 810 489 856 46 765 378 333 798 428 72 870 468 631 702 + 416 854 171 480 411 655 793 850 408 521 278 722 195 597 71 861 595 276 134 768 + 34 46 22 4 20 74 98 34 45 19 62 44 2 60 60 78 14 28 15 81 + 29 8 27 2 26 38 77 93 10 9 22 10 16 65 97 10 7 5 17 55 + 2 5 1 8 10 7 10 7 4 5 7 8 1 2 10 6 10 9 3 3 + 4 2 2 6 10 3 5 1 6 10 7 4 9 7 2 5 2 3 1 2 + 119 181 156 0 129 127 0 0 0 215 211 0 0 125 0 0 0 0 228 56 + 2 0 0 0 0 271 265 156 0 0 0 0 62 0 105 129 0 114 0 0 + 36 24 79 9 17 31 57 52 93 12 2 45 78 43 30 76 16 52 37 38 + 78 21 97 27 65 85 59 43 3 32 54 86 49 3 90 62 87 92 51 80 + 3 1 7 7 8 8 5 9 1 8 9 4 3 3 9 8 3 2 10 10 + 7 1 8 10 10 1 6 8 8 8 7 5 2 6 10 3 4 4 5 6 + 0 0 0 93 0 179 6 99 0 0 129 0 270 46 311 0 0 81 0 0 + 0 0 0 0 0 0 184 180 0 327 0 0 156 333 0 0 0 231 0 338 + 1 79 1 18 100 96 4 21 76 98 30 45 67 57 68 25 37 99 86 2 + 80 1 28 18 18 96 17 19 1 69 54 53 6 58 20 73 39 54 71 35 + 5 10 10 7 9 3 10 6 2 1 6 9 9 5 2 6 5 5 3 1 + 5 4 8 6 1 2 10 6 3 6 4 6 9 8 1 2 9 5 2 2 + 0 61 361 0 0 0 327 0 25 76 323 152 1 190 0 126 278 0 277 227 + 248 35 93 0 45 0 248 68 0 0 0 49 0 0 0 231 297 0 0 0 + 7 64 38 47 67 21 61 3 15 25 42 53 11 47 41 82 41 9 99 75 + 22 86 30 23 96 7 31 17 34 54 75 54 38 61 13 33 34 16 74 55 + 4 9 8 10 7 7 4 6 6 7 1 6 3 3 4 7 1 9 10 5 + 10 1 9 7 8 7 6 2 3 3 8 9 10 9 6 9 3 2 5 3 + 195 0 74 0 103 0 0 0 41 69 271 202 159 329 29 0 192 68 0 6 + 80 0 7 0 242 0 0 144 153 125 0 0 0 0 0 0 0 83 0 165 + 58 88 19 14 45 9 28 62 58 95 9 82 81 12 93 5 21 79 91 64 + 17 68 92 95 32 6 63 38 62 70 48 36 26 93 72 35 76 59 35 83 + 8 9 6 3 5 10 3 7 3 8 9 8 2 4 2 7 2 4 8 3 + 8 1 1 9 7 3 6 6 1 3 2 9 3 3 2 1 3 8 2 9 + 325 384 0 0 0 0 0 139 168 0 0 226 195 0 0 0 37 0 103 0 + 0 0 70 134 0 0 0 0 0 0 47 23 0 84 0 0 0 399 369 377 + 89 11 49 41 88 19 85 67 83 61 82 46 76 1 45 56 97 55 57 76 + 7 92 79 90 93 34 49 84 68 58 84 48 78 90 79 75 94 14 7 60 + 10 8 8 1 2 9 3 9 10 8 1 9 1 4 6 4 9 3 1 9 + 9 6 9 8 1 10 6 9 7 9 5 9 4 1 8 7 10 3 3 2 + 1445 1249 2181 1877 1517 1516 1253 1762 2246 2550 1652 1729 1912 2530 1272 1847 1408 2483 1939 1595 + 2624 1437 2244 2031 1653 2654 1478 1312 1962 1388 1334 1298 1683 1477 2448 1596 2347 1610 1669 1726 + 59 46 15 51 81 59 86 89 94 31 89 64 22 20 98 100 19 39 70 24 + 65 55 35 67 69 87 5 61 69 8 69 43 30 41 93 53 46 68 22 33 + 3 9 5 10 8 7 8 4 3 9 8 4 10 3 3 8 1 5 5 3 + 6 4 10 1 3 6 6 1 9 5 6 5 9 9 5 6 7 1 2 6 + 1567 1721 1314 1352 2126 1136 1462 1138 1789 1507 2085 2152 1967 1862 2260 1711 2058 1220 2164 1254 + 1929 2127 1977 1741 1146 1824 1435 2305 1458 2306 1332 2166 2106 1478 1777 1899 2321 1919 2390 1435 + 58 59 77 73 34 95 68 3 24 65 36 57 75 25 31 6 62 86 78 23 + 88 10 82 87 83 13 26 77 7 65 70 76 47 88 52 71 18 57 90 52 + 8 2 9 8 2 2 10 3 7 8 7 8 2 8 8 7 6 6 9 2 + 5 10 5 6 4 7 7 4 6 7 9 2 3 8 9 7 5 4 6 5 + 1934 1175 1115 1502 2129 1792 1230 1803 1832 2123 1134 1253 2297 1666 1659 2101 2335 1304 2360 2293 + 2138 1620 1730 1340 1179 1868 2003 1600 2038 2238 1471 2313 1730 1505 1920 2194 2097 1926 2265 1361 + 4 72 79 38 86 43 42 86 34 77 9 49 17 28 100 44 32 2 12 92 + 3 59 22 16 82 12 84 44 52 27 30 7 20 24 72 36 25 75 99 69 + 5 2 9 4 9 9 10 8 4 10 8 9 5 2 5 4 9 7 5 9 + 7 5 9 2 1 10 6 2 1 2 4 8 6 7 10 2 1 8 6 5 + 1241 1177 952 1274 1716 1866 1388 1739 1257 1384 1903 1703 1428 1738 980 1550 1451 1819 1662 1977 + 1347 1417 915 1253 1283 1459 1145 1672 1486 1548 1721 1418 1591 1275 1845 1007 1624 994 1800 1640 + 43 56 37 79 6 72 1 20 15 34 71 69 61 91 39 90 62 48 77 51 + 65 60 31 23 44 39 28 79 3 46 73 96 72 99 48 56 10 66 13 40 + 1 10 3 7 3 7 7 3 5 2 4 3 8 2 6 8 4 7 2 10 + 3 6 9 8 3 2 3 8 10 4 9 1 9 9 9 3 3 7 6 10 + 1056 1538 1837 1610 1103 1848 1441 1934 1860 1421 2026 1948 1602 1425 1406 1502 1642 2039 1501 1472 + 1393 2113 1295 1910 1267 1838 1246 2010 1360 1208 1058 1876 1937 1486 1682 1281 2119 1245 1888 1436 + 48 38 51 33 88 6 12 88 83 55 61 47 44 32 62 21 13 37 57 25 + 57 24 67 77 73 1 88 98 86 26 98 11 49 13 51 43 42 27 79 25 + 4 3 5 8 9 3 8 3 10 1 2 10 6 3 2 5 5 8 7 2 + 4 5 7 7 6 4 9 7 5 5 1 9 2 3 4 4 7 8 4 9 + 1443 1541 1612 1229 1652 1580 1612 1310 1555 975 1395 1652 1522 837 1425 1406 997 1398 1699 1366 + 715 1681 763 1024 852 917 1117 860 914 1693 1277 1722 1370 934 1176 1311 1226 1169 1181 1686 + 20 97 94 40 57 20 23 77 84 13 99 16 36 19 59 18 75 36 17 43 + 91 15 64 55 3 58 70 23 58 23 84 18 29 20 31 12 21 11 17 84 + 9 5 2 9 7 10 10 3 7 8 2 9 4 6 3 6 3 1 4 2 + 5 10 9 8 3 4 2 4 6 2 6 3 1 6 3 7 6 7 10 10 + 1171 1010 649 1448 751 1185 648 1100 676 672 1499 988 622 1362 1373 636 1191 1309 1333 577 + 701 666 1341 768 797 871 937 554 1163 1245 1482 1234 984 1293 607 837 920 1451 682 835 + 91 65 21 44 64 45 59 76 91 35 83 15 30 77 100 64 64 66 96 34 + 6 48 35 34 95 25 58 80 100 49 87 89 18 28 17 82 73 21 27 83 + 7 4 5 1 8 4 6 9 3 1 7 10 6 6 4 6 6 9 1 10 + 5 6 1 5 8 6 2 8 3 6 6 3 4 9 9 8 3 4 9 10 + 930 1059 1244 1337 1643 1557 941 1854 1158 1123 1435 1016 1771 1883 960 1119 1116 1010 1800 1102 + 1580 1245 959 1274 1313 1648 1962 1911 917 1313 1434 1166 1276 1021 904 1845 695 1499 1562 1137 + 17 28 62 91 89 61 24 81 8 99 87 82 52 81 31 48 28 98 14 32 + 69 94 71 94 38 67 16 95 24 67 41 99 48 97 16 74 54 14 90 91 + 3 6 8 8 9 2 2 10 2 1 4 6 2 6 5 1 4 5 6 4 + 6 3 5 2 2 8 1 4 4 6 7 1 3 10 9 2 9 6 9 1 + 957 2105 1120 1956 1943 1859 1043 2018 1305 1088 763 1805 895 1461 1409 896 1531 990 1491 1856 + 1429 736 1391 948 1056 863 1444 886 1564 1650 751 1549 1117 964 942 1360 1852 968 720 790 + 86 25 28 31 40 5 99 86 63 38 65 62 53 61 78 58 39 60 68 75 + 76 76 41 19 49 5 84 99 70 93 61 25 80 41 13 12 46 2 60 78 + 5 2 7 4 8 1 5 3 8 8 5 7 2 4 6 8 3 6 4 6 + 3 7 7 3 4 6 6 3 4 7 5 9 2 1 3 7 3 6 1 3 + 1484 864 1710 954 1928 1196 1211 933 1452 924 1210 759 768 901 1076 1204 1819 1324 1910 1314 + 1208 1369 1594 1594 1047 763 793 1387 1089 713 1866 771 1836 1608 686 1804 1837 1362 1029 1395 + 12 46 7 16 55 65 44 92 14 38 49 100 12 98 44 88 99 30 33 2 + 51 70 93 97 75 12 82 26 5 98 36 48 4 26 69 25 61 47 72 20 + 2 8 3 4 9 9 9 7 3 8 2 8 5 5 4 3 7 5 2 6 + 7 1 6 3 2 1 7 3 9 8 3 2 2 3 3 10 3 1 10 7 + 1309 524 939 577 378 358 220 611 813 508 587 1209 762 1001 688 452 299 1344 972 211 + 1322 1359 349 824 973 269 486 1054 933 1070 1286 993 849 209 492 851 569 1356 914 332 + 37 37 64 2 96 82 96 1 49 37 47 47 39 29 12 34 38 40 62 58 + 45 5 51 17 89 13 6 13 25 4 36 52 37 72 26 42 38 66 40 94 + 2 7 2 5 7 7 9 3 7 1 2 3 9 7 7 5 10 1 10 7 + 7 3 2 10 4 1 8 4 2 7 6 3 2 10 7 9 4 6 5 7 + 846 751 1016 643 363 1089 392 1154 1120 512 995 509 1025 290 627 731 328 302 831 616 + 521 763 1016 216 727 431 1126 820 624 347 793 391 841 1044 1005 572 873 243 232 1130 + 31 96 43 48 56 53 73 18 80 26 49 55 69 41 39 97 14 76 88 88 + 39 63 18 73 92 88 32 1 53 87 7 24 25 53 3 98 45 29 54 80 +10 8 9 3 4 4 3 1 8 4 8 7 7 8 7 3 4 9 1 2 + 3 6 2 10 3 8 6 10 3 5 2 9 1 5 4 5 3 2 7 5 + 1126 453 366 995 1142 312 616 1308 212 487 895 670 909 1450 454 1011 783 749 944 922 + 1070 213 320 1287 902 1188 1227 1457 970 394 1109 1109 253 296 607 1465 941 1175 1230 834 + 94 74 66 15 57 24 66 53 52 51 20 79 66 76 65 78 14 77 20 41 + 40 81 78 61 16 69 13 32 40 78 76 54 42 94 97 30 77 59 83 63 + 10 6 6 6 10 2 6 5 4 10 2 6 3 8 1 8 9 9 4 6 + 10 2 5 1 3 2 1 10 3 10 4 5 5 3 8 7 1 5 1 7 + 604 331 1144 648 499 636 1080 1434 342 930 1131 741 1202 353 779 1191 1026 406 996 1204 + 1313 498 376 1079 866 956 1469 230 1104 1291 1471 544 1176 486 1280 900 551 703 265 520 + 52 75 35 70 47 97 50 18 34 9 4 78 23 30 5 84 52 34 45 71 + 86 45 88 80 17 42 81 53 69 57 45 36 89 75 84 77 57 33 75 30 + 4 1 8 4 4 2 2 6 10 8 2 7 1 7 7 10 4 9 8 4 + 5 5 2 3 6 7 7 10 1 7 6 10 9 9 6 7 10 6 5 8 + 1213 263 497 1051 1192 956 820 1466 1323 1104 1339 737 529 998 1364 392 789 680 564 1290 + 631 854 449 381 763 619 1372 893 1394 932 1453 1431 1481 1067 1252 1044 1159 802 1409 1105 + 61 57 26 79 2 26 74 27 25 52 22 15 61 51 54 23 70 92 29 33 + 39 99 87 34 83 45 45 14 45 61 97 65 97 27 16 84 39 78 55 70 + 4 10 3 6 3 9 2 8 10 2 10 9 10 6 8 1 6 2 8 1 + 10 8 9 6 3 5 1 3 3 5 8 10 7 1 8 7 1 5 2 4 + 1005 694 0 268 301 136 86 41 179 0 319 0 826 0 433 269 64 0 917 376 + 629 506 560 78 810 704 973 0 497 177 606 0 448 752 495 791 946 0 753 554 + 3 29 97 16 27 17 66 100 52 83 76 55 29 38 83 7 40 50 18 87 + 93 36 92 28 27 70 93 66 6 77 90 41 23 36 26 96 1 79 26 77 + 9 6 1 3 4 4 8 8 7 3 10 9 9 7 9 5 10 7 2 5 + 9 8 6 1 2 3 4 4 4 10 2 6 10 4 8 1 10 5 6 3 + 892 585 889 788 887 0 182 643 596 743 0 571 975 136 0 1015 225 0 40 694 + 0 330 728 874 197 113 729 404 399 28 763 0 48 574 666 912 344 431 815 702 + 89 55 83 53 57 33 33 57 69 60 44 65 2 47 42 51 59 57 32 93 + 69 5 95 70 92 62 92 66 15 49 63 68 54 42 23 64 53 69 69 47 + 6 6 8 7 1 6 6 9 1 8 9 8 6 5 1 5 1 7 10 4 + 1 9 5 1 9 3 10 10 8 5 3 8 8 10 4 3 7 6 8 3 + 867 0 0 892 753 0 0 690 318 858 0 922 935 401 623 580 552 0 319 229 + 296 346 1073 0 978 25 506 559 281 729 32 384 33 43 350 419 698 380 309 1011 + 60 53 96 70 51 86 60 23 61 94 83 18 87 85 61 75 54 19 72 18 + 14 8 69 74 49 18 85 74 66 70 31 50 5 34 95 39 29 38 45 81 + 1 6 2 10 6 8 6 10 3 9 9 7 6 2 7 6 10 4 7 7 + 5 2 2 7 2 4 7 7 8 8 1 7 10 5 6 8 4 4 8 3 + 204 197 546 1039 103 484 11 443 266 0 147 0 549 0 1044 1024 997 426 195 581 + 399 100 625 0 784 661 0 445 700 729 761 542 1027 149 382 632 893 941 862 0 + 82 92 27 46 100 42 10 26 38 48 26 87 17 12 46 98 81 92 16 29 + 7 97 64 54 96 90 94 49 79 88 88 96 16 60 8 78 95 27 91 80 + 10 5 4 6 10 9 2 7 8 4 6 7 5 4 2 5 9 5 1 3 + 10 1 7 5 2 1 6 4 3 1 9 4 4 2 9 2 7 2 3 3 + 173 171 697 157 534 174 1070 590 659 0 0 547 1098 126 0 979 737 722 130 401 + 934 1089 80 0 404 758 666 0 773 361 257 11 53 513 0 0 1032 0 433 297 + 51 95 45 65 20 23 23 96 40 39 87 51 52 42 80 46 23 88 21 98 + 51 81 9 28 60 83 20 59 3 5 8 19 61 15 5 38 42 32 23 50 + 4 10 10 9 4 4 10 10 5 9 6 1 4 6 7 7 7 1 4 4 + 10 8 2 8 4 4 1 9 2 4 1 3 4 9 9 2 7 6 2 5 + 374 413 0 0 0 0 422 0 51 0 0 134 25 500 511 0 0 337 0 0 + 281 399 0 0 276 0 0 211 0 0 461 509 518 0 0 0 89 528 265 364 + 91 37 97 15 58 100 18 67 34 36 57 90 81 42 28 46 91 69 23 99 + 80 20 11 17 96 52 41 71 14 75 39 34 36 52 9 84 93 99 35 100 + 7 3 8 6 6 5 10 7 7 2 2 3 5 6 4 10 2 1 9 10 + 10 10 4 3 8 10 10 2 4 4 9 8 5 2 8 6 5 10 8 1 + 0 444 471 485 348 0 219 221 359 185 0 96 291 505 260 0 0 589 0 0 + 0 400 651 0 361 135 258 0 299 0 0 424 122 0 237 551 0 0 0 334 + 53 49 10 85 87 100 93 76 15 63 5 88 24 31 30 29 23 52 32 11 + 55 27 53 47 88 9 13 56 17 5 10 59 68 75 21 79 94 31 100 43 + 7 2 10 1 1 8 5 10 8 2 8 7 7 1 10 4 10 1 6 5 + 3 2 3 7 3 1 4 4 4 2 4 4 9 2 10 4 10 5 6 3 + 288 199 0 0 320 67 355 554 0 289 0 0 203 448 0 216 220 472 0 0 + 84 314 0 0 393 201 0 239 0 316 499 130 116 0 424 125 360 354 516 0 + 19 12 85 69 9 2 24 64 8 95 78 33 24 82 5 30 89 8 41 13 + 66 69 99 45 46 17 72 90 46 55 29 15 14 40 18 91 24 81 67 93 + 4 1 2 4 5 8 4 10 8 8 4 8 9 1 6 7 6 9 5 3 + 10 2 2 7 7 7 9 7 6 5 3 6 8 3 4 4 10 6 6 5 + 316 351 0 0 308 14 512 0 0 419 0 330 0 0 357 0 275 0 0 0 + 536 0 0 53 139 235 199 423 0 0 0 0 423 208 550 0 297 450 372 0 + 95 62 4 64 53 38 62 74 64 62 1 13 56 22 50 83 83 59 18 90 + 30 67 90 41 10 94 30 49 62 40 79 27 56 59 58 93 87 57 65 68 + 3 1 2 2 1 3 5 8 8 1 2 4 7 3 5 10 2 2 3 8 + 1 6 7 1 6 1 2 4 3 7 6 1 10 10 10 8 9 4 1 3 + 395 0 588 300 333 31 340 0 512 537 457 0 0 166 0 0 578 0 571 25 + 0 515 0 0 0 552 0 222 216 0 454 0 85 0 606 0 0 353 0 498 + 17 88 28 68 79 94 40 52 64 46 77 23 21 29 41 31 29 81 11 29 + 93 92 45 70 47 8 78 14 54 25 24 22 4 94 75 58 16 19 100 90 + 10 7 2 4 1 8 4 10 7 4 6 1 6 1 3 4 10 2 7 5 + 4 5 8 9 5 3 5 7 7 6 6 6 3 1 4 7 3 8 7 1 + 1338 2062 1669 1534 1564 1391 2193 2223 1373 1713 1026 1743 1537 2199 1243 1534 1179 1214 1727 921 + 2359 2189 2155 1015 1245 1339 871 1508 2175 1422 1456 1406 926 1038 1128 2146 1942 2078 2042 2013 + 52 31 94 85 72 9 68 47 15 83 89 59 50 36 31 17 79 3 45 19 + 28 72 60 69 53 83 85 70 40 31 4 31 99 64 81 65 72 81 58 84 + 9 1 9 10 6 3 6 7 10 6 5 8 10 4 2 2 10 9 9 10 + 5 6 10 3 7 1 3 2 6 10 9 2 3 9 4 5 5 10 4 8 + 1349 2078 1523 2126 1871 1271 1006 1749 1928 1774 1537 1783 2157 1532 1854 1665 2563 1234 1640 1226 + 2350 2052 1793 1525 2415 2488 2060 1254 1641 1408 1302 1105 1150 1980 2200 1949 2375 2320 914 2119 + 90 86 18 19 47 73 33 12 53 61 96 20 1 29 51 86 12 9 23 55 + 37 85 12 77 52 95 98 3 65 31 38 89 9 36 68 93 45 9 1 77 + 2 1 1 2 3 7 4 7 4 5 2 4 9 7 5 8 1 4 5 2 + 9 3 9 2 3 5 5 4 9 8 7 6 5 6 10 4 4 9 5 10 + 1747 2082 2255 1152 1211 1532 819 880 2087 890 916 2090 1131 1782 1415 2247 1650 2229 2271 1521 + 1258 2268 1744 1983 1566 1261 1928 1502 1991 2006 1416 2025 1878 1992 840 1960 1881 2049 1593 1427 + 52 1 94 81 46 32 2 3 44 22 35 9 60 46 12 88 38 32 27 32 + 50 63 53 38 96 73 42 4 3 50 24 32 27 35 4 28 53 23 70 100 + 10 10 2 1 10 2 1 4 2 1 3 9 5 10 9 4 9 10 4 8 + 9 4 5 10 3 5 2 10 3 8 1 9 2 8 9 10 2 2 8 6 + 1839 685 1442 1414 1747 761 1405 1888 1750 1448 1292 737 1821 1696 897 1655 653 1330 1194 1001 + 738 695 1941 1384 1101 929 1236 1250 1072 1012 912 668 816 1936 897 1301 1214 1155 716 1817 + 88 77 95 82 40 84 17 79 78 60 23 32 41 78 1 76 92 62 16 50 + 26 92 16 98 64 99 22 65 56 68 36 100 79 37 38 55 40 97 84 86 + 10 9 6 1 3 6 4 6 6 2 6 4 6 1 6 7 9 1 8 8 + 8 5 2 9 6 4 1 6 2 3 10 3 6 6 9 7 6 9 1 5 + 2380 2123 2763 2365 1172 1171 1943 1878 1732 1367 1370 986 1583 1114 1156 2097 1554 2193 1594 1622 + 1725 1454 1528 2535 2205 2885 1562 2664 1016 1537 2429 1427 2647 1422 2377 1712 2335 1377 1354 1078 + 73 68 69 37 27 80 3 2 78 24 54 97 86 71 83 5 39 41 29 12 + 50 11 9 7 65 9 18 83 53 17 29 37 16 70 93 66 81 56 40 73 + 2 3 4 6 5 4 5 1 6 4 5 2 9 3 8 3 5 9 3 2 + 5 6 8 5 8 2 9 2 10 3 3 1 6 9 2 3 2 8 8 9 + 426 1677 602 778 1508 480 1314 481 1390 1584 1019 718 1699 586 1048 758 1673 1716 1667 576 + 992 1056 823 1550 387 1735 1067 466 1497 1620 859 626 1525 1071 1501 1388 537 1036 1416 1843 + 14 82 47 64 34 24 58 37 92 15 99 18 68 22 3 35 70 63 81 23 + 37 11 21 17 34 91 90 58 54 83 33 88 19 17 74 53 78 80 13 1 + 1 3 8 10 9 7 8 8 2 4 5 5 10 3 5 8 10 3 7 10 + 10 4 4 2 5 5 10 5 8 1 5 4 1 1 2 4 6 2 7 8 + 512 426 483 1102 835 1669 1676 870 657 766 1767 1700 1635 420 1302 1545 990 1611 1546 1456 + 478 1646 1676 1729 935 573 1300 1865 611 751 695 1276 1866 1105 1798 765 391 1258 856 1706 + 43 79 34 85 17 73 61 53 96 87 10 8 83 82 84 27 8 76 66 92 + 30 37 17 85 37 88 76 92 44 4 59 4 94 14 62 13 60 100 57 14 + 10 8 5 9 6 2 1 7 2 8 5 6 2 8 6 1 4 5 4 6 + 9 2 2 6 10 6 8 8 7 9 3 9 8 2 10 8 1 9 8 5 + 1109 506 2070 665 2085 1505 533 1899 1501 724 1976 1522 581 557 2018 690 1868 1554 844 1590 + 467 1940 1763 1369 1936 1425 2013 679 1495 724 1630 1037 1915 1932 1138 1338 1323 1165 1816 2105 + 21 72 15 25 58 39 69 20 96 47 14 86 59 57 25 48 99 81 57 32 + 98 87 42 69 65 93 92 92 37 72 95 37 79 49 17 44 85 2 4 57 + 4 10 5 10 6 10 5 8 10 10 9 2 9 7 7 1 5 7 9 1 + 8 10 2 4 6 1 4 9 5 5 7 9 1 8 8 6 8 6 4 3 + 1806 1799 1583 1636 1853 2134 780 1173 490 552 782 1959 1216 2203 1886 1435 1192 1493 539 905 + 1803 1248 2125 1012 2231 586 696 1683 1072 1571 695 1184 641 663 2045 1416 1443 728 2213 1614 + 37 29 53 10 69 83 16 13 25 55 18 73 51 2 4 63 22 34 3 57 + 42 13 18 31 40 56 32 65 76 32 5 43 38 63 76 51 40 26 63 79 + 2 8 1 10 5 1 6 3 1 4 4 8 1 7 5 6 2 3 2 10 + 1 10 4 4 2 6 9 3 10 10 2 2 5 1 10 7 9 2 4 6 + 661 1410 923 401 1076 1203 573 1355 1202 647 449 846 818 1337 1200 589 1170 1086 952 664 + 1001 1267 637 1396 923 672 993 756 723 1567 1476 1369 1178 994 1102 1502 1361 678 1217 788 + 10 69 88 73 89 24 16 9 43 26 6 95 42 29 79 47 81 39 34 86 + 86 17 45 36 10 76 68 24 32 81 34 81 63 52 56 39 40 61 21 35 + 5 10 8 8 10 8 10 6 8 6 7 10 7 1 1 9 10 4 6 8 + 10 5 2 6 9 9 10 1 7 4 5 3 2 9 7 2 1 4 7 10 + 420 1309 525 1430 1497 131 131 1514 296 1456 762 1278 426 1374 41 693 575 814 596 175 + 574 446 977 1315 476 1021 1343 618 409 1383 594 1099 1208 364 492 1369 1164 995 1251 1014 + 85 65 41 46 85 31 59 91 41 26 43 78 43 1 85 72 10 59 55 39 + 88 59 1 52 51 8 50 84 3 71 5 12 75 64 50 16 26 55 46 79 + 10 4 9 5 6 3 1 8 3 5 4 7 5 7 1 1 3 10 7 1 + 6 1 5 9 3 5 5 8 10 1 7 1 2 5 10 4 2 7 8 1 + 655 170 1030 673 453 1100 1370 1455 621 209 1163 288 292 1547 506 1074 972 78 128 1240 + 143 1531 81 269 1265 886 760 1212 1146 673 29 500 1497 377 482 374 981 691 333 256 + 35 43 7 66 53 26 13 41 36 74 67 53 94 34 32 16 69 7 51 43 + 29 47 43 52 70 60 7 64 17 6 18 2 23 99 31 94 69 3 80 70 + 5 9 5 9 5 1 3 6 4 6 1 6 1 9 2 4 4 2 6 5 + 9 10 3 4 1 8 9 8 2 2 3 5 4 9 6 5 7 7 4 5 + 892 1198 538 525 1016 650 884 1099 634 992 1092 255 10 253 148 633 1093 974 662 1268 + 411 66 711 1030 787 261 342 1365 60 1361 382 1375 15 539 575 564 675 314 1190 881 + 62 83 63 75 18 87 28 46 73 44 99 55 62 99 14 61 48 50 86 24 + 20 46 6 9 98 92 45 84 27 44 51 19 83 11 49 89 68 58 46 71 + 4 8 4 3 10 4 5 4 1 9 7 8 1 8 6 3 5 10 4 7 + 6 1 2 2 6 3 7 7 3 4 10 5 5 6 4 9 10 1 6 1 + 236 261 1737 227 383 1094 776 1307 1372 671 88 172 684 1525 1628 615 870 1468 1711 1129 + 623 98 52 983 751 941 916 167 499 441 1124 218 23 609 1714 654 223 1524 1209 1551 + 97 16 62 14 29 41 26 97 26 62 36 20 43 19 99 89 64 7 99 12 + 2 73 2 1 98 17 49 69 18 94 60 35 61 9 33 20 51 23 44 46 + 10 10 7 8 10 1 8 9 8 3 6 2 6 10 9 1 5 10 9 7 + 4 8 9 6 1 5 10 2 2 8 1 2 2 9 8 1 4 8 10 8 + 553 1235 689 933 678 1103 595 145 126 988 741 1137 1095 1150 537 118 962 956 754 1100 + 611 1301 941 1056 768 686 337 1151 475 464 913 357 465 914 1257 1046 221 851 1011 1192 + 97 29 35 31 69 73 13 86 97 7 53 88 60 69 33 89 64 51 11 43 + 42 96 23 8 58 3 82 47 1 20 80 24 89 53 88 6 70 57 53 64 + 6 10 7 9 4 6 8 8 2 1 5 1 9 7 9 4 2 3 10 5 + 7 2 7 1 8 3 9 1 9 4 9 6 9 5 3 7 10 5 2 5 + 0 883 280 474 0 1053 782 2 185 0 779 873 232 0 0 332 497 45 629 734 + 360 1082 434 425 151 821 973 288 1004 616 993 1209 581 675 530 742 1155 0 1174 465 + 49 59 92 74 44 16 70 11 16 11 3 4 65 80 43 50 20 9 18 52 + 35 40 37 16 96 68 85 49 14 39 25 63 29 55 100 73 11 68 30 97 + 1 9 2 10 8 8 4 10 5 1 5 6 9 8 7 9 2 6 3 9 + 7 8 1 9 6 3 3 2 7 6 3 2 9 8 10 4 6 5 5 3 + 437 311 882 962 0 0 410 619 899 691 625 0 0 0 631 137 493 411 39 157 + 0 521 635 0 395 909 84 790 62 936 860 0 736 495 1040 0 607 1050 337 0 + 67 98 20 98 91 60 55 71 41 60 18 12 39 32 31 65 35 59 96 81 + 5 80 91 10 58 86 76 8 25 22 85 34 71 83 31 49 84 22 8 17 + 4 3 1 4 7 5 8 3 10 9 10 1 4 6 9 3 1 2 2 3 + 8 9 8 8 4 5 10 5 4 4 4 2 2 5 1 8 2 5 2 4 + 966 151 262 477 980 323 0 1172 0 848 1200 187 0 1148 1030 826 522 0 0 135 + 0 1206 150 0 0 36 0 243 1007 0 0 0 1244 413 503 0 0 882 192 603 + 94 92 86 56 74 45 8 68 59 92 8 62 89 62 80 31 38 34 97 71 + 5 46 12 81 17 84 53 62 31 90 28 21 31 57 23 64 28 34 1 4 + 3 6 10 10 6 6 6 9 7 5 6 1 3 1 5 4 1 10 10 8 + 8 3 2 3 7 6 5 10 10 4 9 1 9 4 3 10 2 2 2 1 + 628 713 0 822 0 413 488 833 0 0 736 1019 439 211 171 794 447 941 1064 845 + 310 83 654 655 30 0 263 72 497 0 712 62 916 54 155 1043 267 0 781 422 + 99 38 34 9 87 68 44 8 83 100 59 8 52 57 48 48 88 20 54 71 + 13 19 2 22 75 40 92 51 54 19 3 9 55 50 94 52 12 75 99 38 + 2 7 6 3 6 2 7 8 4 7 5 9 9 10 5 1 5 6 9 7 + 6 4 6 1 10 1 5 8 8 9 4 5 7 5 6 8 1 4 7 7 + 956 600 1039 413 130 759 56 968 702 442 413 360 985 0 986 389 870 1005 507 290 + 135 0 929 567 627 667 648 0 0 93 989 234 1137 680 989 1121 655 639 760 0 + 82 87 86 18 48 76 57 100 56 27 40 5 91 61 74 18 71 55 77 28 + 37 35 42 29 63 86 45 17 47 40 34 90 17 87 41 51 30 67 73 4 + 6 9 10 3 9 4 3 1 8 7 10 3 6 4 7 6 4 9 6 5 + 8 2 1 1 8 7 10 3 8 6 3 10 2 6 4 4 3 9 2 7 + 303 0 403 0 0 508 114 0 0 273 111 164 0 338 0 0 0 251 0 272 + 184 328 0 116 0 347 0 0 0 750 54 120 60 0 0 0 0 780 100 804 + 70 43 42 48 8 70 18 57 49 72 92 11 100 68 74 46 1 30 52 97 + 16 84 93 3 38 8 44 8 89 34 26 57 19 48 41 99 6 87 68 86 + 8 4 6 6 2 3 1 3 6 4 9 10 7 1 9 7 4 8 4 8 + 1 4 9 3 2 1 3 6 8 1 10 3 1 9 4 7 9 7 6 5 + 76 782 0 0 231 340 381 3 0 730 93 0 273 114 0 0 0 0 0 0 + 624 0 588 0 157 0 0 0 0 21 0 760 506 69 0 0 0 127 0 184 + 18 65 81 68 43 4 80 92 60 86 39 90 100 25 41 42 21 90 46 32 + 11 5 93 45 86 45 99 72 89 37 58 6 91 65 45 41 92 63 10 78 + 7 8 3 4 3 5 10 6 10 2 3 6 3 5 4 9 5 5 5 10 + 7 10 4 6 1 2 3 4 5 2 4 2 6 3 6 1 9 8 9 10 + 0 637 0 0 578 572 0 0 0 390 396 0 358 0 253 0 0 94 0 0 + 152 878 0 237 296 0 0 714 0 588 0 677 0 362 0 0 386 0 81 388 + 9 61 58 50 58 86 69 25 17 73 81 5 37 33 48 18 1 34 3 92 + 17 95 57 62 23 88 97 63 83 38 92 36 91 24 86 23 69 78 3 70 + 5 4 2 8 9 1 3 7 4 3 7 3 1 6 7 3 10 9 7 9 + 9 9 7 1 6 8 9 9 4 6 8 8 9 5 4 9 2 6 9 8 + 257 0 558 0 0 516 0 585 169 0 513 0 576 0 0 653 134 0 168 0 + 633 0 391 87 711 694 251 0 627 0 501 222 0 637 717 0 783 797 0 0 + 53 96 10 66 86 76 93 77 74 90 96 26 91 48 45 88 41 13 45 40 + 54 1 78 62 89 82 5 36 35 77 65 51 5 63 74 95 44 68 27 60 + 7 10 6 8 3 8 6 10 8 3 9 8 10 8 9 5 5 4 2 4 + 8 4 10 10 2 2 6 10 9 4 8 9 10 4 5 5 3 10 3 6 + 0 0 414 395 895 454 0 0 0 0 0 562 0 0 0 0 0 208 502 719 + 492 689 205 0 0 0 0 0 0 369 0 387 62 0 802 592 115 492 512 0 + 91 87 47 96 88 88 35 72 14 48 41 23 65 76 61 72 66 30 73 40 + 70 25 31 57 57 25 67 51 37 51 93 56 47 74 96 8 53 20 44 48 + 1 1 6 9 9 10 9 3 2 4 9 3 6 5 1 7 1 1 4 2 + 1 6 9 8 6 2 2 2 2 5 2 8 8 7 10 5 9 6 3 6 + 1702 2117 2171 2067 2477 2866 1026 1652 1803 1835 1758 2600 1385 1375 2016 1910 1018 1725 1750 2637 + 2831 2622 1326 1596 1075 1559 771 2629 1913 2043 2335 2093 2105 761 845 2121 2131 2497 1824 2775 + 90 3 63 19 54 87 60 38 49 11 50 72 93 77 2 90 32 44 64 62 + 42 20 64 62 24 69 4 68 11 6 69 59 61 2 18 19 61 78 90 35 + 10 6 2 2 7 7 6 2 4 5 3 10 2 2 1 7 10 6 10 5 + 8 10 3 6 1 4 7 5 4 2 3 5 10 8 3 4 2 7 7 6 + 1077 2349 748 1107 612 1111 2393 2411 592 757 1063 1644 1522 1852 2348 1293 1657 1341 728 2475 + 668 1616 1020 1993 2345 1324 2337 1887 959 596 2234 725 1269 1468 1814 1230 1037 1569 2184 1571 + 45 68 25 21 1 95 8 6 18 95 36 49 36 66 52 67 22 64 76 75 + 75 94 6 96 18 16 18 54 5 44 67 74 16 44 84 87 60 34 82 28 + 9 3 9 3 7 4 2 7 9 1 9 1 1 5 9 1 1 3 3 2 + 3 7 6 1 10 6 6 6 3 5 7 5 2 4 2 9 6 10 2 8 + 2017 780 2268 1232 1486 1892 1167 1097 1071 1186 1160 1453 1512 1372 1281 1940 691 1653 1349 1911 + 1161 1034 2267 1328 1293 663 1519 708 878 1372 1157 2024 1908 2167 1378 942 689 1616 816 1200 + 80 57 81 87 75 84 97 9 88 69 90 36 83 9 75 30 48 53 43 56 + 3 7 64 81 94 25 86 76 96 20 35 14 11 52 17 74 72 39 13 35 + 8 3 6 8 8 1 8 7 2 1 8 1 6 1 7 7 2 9 6 5 + 1 5 3 7 3 8 8 5 7 10 4 1 7 2 8 9 1 9 1 4 + 1406 2620 1708 868 2449 1982 2382 2807 2593 1857 767 2280 2118 941 2734 1902 1750 2773 2456 850 + 2453 2152 2229 2372 1984 750 902 734 1181 2180 710 1712 2678 1651 2201 882 2048 2665 2413 1948 + 47 99 55 8 49 80 25 85 56 58 82 16 51 25 11 11 48 30 78 81 + 86 73 22 40 34 7 65 6 88 39 4 99 74 30 16 45 44 92 55 69 + 8 5 5 2 5 7 8 3 6 1 6 2 4 7 1 2 8 9 8 8 + 5 8 4 3 1 10 2 4 1 8 6 8 9 1 5 2 5 3 1 6 + 2131 2372 2386 1464 1665 2256 633 2265 2299 664 1505 1065 1060 2075 2130 791 708 1656 1194 2338 + 1625 2549 1222 1537 634 2421 1959 977 725 843 676 1033 1948 1700 1397 1779 1233 748 1337 1940 + 27 77 52 78 26 63 68 74 44 36 58 77 88 88 80 43 90 63 71 31 + 14 58 43 1 50 24 89 18 16 64 79 50 59 35 60 68 53 48 60 45 + 7 2 1 3 3 4 8 6 3 5 1 2 5 4 4 8 4 3 1 3 + 5 4 4 4 10 1 4 4 1 6 10 1 7 8 1 9 6 9 10 9 + 237 1217 1587 1994 1991 1195 1477 1525 968 1952 2371 822 413 1314 2139 1235 492 2238 755 940 + 259 1356 635 294 701 295 1559 1768 2369 726 1007 2290 1925 507 2085 1849 606 1743 859 2344 + 26 27 54 92 35 64 78 18 5 34 43 8 65 24 79 64 70 76 92 90 + 5 83 69 95 81 93 72 90 40 7 45 91 49 78 6 14 5 13 55 80 + 1 4 4 10 7 1 10 9 3 10 6 6 5 8 6 4 4 8 10 5 + 2 9 7 1 8 6 3 7 9 9 6 8 2 5 7 9 1 7 2 8 + 894 2007 1726 868 1628 2120 505 575 2001 1808 1630 2012 2263 248 1103 2307 1114 1751 1595 914 + 1848 367 2220 600 535 1488 2027 1040 610 2228 334 1923 1054 340 228 769 422 2079 441 264 + 82 36 18 50 34 69 88 66 19 33 42 4 85 12 82 13 12 44 88 45 + 4 20 17 56 95 85 5 4 49 36 64 50 55 23 89 58 32 50 47 97 + 7 8 5 1 7 4 6 4 4 10 4 2 9 4 7 2 9 6 3 10 + 4 5 5 2 8 5 10 8 6 5 7 6 10 1 9 6 2 6 4 8 + 674 456 1540 1236 1816 2008 878 1173 422 1376 244 1143 883 602 1167 285 1934 783 241 823 + 194 1543 568 1513 211 1447 1829 1291 1282 1186 572 2010 1924 275 722 947 1558 241 867 963 + 58 47 84 55 10 17 6 65 93 69 35 59 79 84 44 33 66 97 85 86 + 84 96 86 21 88 22 55 42 62 69 79 57 18 85 20 78 97 70 21 20 + 7 6 10 7 4 3 4 6 3 6 6 8 9 9 5 4 9 4 2 4 + 6 8 1 9 3 4 9 1 1 10 9 3 9 1 4 10 4 8 4 7 + 1786 896 2051 2195 254 2157 2573 2021 795 341 585 1241 1619 1215 1151 568 2437 2473 1213 1246 + 1907 2446 1672 1456 922 613 1896 929 2394 1623 2428 2047 490 1764 1840 397 1497 2348 1784 2266 + 48 54 68 13 77 100 22 69 65 31 83 74 3 26 96 72 14 17 85 7 + 48 89 50 30 82 80 72 91 23 81 7 47 4 35 91 55 87 41 39 76 + 3 4 8 9 7 5 4 10 5 3 4 3 9 10 6 3 2 8 3 4 + 5 2 1 2 6 5 10 1 3 4 2 10 2 4 4 7 7 4 5 1 + 1579 287 1273 2303 1093 2152 1853 299 1321 1075 1570 957 1683 718 697 2018 2311 1762 1056 764 + 1694 1301 2055 1484 1854 1925 2096 344 1691 351 1064 1454 2161 807 1741 1745 1769 1515 1533 580 + 72 6 63 32 78 62 99 33 97 60 22 52 45 76 47 78 63 40 70 4 + 26 32 63 26 30 88 79 43 6 96 46 56 8 96 72 71 21 83 42 97 + 7 8 2 9 3 4 2 2 3 2 3 8 8 7 6 4 5 6 5 10 + 3 6 7 3 6 7 4 5 9 6 1 7 3 7 4 10 1 8 6 9 + 1023 0 0 660 228 0 732 1145 1008 1912 1104 1101 861 1191 372 1551 1685 958 0 0 + 846 969 1185 1958 798 141 767 0 143 302 1448 1685 1068 123 655 649 414 1046 1680 251 + 78 3 41 24 83 100 84 16 84 51 69 97 18 27 86 4 50 54 100 76 + 75 11 57 56 13 30 80 89 14 95 71 25 56 44 8 15 45 69 50 15 + 7 6 8 10 7 7 4 9 1 3 2 3 4 6 5 2 4 5 9 9 + 2 4 2 7 1 3 8 10 2 9 2 4 4 2 3 6 4 2 5 1 + 94 1227 63 140 429 625 1510 1616 370 1643 1336 103 1152 1814 1462 1506 1339 195 0 1240 + 754 912 1291 1690 1319 1245 0 1841 677 1349 330 299 1271 589 483 1420 0 1076 39 1839 + 43 100 53 5 90 67 98 80 45 52 19 20 65 22 78 6 39 1 13 26 + 82 63 41 69 37 39 58 27 76 9 36 97 98 8 26 77 31 24 59 69 + 1 5 5 5 4 10 1 10 8 8 4 1 6 1 9 3 1 7 1 2 + 2 8 3 6 8 5 9 4 7 1 5 5 4 8 3 9 2 3 7 8 + 422 532 1715 1540 598 781 1339 326 1439 39 296 800 938 1552 89 1275 1747 389 823 989 + 1257 426 1513 1014 182 1139 1359 307 425 947 51 514 28 1541 999 64 779 1484 0 929 + 73 4 28 92 28 67 40 91 29 63 42 32 57 60 67 43 22 75 24 60 + 61 82 3 18 39 5 71 56 50 24 72 18 14 70 29 49 83 28 45 98 + 4 5 7 10 5 3 9 3 3 8 6 4 8 1 4 3 9 10 2 1 + 2 5 2 8 3 6 10 8 5 7 4 3 1 8 8 9 10 4 8 1 + 422 192 488 1319 1672 163 137 427 1141 674 1429 1364 676 587 993 839 363 417 179 1366 + 263 0 1537 691 1188 241 511 1292 697 0 1032 1714 1521 666 1478 335 0 1297 710 83 + 100 75 71 8 49 56 90 17 13 41 90 48 93 92 76 77 21 25 28 86 + 55 10 9 58 84 70 57 79 22 79 88 38 13 66 99 28 29 13 61 46 + 2 5 9 10 2 2 4 8 4 7 6 7 5 10 2 2 9 7 2 6 + 10 8 2 7 9 1 6 10 7 9 7 2 3 2 9 3 2 4 8 1 + 1246 152 965 1255 353 1193 1701 1284 1061 960 1505 1466 318 1651 1082 282 1048 985 400 1832 + 1151 243 1182 1721 861 411 1850 0 382 1578 17 0 766 1244 394 1199 1550 200 0 1856 + 86 27 68 77 39 46 5 28 2 91 91 86 99 21 4 73 83 52 97 18 + 35 47 3 49 64 28 32 1 8 65 74 9 31 84 94 14 28 65 39 58 + 5 8 2 3 4 1 8 5 7 8 7 4 9 9 7 7 10 7 9 2 + 10 4 10 10 9 5 8 1 1 3 5 7 4 6 6 10 10 8 2 3 + 0 0 152 1288 16 275 1087 0 462 1130 1134 805 798 1273 370 1290 0 0 495 0 + 199 751 1299 334 1309 735 0 0 1240 1183 1096 78 319 0 459 10 767 0 879 160 + 20 27 81 26 8 99 18 100 29 73 52 68 30 7 31 99 86 9 81 69 + 99 44 7 52 100 27 91 31 49 27 93 88 32 67 54 9 66 22 89 85 + 7 3 8 6 8 7 7 8 6 9 6 1 10 4 7 9 9 8 2 3 + 7 9 8 6 2 3 7 5 5 2 10 4 9 2 7 1 4 8 7 1 + 1338 923 0 770 1335 300 0 251 1167 1117 326 518 198 951 909 1494 241 0 7 360 + 1072 795 1055 1145 1299 0 728 0 303 785 0 0 1254 1142 1225 1178 194 467 0 1221 + 20 95 51 86 95 33 43 11 55 3 56 19 70 58 12 27 34 5 8 58 + 73 4 21 33 40 68 5 82 16 63 92 33 9 8 93 20 58 76 97 79 + 6 5 4 6 2 6 7 7 4 1 3 5 9 9 3 8 1 10 3 2 + 4 4 1 4 8 6 9 4 3 5 2 1 9 9 9 6 1 9 3 10 + 0 407 1105 76 733 1032 363 397 0 403 65 811 529 525 835 607 0 0 82 0 + 1011 710 0 747 0 1073 880 1134 393 864 882 0 1175 0 0 378 113 887 473 1207 + 30 3 97 91 68 17 70 40 71 46 68 65 29 38 83 31 61 100 86 6 + 14 85 24 60 61 6 24 68 7 24 91 5 82 56 6 54 45 71 31 96 + 1 7 1 5 9 7 1 7 2 6 10 4 3 10 1 7 2 10 9 6 + 10 5 6 3 4 7 4 2 1 7 1 5 5 9 7 8 1 5 5 5 + 1068 1151 1235 0 0 14 0 283 0 0 1263 269 229 0 0 23 907 79 0 0 + 191 683 698 1288 278 486 0 538 822 0 958 0 0 0 82 0 1142 0 359 34 + 26 77 17 78 22 74 68 72 92 21 27 38 47 65 56 89 28 6 98 38 + 3 98 73 4 69 48 78 7 51 15 91 71 100 13 65 24 100 7 34 94 + 7 4 1 3 3 6 5 2 10 8 5 6 8 3 8 7 4 3 10 1 + 6 5 4 4 6 10 5 3 9 7 1 5 7 2 4 9 7 5 10 5 + 557 1428 0 0 0 759 0 0 0 0 0 0 904 909 0 500 0 1016 0 1431 + 0 916 719 290 1306 0 19 543 64 599 891 67 150 548 609 0 1338 493 1399 739 + 8 97 86 87 82 81 86 93 65 3 53 72 92 8 22 81 24 92 8 83 + 53 6 1 40 96 54 73 22 100 92 71 99 40 31 21 45 19 33 28 37 + 10 10 2 10 4 7 5 9 5 8 8 10 8 9 6 3 8 8 9 6 + 8 1 1 9 9 1 8 4 10 4 1 1 10 5 2 4 5 3 9 6 + 378 913 0 0 534 0 91 0 0 461 0 191 0 48 690 945 0 0 0 0 + 0 140 0 0 261 719 927 0 0 0 0 881 241 0 0 0 0 0 0 675 + 7 95 56 62 98 66 47 95 63 17 43 4 58 89 50 92 67 76 56 69 + 2 26 23 46 3 38 93 37 40 67 86 59 7 19 1 35 31 54 14 88 + 4 5 5 9 4 6 5 2 4 4 1 8 7 2 10 9 4 9 2 9 + 4 9 4 1 5 4 3 1 3 9 1 4 2 8 3 7 6 9 7 9 + 266 139 686 373 572 37 0 376 716 0 558 0 0 0 0 0 0 0 63 0 + 473 0 819 0 316 0 634 151 0 574 52 0 0 0 796 0 665 0 140 401 + 81 57 60 48 52 13 56 70 89 43 72 73 71 86 88 8 3 32 61 32 + 19 27 28 1 81 66 1 78 80 2 73 27 34 36 13 16 90 27 70 16 + 10 3 2 4 10 9 10 10 2 9 2 10 7 7 8 10 10 8 1 9 + 8 9 6 3 5 8 4 2 5 1 7 5 7 9 3 5 2 2 2 2 + 0 350 627 0 383 122 936 804 0 875 357 252 0 0 0 0 111 0 935 0 + 871 0 0 0 0 0 0 131 282 0 0 416 848 0 0 542 247 0 376 0 + 15 21 8 96 50 55 94 15 72 12 94 23 81 26 60 20 4 86 22 7 + 98 32 86 91 46 59 38 78 60 14 42 80 82 98 41 13 79 63 53 93 + 8 5 2 9 9 4 9 1 3 2 2 9 6 9 3 4 2 3 7 6 + 3 7 10 9 10 8 6 5 2 3 7 8 7 2 2 3 1 2 4 7 + 0 740 0 0 571 272 0 0 0 0 0 870 845 765 668 208 0 281 745 0 + 0 0 0 799 555 537 595 752 572 0 41 0 258 61 0 0 933 1021 0 778 + 26 78 87 57 31 82 20 28 71 10 41 1 69 94 71 21 74 17 12 37 + 63 19 43 1 44 89 23 69 49 74 86 99 41 69 47 52 10 25 97 93 + 7 8 5 8 6 9 3 4 8 8 4 8 4 5 1 6 5 7 8 3 + 10 5 7 6 6 7 9 10 9 8 2 6 3 6 4 10 2 9 9 5 + 506 0 0 197 0 0 772 818 660 0 801 420 0 593 0 249 0 0 292 0 + 469 122 0 719 42 152 0 824 241 0 0 966 0 0 0 0 430 0 814 0 + diff --git a/examples/cpp/variable_intervals_sat_test.bintest b/examples/cpp/variable_intervals_sat_test.bintest new file mode 100644 index 00000000000..51d3dbf2eb0 --- /dev/null +++ b/examples/cpp/variable_intervals_sat_test.bintest @@ -0,0 +1 @@ +RUN: $(variable_intervals_sat) diff --git a/examples/cpp/weighted_tardiness_sat_test.bintest b/examples/cpp/weighted_tardiness_sat_test.bintest new file mode 100644 index 00000000000..5c95404e752 --- /dev/null +++ b/examples/cpp/weighted_tardiness_sat_test.bintest @@ -0,0 +1 @@ +RUN: $(weighted_tardiness_sat) --input $(wt40.txt) diff --git a/examples/python/BUILD.bazel b/examples/python/BUILD.bazel index 03313e71d66..3798011fd23 100644 --- a/examples/python/BUILD.bazel +++ b/examples/python/BUILD.bazel @@ -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"}, ) diff --git a/examples/python/CMakeBazel.txt b/examples/python/CMakeBazel.txt new file mode 100644 index 00000000000..5aacc949071 --- /dev/null +++ b/examples/python/CMakeBazel.txt @@ -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=$ +) + +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=$ +) + +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=$ +) + +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=$ +) + +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=$ +) + +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=$ +) + +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=$ +) + +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=$ +) + +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=$ +) + +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=$ +) + +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=$ +) + +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=$ +) + +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=$ +) + +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=$ +) + +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=$ +) + +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=$ 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=$ +) + +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=$ +) + +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=$ +) + +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=$ +) + +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=$ +) + +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=$ +) + +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=$ +) + +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=$ +) + +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=$ +) + +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=$ 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=$ 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=$ +) + +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=$ +) + +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=$ +) + +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=$ +) + +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=$ +) + +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=$ +) + +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=$ +) + +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=$ +) + +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=$ +) + +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=$ +) + +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=$ +) + +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=$ +) \ No newline at end of file diff --git a/examples/python/CMakeLists.txt b/examples/python/CMakeLists.txt index 85f6eff8165..14c812cd906 100644 --- a/examples/python/CMakeLists.txt +++ b/examples/python/CMakeLists.txt @@ -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() diff --git a/examples/python/appointments_py_test.bintest b/examples/python/appointments_py_test.bintest new file mode 100644 index 00000000000..367623a5cf7 --- /dev/null +++ b/examples/python/appointments_py_test.bintest @@ -0,0 +1 @@ +RUN: $(appointments_py3) diff --git a/examples/python/assignment_with_constraints_sat_py_test.bintest b/examples/python/assignment_with_constraints_sat_py_test.bintest new file mode 100644 index 00000000000..340ba4e3099 --- /dev/null +++ b/examples/python/assignment_with_constraints_sat_py_test.bintest @@ -0,0 +1 @@ +RUN: $(assignment_with_constraints_sat_py3) diff --git a/examples/python/balance_group_sat_py_test.bintest b/examples/python/balance_group_sat_py_test.bintest new file mode 100644 index 00000000000..97692558c5e --- /dev/null +++ b/examples/python/balance_group_sat_py_test.bintest @@ -0,0 +1 @@ +RUN: $(balance_group_sat_py3) diff --git a/examples/python/bus_driver_scheduling_sat_py_test.bintest b/examples/python/bus_driver_scheduling_sat_py_test.bintest new file mode 100644 index 00000000000..229e11c4102 --- /dev/null +++ b/examples/python/bus_driver_scheduling_sat_py_test.bintest @@ -0,0 +1 @@ +RUN: $(bus_driver_scheduling_sat_py3) --params=max_time_in_seconds:40 diff --git a/examples/python/car_sequencing_optimization_sat_py_test.bintest b/examples/python/car_sequencing_optimization_sat_py_test.bintest new file mode 100644 index 00000000000..6a95ef22b14 --- /dev/null +++ b/examples/python/car_sequencing_optimization_sat_py_test.bintest @@ -0,0 +1 @@ +RUN: $(car_sequencing_optimization_sat_py3) diff --git a/examples/python/chemical_balance_sat_py_test.bintest b/examples/python/chemical_balance_sat_py_test.bintest new file mode 100644 index 00000000000..470ce4c8558 --- /dev/null +++ b/examples/python/chemical_balance_sat_py_test.bintest @@ -0,0 +1 @@ +RUN: $(chemical_balance_sat_py3) diff --git a/examples/python/clustering_sat_py_test.bintest b/examples/python/clustering_sat_py_test.bintest new file mode 100644 index 00000000000..8f29b62d31d --- /dev/null +++ b/examples/python/clustering_sat_py_test.bintest @@ -0,0 +1 @@ +RUN: $(clustering_sat_py3) diff --git a/examples/python/cover_rectangle_sat_py_test.bintest b/examples/python/cover_rectangle_sat_py_test.bintest new file mode 100644 index 00000000000..0022f144797 --- /dev/null +++ b/examples/python/cover_rectangle_sat_py_test.bintest @@ -0,0 +1 @@ +RUN: $(cover_rectangle_sat_py3) diff --git a/examples/python/cryptarithm_sat_py_test.bintest b/examples/python/cryptarithm_sat_py_test.bintest new file mode 100644 index 00000000000..29accdda2b1 --- /dev/null +++ b/examples/python/cryptarithm_sat_py_test.bintest @@ -0,0 +1 @@ +RUN: $(cryptarithm_sat_py3) diff --git a/examples/python/cvrptw_plot.py b/examples/python/cvrptw_plot.py deleted file mode 100644 index 7ccab15ab06..00000000000 --- a/examples/python/cvrptw_plot.py +++ /dev/null @@ -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.routing import 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 = ( - 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() diff --git a/examples/python/flexible_job_shop_sat_py_test.bintest b/examples/python/flexible_job_shop_sat_py_test.bintest new file mode 100644 index 00000000000..eed68ac56e1 --- /dev/null +++ b/examples/python/flexible_job_shop_sat_py_test.bintest @@ -0,0 +1 @@ +RUN: $(flexible_job_shop_sat_py3) diff --git a/examples/python/gate_scheduling_sat.py b/examples/python/gate_scheduling_sat.py index 9cea61deb76..84c6a083d38 100644 --- a/examples/python/gate_scheduling_sat.py +++ b/examples/python/gate_scheduling_sat.py @@ -24,9 +24,8 @@ """ 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: diff --git a/examples/python/gate_scheduling_sat_py_test.bintest b/examples/python/gate_scheduling_sat_py_test.bintest new file mode 100644 index 00000000000..279fc094b0d --- /dev/null +++ b/examples/python/gate_scheduling_sat_py_test.bintest @@ -0,0 +1 @@ +RUN: $(gate_scheduling_sat_py3) diff --git a/examples/python/golomb8_py_test.bintest b/examples/python/golomb8_py_test.bintest new file mode 100644 index 00000000000..e65bf5dc1ec --- /dev/null +++ b/examples/python/golomb8_py_test.bintest @@ -0,0 +1 @@ +RUN: $(golomb8_py3) diff --git a/examples/python/golomb_sat_py_test.bintest b/examples/python/golomb_sat_py_test.bintest new file mode 100644 index 00000000000..3ab2199d13a --- /dev/null +++ b/examples/python/golomb_sat_py_test.bintest @@ -0,0 +1 @@ +RUN: $(golomb_sat_py3) diff --git a/examples/python/hidato_sat_py_test.bintest b/examples/python/hidato_sat_py_test.bintest new file mode 100644 index 00000000000..a5e01c6f508 --- /dev/null +++ b/examples/python/hidato_sat_py_test.bintest @@ -0,0 +1 @@ +RUN: $(hidato_sat_py3) diff --git a/examples/python/horse_jumping_show.py b/examples/python/horse_jumping_show.py new file mode 100644 index 00000000000..bff393a6fe3 --- /dev/null +++ b/examples/python/horse_jumping_show.py @@ -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) diff --git a/examples/python/horse_jumping_show_py_test.bintest b/examples/python/horse_jumping_show_py_test.bintest new file mode 100644 index 00000000000..d8ed39bb39d --- /dev/null +++ b/examples/python/horse_jumping_show_py_test.bintest @@ -0,0 +1,2 @@ +RUN: $(horse_jumping_show_py3) +CHECK: "Day 3: C_1.60m_Grand_Prix" diff --git a/examples/python/integer_programming_py_test.bintest b/examples/python/integer_programming_py_test.bintest new file mode 100644 index 00000000000..b6741b163f4 --- /dev/null +++ b/examples/python/integer_programming_py_test.bintest @@ -0,0 +1 @@ +RUN: $(integer_programming_py3) diff --git a/examples/python/jobshop_ft06_distance_sat_py_test.bintest b/examples/python/jobshop_ft06_distance_sat_py_test.bintest new file mode 100644 index 00000000000..311b9f8eff9 --- /dev/null +++ b/examples/python/jobshop_ft06_distance_sat_py_test.bintest @@ -0,0 +1 @@ +RUN: $(jobshop_ft06_distance_sat_py3) diff --git a/examples/python/jobshop_ft06_sat_py_test.bintest b/examples/python/jobshop_ft06_sat_py_test.bintest new file mode 100644 index 00000000000..3a2dd1304d3 --- /dev/null +++ b/examples/python/jobshop_ft06_sat_py_test.bintest @@ -0,0 +1 @@ +RUN: $(jobshop_ft06_sat_py3) diff --git a/examples/python/jobshop_with_maintenance_sat_py_test.bintest b/examples/python/jobshop_with_maintenance_sat_py_test.bintest new file mode 100644 index 00000000000..c8fe05b4a15 --- /dev/null +++ b/examples/python/jobshop_with_maintenance_sat_py_test.bintest @@ -0,0 +1 @@ +RUN: $(jobshop_with_maintenance_sat_py3) diff --git a/examples/python/knapsack_2d_sat_py_test.bintest b/examples/python/knapsack_2d_sat_py_test.bintest new file mode 100644 index 00000000000..4ab15a54c1c --- /dev/null +++ b/examples/python/knapsack_2d_sat_py_test.bintest @@ -0,0 +1 @@ +RUN: $(knapsack_2d_sat_py3) diff --git a/examples/python/line_balancing_sat_salbp_20_1_py_test.bintest b/examples/python/line_balancing_sat_salbp_20_1_py_test.bintest new file mode 100644 index 00000000000..10d11bef994 --- /dev/null +++ b/examples/python/line_balancing_sat_salbp_20_1_py_test.bintest @@ -0,0 +1,2 @@ +RUN: $(line_balancing_sat_py3) --input=$(salbp_20_1.alb) +CHECK: "objective: 3" diff --git a/examples/python/linear_assignment_api_py_test.bintest b/examples/python/linear_assignment_api_py_test.bintest new file mode 100644 index 00000000000..aa508f3bceb --- /dev/null +++ b/examples/python/linear_assignment_api_py_test.bintest @@ -0,0 +1 @@ +RUN: $(linear_assignment_api_py3) diff --git a/examples/python/linear_programming_py_test.bintest b/examples/python/linear_programming_py_test.bintest new file mode 100644 index 00000000000..6c7b0be1bea --- /dev/null +++ b/examples/python/linear_programming_py_test.bintest @@ -0,0 +1 @@ +RUN: $(linear_programming_py3) diff --git a/examples/python/magic_sequence_distribute_py_test.bintest b/examples/python/magic_sequence_distribute_py_test.bintest new file mode 100644 index 00000000000..8ab9383e2be --- /dev/null +++ b/examples/python/magic_sequence_distribute_py_test.bintest @@ -0,0 +1 @@ +RUN: $(magic_sequence_distribute_py3) diff --git a/examples/python/magic_sequence_distribute_with_arg_py_test.bintest b/examples/python/magic_sequence_distribute_with_arg_py_test.bintest new file mode 100644 index 00000000000..b200a93a358 --- /dev/null +++ b/examples/python/magic_sequence_distribute_with_arg_py_test.bintest @@ -0,0 +1 @@ +RUN: $(magic_sequence_distribute_py3) 5 diff --git a/examples/python/maximize_combinations_sat_py_test.bintest b/examples/python/maximize_combinations_sat_py_test.bintest new file mode 100644 index 00000000000..cb4b60da6b8 --- /dev/null +++ b/examples/python/maximize_combinations_sat_py_test.bintest @@ -0,0 +1 @@ +RUN: $(maximize_combinations_sat_py3) diff --git a/examples/python/maze_escape_sat_py_test.bintest b/examples/python/maze_escape_sat_py_test.bintest new file mode 100644 index 00000000000..201abe1c108 --- /dev/null +++ b/examples/python/maze_escape_sat_py_test.bintest @@ -0,0 +1 @@ +RUN: $(maze_escape_sat_py3) diff --git a/examples/python/memory_layout_and_infeasibility_sat_py_test.bintest b/examples/python/memory_layout_and_infeasibility_sat_py_test.bintest new file mode 100644 index 00000000000..98284d635a2 --- /dev/null +++ b/examples/python/memory_layout_and_infeasibility_sat_py_test.bintest @@ -0,0 +1 @@ +RUN: $(memory_layout_and_infeasibility_sat_py3) diff --git a/examples/python/music_playlist_sat_py_test.bintest b/examples/python/music_playlist_sat_py_test.bintest new file mode 100644 index 00000000000..d0947fd3058 --- /dev/null +++ b/examples/python/music_playlist_sat_py_test.bintest @@ -0,0 +1 @@ +RUN: $(music_playlist_sat_py3) diff --git a/examples/python/no_wait_baking_scheduling_sat_py_test.bintest b/examples/python/no_wait_baking_scheduling_sat_py_test.bintest new file mode 100644 index 00000000000..1e8edde5a0f --- /dev/null +++ b/examples/python/no_wait_baking_scheduling_sat_py_test.bintest @@ -0,0 +1 @@ +RUN: $(no_wait_baking_scheduling_sat_py3) diff --git a/examples/python/nqueens_sat_py_test.bintest b/examples/python/nqueens_sat_py_test.bintest new file mode 100644 index 00000000000..5d7cc4f6e3e --- /dev/null +++ b/examples/python/nqueens_sat_py_test.bintest @@ -0,0 +1 @@ +RUN: $(nqueens_sat_py3) diff --git a/examples/python/pell_equation_sat_py_test.bintest b/examples/python/pell_equation_sat_py_test.bintest new file mode 100644 index 00000000000..7d83a74a24a --- /dev/null +++ b/examples/python/pell_equation_sat_py_test.bintest @@ -0,0 +1 @@ +RUN: $(pell_equation_sat_py3) diff --git a/examples/python/pentominoes_sat_py_test.bintest b/examples/python/pentominoes_sat_py_test.bintest new file mode 100644 index 00000000000..3ce95b060ca --- /dev/null +++ b/examples/python/pentominoes_sat_py_test.bintest @@ -0,0 +1 @@ +RUN: $(pentominoes_sat_py3) diff --git a/examples/python/prize_collecting_tsp_sat_py_test.bintest b/examples/python/prize_collecting_tsp_sat_py_test.bintest new file mode 100644 index 00000000000..1ed6cb26485 --- /dev/null +++ b/examples/python/prize_collecting_tsp_sat_py_test.bintest @@ -0,0 +1 @@ +RUN: $(prize_collecting_tsp_sat_py3) diff --git a/examples/python/prize_collecting_vrp_sat_py_test.bintest b/examples/python/prize_collecting_vrp_sat_py_test.bintest new file mode 100644 index 00000000000..1789eec0fde --- /dev/null +++ b/examples/python/prize_collecting_vrp_sat_py_test.bintest @@ -0,0 +1 @@ +RUN: $(prize_collecting_vrp_sat_py3) diff --git a/examples/python/pyflow_example_py_test.bintest b/examples/python/pyflow_example_py_test.bintest new file mode 100644 index 00000000000..a9af12919e7 --- /dev/null +++ b/examples/python/pyflow_example_py_test.bintest @@ -0,0 +1 @@ +RUN: $(pyflow_example_py3) diff --git a/examples/python/qubo_sat_py_test.bintest b/examples/python/qubo_sat_py_test.bintest new file mode 100644 index 00000000000..6dc654a1216 --- /dev/null +++ b/examples/python/qubo_sat_py_test.bintest @@ -0,0 +1 @@ +RUN: $(qubo_sat_py3) diff --git a/examples/python/rcpsp_sat_c1510_1_py_test.bintest b/examples/python/rcpsp_sat_c1510_1_py_test.bintest new file mode 100644 index 00000000000..48a07a05544 --- /dev/null +++ b/examples/python/rcpsp_sat_c1510_1_py_test.bintest @@ -0,0 +1,2 @@ +RUN: $(rcpsp_sat_py3) --input=$(c1510_1.mm.txt) +CHECK: "objective: 21" diff --git a/examples/python/rcpsp_sat_j301_1_py_test.bintest b/examples/python/rcpsp_sat_j301_1_py_test.bintest new file mode 100644 index 00000000000..9df595ccd46 --- /dev/null +++ b/examples/python/rcpsp_sat_j301_1_py_test.bintest @@ -0,0 +1,2 @@ +RUN: $(rcpsp_sat_py3) --input=$(j301_1.sm) +CHECK: "objective: 43" diff --git a/examples/python/rcpsp_sat_rip1_py_test.bintest b/examples/python/rcpsp_sat_rip1_py_test.bintest new file mode 100644 index 00000000000..3b3f0c7dfb2 --- /dev/null +++ b/examples/python/rcpsp_sat_rip1_py_test.bintest @@ -0,0 +1,2 @@ +RUN: $(rcpsp_sat_py3) --input=$(rip1.sch) +CHECK: "objective: 100" diff --git a/examples/python/rcpsp_sat_testset_mm30_psp3_py_test.bintest b/examples/python/rcpsp_sat_testset_mm30_psp3_py_test.bintest new file mode 100644 index 00000000000..801590ce88e --- /dev/null +++ b/examples/python/rcpsp_sat_testset_mm30_psp3_py_test.bintest @@ -0,0 +1 @@ +RUN: $(rcpsp_sat_py3) --input=$(testset_mm30_psp3.sch) --params=max_time_in_seconds:8.0 diff --git a/examples/python/rcpsp_sat_ubo_10_psp2_py_test.bintest b/examples/python/rcpsp_sat_ubo_10_psp2_py_test.bintest new file mode 100644 index 00000000000..47fad4820be --- /dev/null +++ b/examples/python/rcpsp_sat_ubo_10_psp2_py_test.bintest @@ -0,0 +1,2 @@ +RUN: $(rcpsp_sat_py3) --input=$(ubo_10_psp2.sch) +CHECK: "objective: 45" diff --git a/examples/python/shift_scheduling_sat_py_test.bintest b/examples/python/shift_scheduling_sat_py_test.bintest new file mode 100644 index 00000000000..a54e4fa4b57 --- /dev/null +++ b/examples/python/shift_scheduling_sat_py_test.bintest @@ -0,0 +1 @@ +RUN: $(shift_scheduling_sat_py3) --params=max_time_in_seconds:10 diff --git a/examples/python/single_machine_scheduling_with_setup_release_due_dates_sat_py_test.bintest b/examples/python/single_machine_scheduling_with_setup_release_due_dates_sat_py_test.bintest new file mode 100644 index 00000000000..62c965b85c5 --- /dev/null +++ b/examples/python/single_machine_scheduling_with_setup_release_due_dates_sat_py_test.bintest @@ -0,0 +1 @@ +RUN: $(single_machine_scheduling_with_setup_release_due_dates_sat_py3) diff --git a/examples/python/spillover_sat.py b/examples/python/spillover_sat.py new file mode 100644 index 00000000000..02609f30b76 --- /dev/null +++ b/examples/python/spillover_sat.py @@ -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) diff --git a/examples/python/spillover_sat_test_py_test.bintest b/examples/python/spillover_sat_test_py_test.bintest new file mode 100644 index 00000000000..9a0b29ef22c --- /dev/null +++ b/examples/python/spillover_sat_test_py_test.bintest @@ -0,0 +1,2 @@ +RUN: $(spillover_sat) --test_data +CHECK: "objective: 360.0" diff --git a/examples/python/spread_robots_sat_py_test.bintest b/examples/python/spread_robots_sat_py_test.bintest new file mode 100644 index 00000000000..038d178ca81 --- /dev/null +++ b/examples/python/spread_robots_sat_py_test.bintest @@ -0,0 +1 @@ +RUN: $(spread_robots_sat_py3) diff --git a/examples/python/steel_mill_slab_sat_py_test.bintest b/examples/python/steel_mill_slab_sat_py_test.bintest new file mode 100644 index 00000000000..0f4fdecb94b --- /dev/null +++ b/examples/python/steel_mill_slab_sat_py_test.bintest @@ -0,0 +1 @@ +RUN: $(steel_mill_slab_sat_py3) diff --git a/examples/python/sudoku_sat_py_test.bintest b/examples/python/sudoku_sat_py_test.bintest new file mode 100644 index 00000000000..d65f3ef7012 --- /dev/null +++ b/examples/python/sudoku_sat_py_test.bintest @@ -0,0 +1 @@ +RUN: $(sudoku_sat_py3) diff --git a/examples/python/task_allocation_sat.py b/examples/python/task_allocation_sat.py index 381ebe2cf1c..c35b9c6e20e 100644 --- a/examples/python/task_allocation_sat.py +++ b/examples/python/task_allocation_sat.py @@ -284,7 +284,7 @@ def task_allocation_sat() -> None: solver = cp_model.CpSolver() # Uses the portfolion of heuristics. solver.parameters.log_search_progress = True - solver.parameters.num_workers = 16 + solver.parameters.num_search_workers = 16 solver.solve(model) diff --git a/examples/python/task_allocation_sat_py_test.bintest b/examples/python/task_allocation_sat_py_test.bintest new file mode 100644 index 00000000000..b2510011e77 --- /dev/null +++ b/examples/python/task_allocation_sat_py_test.bintest @@ -0,0 +1 @@ +RUN: $(task_allocation_sat_py3) diff --git a/examples/python/tasks_and_workers_assignment_sat_py_test.bintest b/examples/python/tasks_and_workers_assignment_sat_py_test.bintest new file mode 100644 index 00000000000..e5713b78e4b --- /dev/null +++ b/examples/python/tasks_and_workers_assignment_sat_py_test.bintest @@ -0,0 +1 @@ +RUN: $(tasks_and_workers_assignment_sat_py3) diff --git a/examples/python/test_scheduling_sat_py_test.bintest b/examples/python/test_scheduling_sat_py_test.bintest new file mode 100644 index 00000000000..71bd923c0fd --- /dev/null +++ b/examples/python/test_scheduling_sat_py_test.bintest @@ -0,0 +1 @@ +RUN: $(test_scheduling_sat_py3) diff --git a/examples/python/tsp_norandom_py_test.bintest b/examples/python/tsp_norandom_py_test.bintest new file mode 100644 index 00000000000..13fc0ce75da --- /dev/null +++ b/examples/python/tsp_norandom_py_test.bintest @@ -0,0 +1 @@ +RUN: $(tsp_py3) --tsp_use_random_matrix=false diff --git a/examples/python/tsp_py_test.bintest b/examples/python/tsp_py_test.bintest new file mode 100644 index 00000000000..b60c8f2cb33 --- /dev/null +++ b/examples/python/tsp_py_test.bintest @@ -0,0 +1 @@ +RUN: $(tsp_py3) diff --git a/examples/python/tsp_sat_py_test.bintest b/examples/python/tsp_sat_py_test.bintest new file mode 100644 index 00000000000..538bc482ea1 --- /dev/null +++ b/examples/python/tsp_sat_py_test.bintest @@ -0,0 +1 @@ +RUN: $(tsp_sat_py3) diff --git a/examples/python/vendor_scheduling_sat_py_test.bintest b/examples/python/vendor_scheduling_sat_py_test.bintest new file mode 100644 index 00000000000..7fbd519cd67 --- /dev/null +++ b/examples/python/vendor_scheduling_sat_py_test.bintest @@ -0,0 +1 @@ +RUN: $(vendor_scheduling_sat_py3) diff --git a/examples/python/wedding_optimal_chart_sat_py_test.bintest b/examples/python/wedding_optimal_chart_sat_py_test.bintest new file mode 100644 index 00000000000..c33c3fd0611 --- /dev/null +++ b/examples/python/wedding_optimal_chart_sat_py_test.bintest @@ -0,0 +1 @@ +RUN: $(wedding_optimal_chart_sat_py3) diff --git a/examples/python/weighted_latency_problem_sat_py_test.bintest b/examples/python/weighted_latency_problem_sat_py_test.bintest new file mode 100644 index 00000000000..97347db8dc4 --- /dev/null +++ b/examples/python/weighted_latency_problem_sat_py_test.bintest @@ -0,0 +1 @@ +RUN: $(weighted_latency_problem_sat_py3) diff --git a/examples/python/zebra_sat_py_test.bintest b/examples/python/zebra_sat_py_test.bintest new file mode 100644 index 00000000000..92374872f19 --- /dev/null +++ b/examples/python/zebra_sat_py_test.bintest @@ -0,0 +1 @@ +RUN: $(zebra_sat_py3) diff --git a/ortools/algorithms/samples/BUILD.bazel b/ortools/algorithms/samples/BUILD.bazel index a845ec9aec4..4152d7ced52 100644 --- a/ortools/algorithms/samples/BUILD.bazel +++ b/ortools/algorithms/samples/BUILD.bazel @@ -14,7 +14,7 @@ load("@rules_cc//cc:cc_test.bzl", "cc_test") load("@rules_java//java:java_binary.bzl", "java_binary") load("@rules_python//python:py_test.bzl", "py_test") -load("//bazel:run_binary_test.bzl", "run_binary_test") +load("//tools/testing:bintest.bzl", "bintest") package(default_visibility = ["//visibility:public"]) @@ -65,7 +65,8 @@ java_binary( ], ) -run_binary_test( +bintest( name = "KnapsackTest", - binary = ":Knapsack", + srcs = [":KnapsackTest.bintest"], + named_data = {"Knapsack": ":Knapsack"}, ) diff --git a/ortools/algorithms/samples/KnapsackTest.bintest b/ortools/algorithms/samples/KnapsackTest.bintest new file mode 100644 index 00000000000..759db770f5c --- /dev/null +++ b/ortools/algorithms/samples/KnapsackTest.bintest @@ -0,0 +1 @@ +RUN: $(Knapsack) diff --git a/ortools/glop/samples/BUILD.bazel b/ortools/glop/samples/BUILD.bazel index cddf4f30b16..7afa51de2e6 100644 --- a/ortools/glop/samples/BUILD.bazel +++ b/ortools/glop/samples/BUILD.bazel @@ -12,7 +12,7 @@ # limitations under the License. load("@rules_cc//cc:cc_binary.bzl", "cc_binary") -load("//bazel:run_binary_test.bzl", "run_binary_test") +load("//tools/testing:bintest.bzl", "bintest") package(default_visibility = ["//visibility:public"]) @@ -28,8 +28,9 @@ cc_binary( ], ) -run_binary_test( +bintest( name = "simple_glop_program_cc_test", size = "small", - binary = ":simple_glop_program_cc", + srcs = [":simple_glop_program_cc_test.bintest"], + named_data = {"simple_glop_program_cc": ":simple_glop_program_cc"}, ) diff --git a/ortools/glop/samples/simple_glop_program_cc_test.bintest b/ortools/glop/samples/simple_glop_program_cc_test.bintest new file mode 100644 index 00000000000..d17178feb41 --- /dev/null +++ b/ortools/glop/samples/simple_glop_program_cc_test.bintest @@ -0,0 +1 @@ +RUN: $(simple_glop_program_cc) diff --git a/ortools/graph/samples/AssignmentLinearSumAssignmentTest.bintest b/ortools/graph/samples/AssignmentLinearSumAssignmentTest.bintest new file mode 100644 index 00000000000..d6d7060d7ae --- /dev/null +++ b/ortools/graph/samples/AssignmentLinearSumAssignmentTest.bintest @@ -0,0 +1 @@ +RUN: $(AssignmentLinearSumAssignment) diff --git a/ortools/graph/samples/AssignmentMinFlowTest.bintest b/ortools/graph/samples/AssignmentMinFlowTest.bintest new file mode 100644 index 00000000000..4d3c0911d2a --- /dev/null +++ b/ortools/graph/samples/AssignmentMinFlowTest.bintest @@ -0,0 +1 @@ +RUN: $(AssignmentMinFlow) diff --git a/ortools/graph/samples/BUILD.bazel b/ortools/graph/samples/BUILD.bazel index 37d8c63ff0b..8f96ea4c25c 100644 --- a/ortools/graph/samples/BUILD.bazel +++ b/ortools/graph/samples/BUILD.bazel @@ -16,7 +16,7 @@ load("@rules_cc//cc:cc_binary.bzl", "cc_binary") load("@rules_cc//cc:cc_test.bzl", "cc_test") load("@rules_java//java:java_binary.bzl", "java_binary") load("@rules_python//python:py_test.bzl", "py_test") -load("//bazel:run_binary_test.bzl", "run_binary_test") +load("//tools/testing:bintest.bzl", "bintest") package(default_visibility = ["//visibility:public"]) @@ -52,9 +52,10 @@ java_binary( ], ) -run_binary_test( +bintest( name = "AssignmentLinearSumAssignmentTest", - binary = ":AssignmentLinearSumAssignment", + srcs = [":AssignmentLinearSumAssignmentTest.bintest"], + named_data = {"AssignmentLinearSumAssignment": ":AssignmentLinearSumAssignment"}, ) cc_test( @@ -86,9 +87,10 @@ java_binary( ], ) -run_binary_test( +bintest( name = "AssignmentMinFlowTest", - binary = ":AssignmentMinFlow", + srcs = [":AssignmentMinFlowTest.bintest"], + named_data = {"AssignmentMinFlow": ":AssignmentMinFlow"}, ) cc_test( @@ -120,9 +122,10 @@ java_binary( ], ) -run_binary_test( +bintest( name = "BalanceMinFlowTest", - binary = ":BalanceMinFlow", + srcs = [":BalanceMinFlowTest.bintest"], + named_data = {"BalanceMinFlow": ":BalanceMinFlow"}, ) cc_test( @@ -157,9 +160,10 @@ java_binary( ], ) -run_binary_test( +bintest( name = "SimpleMaxFlowProgramTest", - binary = ":SimpleMaxFlowProgram", + srcs = [":SimpleMaxFlowProgramTest.bintest"], + named_data = {"SimpleMaxFlowProgram": ":SimpleMaxFlowProgram"}, ) cc_test( @@ -194,9 +198,10 @@ java_binary( ], ) -run_binary_test( +bintest( name = "SimpleMinCostFlowProgramTest", - binary = ":SimpleMinCostFlowProgram", + srcs = [":SimpleMinCostFlowProgramTest.bintest"], + named_data = {"SimpleMinCostFlowProgram": ":SimpleMinCostFlowProgram"}, ) cc_binary( @@ -209,10 +214,10 @@ cc_binary( ], ) -run_binary_test( +bintest( name = "dijkstra_directed_test", - binary = ":dijkstra_directed", - grep_lines = ["Shortest path length: 8"], + srcs = [":dijkstra_directed_test.bintest"], + named_data = {"dijkstra_directed": ":dijkstra_directed"}, ) cc_binary( @@ -225,10 +230,10 @@ cc_binary( ], ) -run_binary_test( +bintest( name = "dijkstra_undirected_test", - binary = ":dijkstra_undirected", - grep_lines = ["Shortest path length: 4"], + srcs = [":dijkstra_undirected_test.bintest"], + named_data = {"dijkstra_undirected": ":dijkstra_undirected"}, ) cc_binary( @@ -242,14 +247,10 @@ cc_binary( ], ) -run_binary_test( +bintest( name = "dijkstra_one_to_all_test", - binary = ":dijkstra_one_to_all", - grep_lines = [ - "Distance to 1: 2", - "Distance to 2: 6", - "Distance to 3: 2", - ], + srcs = [":dijkstra_one_to_all_test.bintest"], + named_data = {"dijkstra_one_to_all": ":dijkstra_one_to_all"}, ) cc_binary( @@ -264,15 +265,10 @@ cc_binary( ], ) -run_binary_test( +bintest( name = "dijkstra_sequential_test", - binary = ":dijkstra_sequential", - grep_lines = [ - "Initial distance: 200", - "Distance_2_4: 2", - "Distance_8_1: 3", - "Distance_3_7: 4", - ], + srcs = [":dijkstra_sequential_test.bintest"], + named_data = {"dijkstra_sequential": ":dijkstra_sequential"}, ) cc_binary( @@ -290,9 +286,10 @@ cc_binary( ], ) -run_binary_test( +bintest( name = "dijkstra_all_pairs_shortest_paths_test", - binary = ":dijkstra_all_pairs_shortest_paths", + srcs = [":dijkstra_all_pairs_shortest_paths_test.bintest"], + named_data = {"dijkstra_all_pairs_shortest_paths": ":dijkstra_all_pairs_shortest_paths"}, ) cc_binary( @@ -305,10 +302,10 @@ cc_binary( ], ) -run_binary_test( +bintest( name = "dag_simple_shortest_path_test", - binary = ":dag_simple_shortest_path", - grep_lines = ["Shortest path length: 2"], + srcs = [":dag_simple_shortest_path_test.bintest"], + named_data = {"dag_simple_shortest_path": ":dag_simple_shortest_path"}, ) cc_binary( @@ -326,10 +323,10 @@ cc_binary( ], ) -run_binary_test( +bintest( name = "dag_shortest_path_one_to_all_test", - binary = ":dag_shortest_path_one_to_all", - grep_lines = ["Length of shortest path to node 4: 2"], + srcs = [":dag_shortest_path_one_to_all_test.bintest"], + named_data = {"dag_shortest_path_one_to_all": ":dag_shortest_path_one_to_all"}, ) cc_binary( @@ -343,15 +340,10 @@ cc_binary( ], ) -run_binary_test( +bintest( name = "dag_shortest_path_sequential_test", - binary = ":dag_shortest_path_sequential", - grep_lines = [ - "Initial distance: 200", - "Distance_2_4: 2", - "Distance_8_1: 100", - "Distance_3_7: 4", - ], + srcs = [":dag_shortest_path_sequential_test.bintest"], + named_data = {"dag_shortest_path_sequential": ":dag_shortest_path_sequential"}, ) cc_binary( @@ -367,10 +359,10 @@ cc_binary( ], ) -run_binary_test( +bintest( name = "bfs_directed_test", - binary = ":bfs_directed", - grep_lines = ["Shortest path length (in arcs): 2"], + srcs = [":bfs_directed_test.bintest"], + named_data = {"bfs_directed": ":bfs_directed"}, ) cc_binary( @@ -386,10 +378,10 @@ cc_binary( ], ) -run_binary_test( +bintest( name = "bfs_undirected_test", - binary = ":bfs_undirected", - grep_lines = ["Shortest path length (in arcs): 2"], + srcs = [":bfs_undirected_test.bintest"], + named_data = {"bfs_undirected": ":bfs_undirected"}, ) cc_binary( @@ -405,10 +397,10 @@ cc_binary( ], ) -run_binary_test( +bintest( name = "bfs_one_to_all_test", - binary = ":bfs_one_to_all", - grep_lines = ["Shortest path from 0 to 2 has length: 2"], + srcs = [":bfs_one_to_all_test.bintest"], + named_data = {"bfs_one_to_all": ":bfs_one_to_all"}, ) cc_binary( @@ -425,10 +417,10 @@ cc_binary( ], ) -run_binary_test( +bintest( name = "root_a_tree_test", - binary = ":root_a_tree", - grep_lines = ["Depths:\n 0 -> 2"], + srcs = [":root_a_tree_test.bintest"], + named_data = {"root_a_tree": ":root_a_tree"}, ) cc_binary( @@ -444,10 +436,10 @@ cc_binary( ], ) -run_binary_test( +bintest( name = "rooted_tree_paths_test", - binary = ":rooted_tree_paths", - grep_lines = ["0 -> 4 [0, 1, 4]"], + srcs = [":rooted_tree_paths_test.bintest"], + named_data = {"rooted_tree_paths": ":rooted_tree_paths"}, ) cc_binary( @@ -460,13 +452,10 @@ cc_binary( ], ) -run_binary_test( +bintest( name = "dag_simple_multiple_shortest_paths_test", - binary = ":dag_simple_multiple_shortest_paths", - grep_lines = [ - "#1 shortest path has length: 2", - "#2 shortest path has length: 3", - ], + srcs = [":dag_simple_multiple_shortest_paths_test.bintest"], + named_data = {"dag_simple_multiple_shortest_paths": ":dag_simple_multiple_shortest_paths"}, ) cc_binary( @@ -484,13 +473,10 @@ cc_binary( ], ) -run_binary_test( +bintest( name = "dag_multiple_shortest_paths_one_to_all_test", - binary = ":dag_multiple_shortest_paths_one_to_all", - grep_lines = [ - "\t#1 shortest path to node 4 has length: 2", - "\t#2 shortest path to node 4 has length: 3", - ], + srcs = [":dag_multiple_shortest_paths_one_to_all_test.bintest"], + named_data = {"dag_multiple_shortest_paths_one_to_all": ":dag_multiple_shortest_paths_one_to_all"}, ) cc_binary( @@ -504,21 +490,10 @@ cc_binary( ], ) -run_binary_test( +bintest( name = "dag_multiple_shortest_paths_sequential_test", - binary = ":dag_multiple_shortest_paths_sequential", - grep_lines = [ - "\t#1 shortest path has length: 200", - "\t#2 shortest path has length: 202", - "\t#1 shortest path (2, 4) has length: 20", - "\t#2 shortest path (2, 4) has length: 102", - "\t#1 shortest path (8, 1) has length: 101", - "\t#2 shortest path (8, 1) has length: 108", - "\t#1 shortest path (3, 3) has length: 0", - "\t#2 shortest path (3, 3) has length: 112", - "\t#1 shortest path (0, 0) has length: 0", - "\t#2 shortest path (0, 0) has length: 111", - ], + srcs = [":dag_multiple_shortest_paths_sequential_test.bintest"], + named_data = {"dag_multiple_shortest_paths_sequential": ":dag_multiple_shortest_paths_sequential"}, ) cc_binary( @@ -532,10 +507,10 @@ cc_binary( ], ) -run_binary_test( +bintest( name = "dag_simple_constrained_shortest_path_test", - binary = ":dag_simple_constrained_shortest_path", - grep_lines = ["Constrained shortest path length: 4"], + srcs = [":dag_simple_constrained_shortest_path_test.bintest"], + named_data = {"dag_simple_constrained_shortest_path": ":dag_simple_constrained_shortest_path"}, ) cc_binary( @@ -549,13 +524,8 @@ cc_binary( ], ) -run_binary_test( +bintest( name = "dag_constrained_shortest_path_sequential_test", - binary = ":dag_constrained_shortest_path_sequential", - grep_lines = [ - "Initial distance: 200", - "Distance_2_3: 1", - "Distance_8_1: 100", - "Distance_3_7: 100", - ], + srcs = [":dag_constrained_shortest_path_sequential_test.bintest"], + named_data = {"dag_constrained_shortest_path_sequential": ":dag_constrained_shortest_path_sequential"}, ) diff --git a/ortools/graph/samples/BalanceMinFlowTest.bintest b/ortools/graph/samples/BalanceMinFlowTest.bintest new file mode 100644 index 00000000000..42a9ecd4e98 --- /dev/null +++ b/ortools/graph/samples/BalanceMinFlowTest.bintest @@ -0,0 +1 @@ +RUN: $(BalanceMinFlow) diff --git a/ortools/graph/samples/SimpleMaxFlowProgramTest.bintest b/ortools/graph/samples/SimpleMaxFlowProgramTest.bintest new file mode 100644 index 00000000000..0634bc265ae --- /dev/null +++ b/ortools/graph/samples/SimpleMaxFlowProgramTest.bintest @@ -0,0 +1 @@ +RUN: $(SimpleMaxFlowProgram) diff --git a/ortools/graph/samples/SimpleMinCostFlowProgramTest.bintest b/ortools/graph/samples/SimpleMinCostFlowProgramTest.bintest new file mode 100644 index 00000000000..f3ca754ec83 --- /dev/null +++ b/ortools/graph/samples/SimpleMinCostFlowProgramTest.bintest @@ -0,0 +1 @@ +RUN: $(SimpleMinCostFlowProgram) diff --git a/ortools/graph/samples/bfs_directed_test.bintest b/ortools/graph/samples/bfs_directed_test.bintest new file mode 100644 index 00000000000..0c243abb199 --- /dev/null +++ b/ortools/graph/samples/bfs_directed_test.bintest @@ -0,0 +1,2 @@ +RUN: $(bfs_directed) +CHECK: "Shortest path length (in arcs): 2" diff --git a/ortools/graph/samples/bfs_one_to_all_test.bintest b/ortools/graph/samples/bfs_one_to_all_test.bintest new file mode 100644 index 00000000000..2831ab5fed7 --- /dev/null +++ b/ortools/graph/samples/bfs_one_to_all_test.bintest @@ -0,0 +1,2 @@ +RUN: $(bfs_one_to_all) +CHECK: "Shortest path from 0 to 2 has length: 2" diff --git a/ortools/graph/samples/bfs_undirected_test.bintest b/ortools/graph/samples/bfs_undirected_test.bintest new file mode 100644 index 00000000000..e8c2534c4cd --- /dev/null +++ b/ortools/graph/samples/bfs_undirected_test.bintest @@ -0,0 +1,2 @@ +RUN: $(bfs_undirected) +CHECK: "Shortest path length (in arcs): 2" diff --git a/ortools/graph/samples/dag_constrained_shortest_path_sequential_test.bintest b/ortools/graph/samples/dag_constrained_shortest_path_sequential_test.bintest new file mode 100644 index 00000000000..9af86fd995c --- /dev/null +++ b/ortools/graph/samples/dag_constrained_shortest_path_sequential_test.bintest @@ -0,0 +1,5 @@ +RUN: $(dag_constrained_shortest_path_sequential) +CHECK: "Initial distance: 200" +CHECK: "Distance_2_3: 1" +CHECK: "Distance_8_1: 100" +CHECK: "Distance_3_7: 100" diff --git a/ortools/graph/samples/dag_multiple_shortest_paths_one_to_all_test.bintest b/ortools/graph/samples/dag_multiple_shortest_paths_one_to_all_test.bintest new file mode 100644 index 00000000000..cc9d8cfa7b8 --- /dev/null +++ b/ortools/graph/samples/dag_multiple_shortest_paths_one_to_all_test.bintest @@ -0,0 +1,3 @@ +RUN: $(dag_multiple_shortest_paths_one_to_all) +CHECK: "#1 shortest path to node 4 has length: 2" +CHECK: "#2 shortest path to node 4 has length: 3" diff --git a/ortools/graph/samples/dag_multiple_shortest_paths_sequential_test.bintest b/ortools/graph/samples/dag_multiple_shortest_paths_sequential_test.bintest new file mode 100644 index 00000000000..3f10b3e45fe --- /dev/null +++ b/ortools/graph/samples/dag_multiple_shortest_paths_sequential_test.bintest @@ -0,0 +1,11 @@ +RUN: $(dag_multiple_shortest_paths_sequential) +CHECK: "#1 shortest path has length: 200" +CHECK: "#2 shortest path has length: 202" +CHECK: "#1 shortest path (2, 4) has length: 20" +CHECK: "#2 shortest path (2, 4) has length: 102" +CHECK: "#1 shortest path (8, 1) has length: 101" +CHECK: "#2 shortest path (8, 1) has length: 108" +CHECK: "#1 shortest path (3, 3) has length: 0" +CHECK: "#2 shortest path (3, 3) has length: 112" +CHECK: "#1 shortest path (0, 0) has length: 0" +CHECK: "#2 shortest path (0, 0) has length: 111" diff --git a/ortools/graph/samples/dag_shortest_path_one_to_all_test.bintest b/ortools/graph/samples/dag_shortest_path_one_to_all_test.bintest new file mode 100644 index 00000000000..d7792066615 --- /dev/null +++ b/ortools/graph/samples/dag_shortest_path_one_to_all_test.bintest @@ -0,0 +1,2 @@ +RUN: $(dag_shortest_path_one_to_all) +CHECK: "Length of shortest path to node 4: 2" diff --git a/ortools/graph/samples/dag_shortest_path_sequential_test.bintest b/ortools/graph/samples/dag_shortest_path_sequential_test.bintest new file mode 100644 index 00000000000..e10fe2767bb --- /dev/null +++ b/ortools/graph/samples/dag_shortest_path_sequential_test.bintest @@ -0,0 +1,5 @@ +RUN: $(dag_shortest_path_sequential) +CHECK: "Initial distance: 200" +CHECK: "Distance_2_4: 2" +CHECK: "Distance_8_1: 100" +CHECK: "Distance_3_7: 4" diff --git a/ortools/graph/samples/dag_simple_constrained_shortest_path_test.bintest b/ortools/graph/samples/dag_simple_constrained_shortest_path_test.bintest new file mode 100644 index 00000000000..40a291cdae1 --- /dev/null +++ b/ortools/graph/samples/dag_simple_constrained_shortest_path_test.bintest @@ -0,0 +1,2 @@ +RUN: $(dag_simple_constrained_shortest_path) +CHECK: "Constrained shortest path length: 4" diff --git a/ortools/graph/samples/dag_simple_multiple_shortest_paths_test.bintest b/ortools/graph/samples/dag_simple_multiple_shortest_paths_test.bintest new file mode 100644 index 00000000000..89e39b96031 --- /dev/null +++ b/ortools/graph/samples/dag_simple_multiple_shortest_paths_test.bintest @@ -0,0 +1,3 @@ +RUN: $(dag_simple_multiple_shortest_paths) +CHECK: "#1 shortest path has length: 2" +CHECK: "#2 shortest path has length: 3" diff --git a/ortools/graph/samples/dag_simple_shortest_path_test.bintest b/ortools/graph/samples/dag_simple_shortest_path_test.bintest new file mode 100644 index 00000000000..37b5c4daf52 --- /dev/null +++ b/ortools/graph/samples/dag_simple_shortest_path_test.bintest @@ -0,0 +1,2 @@ +RUN: $(dag_simple_shortest_path) +CHECK: "Shortest path length: 2" diff --git a/ortools/graph/samples/dijkstra_all_pairs_shortest_paths_test.bintest b/ortools/graph/samples/dijkstra_all_pairs_shortest_paths_test.bintest new file mode 100644 index 00000000000..fff8e2c486b --- /dev/null +++ b/ortools/graph/samples/dijkstra_all_pairs_shortest_paths_test.bintest @@ -0,0 +1 @@ +RUN: $(dijkstra_all_pairs_shortest_paths) diff --git a/ortools/graph/samples/dijkstra_directed_test.bintest b/ortools/graph/samples/dijkstra_directed_test.bintest new file mode 100644 index 00000000000..0966f4e0aab --- /dev/null +++ b/ortools/graph/samples/dijkstra_directed_test.bintest @@ -0,0 +1,2 @@ +RUN: $(dijkstra_directed) +CHECK: "Shortest path length: 8" diff --git a/ortools/graph/samples/dijkstra_one_to_all_test.bintest b/ortools/graph/samples/dijkstra_one_to_all_test.bintest new file mode 100644 index 00000000000..7939cd1b476 --- /dev/null +++ b/ortools/graph/samples/dijkstra_one_to_all_test.bintest @@ -0,0 +1,4 @@ +RUN: $(dijkstra_one_to_all) +CHECK: "Distance to 1: 2" +CHECK: "Distance to 2: 6" +CHECK: "Distance to 3: 2" diff --git a/ortools/graph/samples/dijkstra_sequential_test.bintest b/ortools/graph/samples/dijkstra_sequential_test.bintest new file mode 100644 index 00000000000..d32c3dcd283 --- /dev/null +++ b/ortools/graph/samples/dijkstra_sequential_test.bintest @@ -0,0 +1,5 @@ +RUN: $(dijkstra_sequential) +CHECK: "Initial distance: 200" +CHECK: "Distance_2_4: 2" +CHECK: "Distance_8_1: 3" +CHECK: "Distance_3_7: 4" diff --git a/ortools/graph/samples/dijkstra_undirected_test.bintest b/ortools/graph/samples/dijkstra_undirected_test.bintest new file mode 100644 index 00000000000..9daf3d2cde6 --- /dev/null +++ b/ortools/graph/samples/dijkstra_undirected_test.bintest @@ -0,0 +1,2 @@ +RUN: $(dijkstra_undirected) +CHECK: "Shortest path length: 4" diff --git a/ortools/graph/samples/root_a_tree_test.bintest b/ortools/graph/samples/root_a_tree_test.bintest new file mode 100644 index 00000000000..cabeefcf2b5 --- /dev/null +++ b/ortools/graph/samples/root_a_tree_test.bintest @@ -0,0 +1,2 @@ +RUN: $(root_a_tree) +CHECK: "Depths:" "0 -> 2" diff --git a/ortools/graph/samples/rooted_tree_paths_test.bintest b/ortools/graph/samples/rooted_tree_paths_test.bintest new file mode 100644 index 00000000000..0e5d8dd32d9 --- /dev/null +++ b/ortools/graph/samples/rooted_tree_paths_test.bintest @@ -0,0 +1,2 @@ +RUN: $(rooted_tree_paths) +CHECK: "0 -> 4 [0, 1, 4]" diff --git a/ortools/linear_solver/samples/AssignmentMbTest.bintest b/ortools/linear_solver/samples/AssignmentMbTest.bintest new file mode 100644 index 00000000000..81907f9146e --- /dev/null +++ b/ortools/linear_solver/samples/AssignmentMbTest.bintest @@ -0,0 +1 @@ +RUN: $(AssignmentMb) diff --git a/ortools/linear_solver/samples/BUILD.bazel b/ortools/linear_solver/samples/BUILD.bazel index c2498d96d9e..dce7e576528 100644 --- a/ortools/linear_solver/samples/BUILD.bazel +++ b/ortools/linear_solver/samples/BUILD.bazel @@ -15,7 +15,7 @@ load("@pip_deps//:requirements.bzl", "requirement") load("@rules_cc//cc:cc_test.bzl", "cc_test") load("@rules_java//java:java_binary.bzl", "java_binary") load("@rules_python//python:py_test.bzl", "py_test") -load("//bazel:run_binary_test.bzl", "run_binary_test") +load("//tools/testing:bintest.bzl", "bintest") package(default_visibility = ["//visibility:public"]) @@ -224,9 +224,10 @@ java_binary( ], ) -run_binary_test( +bintest( name = "AssignmentMbTest", - binary = ":AssignmentMb", + srcs = [":AssignmentMbTest.bintest"], + named_data = {"AssignmentMb": ":AssignmentMb"}, ) java_binary( @@ -240,9 +241,10 @@ java_binary( ], ) -run_binary_test( +bintest( name = "BinPackingMbTest", - binary = ":BinPackingMb", + srcs = [":BinPackingMbTest.bintest"], + named_data = {"BinPackingMb": ":BinPackingMb"}, ) java_binary( @@ -256,9 +258,10 @@ java_binary( ], ) -run_binary_test( +bintest( name = "CloneModelMbTest", - binary = ":CloneModelMb", + srcs = [":CloneModelMbTest.bintest"], + named_data = {"CloneModelMb": ":CloneModelMb"}, ) java_binary( @@ -272,9 +275,10 @@ java_binary( ], ) -run_binary_test( +bintest( name = "SimpleLpProgramMbTest", - binary = ":SimpleLpProgramMb", + srcs = [":SimpleLpProgramMbTest.bintest"], + named_data = {"SimpleLpProgramMb": ":SimpleLpProgramMb"}, ) java_binary( @@ -288,7 +292,8 @@ java_binary( ], ) -run_binary_test( +bintest( name = "SimpleMipProgramMbTest", - binary = ":SimpleMipProgramMb", + srcs = [":SimpleMipProgramMbTest.bintest"], + named_data = {"SimpleMipProgramMb": ":SimpleMipProgramMb"}, ) diff --git a/ortools/linear_solver/samples/BinPackingMbTest.bintest b/ortools/linear_solver/samples/BinPackingMbTest.bintest new file mode 100644 index 00000000000..cdd153dcbc0 --- /dev/null +++ b/ortools/linear_solver/samples/BinPackingMbTest.bintest @@ -0,0 +1 @@ +RUN: $(BinPackingMb) diff --git a/ortools/linear_solver/samples/CloneModelMbTest.bintest b/ortools/linear_solver/samples/CloneModelMbTest.bintest new file mode 100644 index 00000000000..378e330a0cd --- /dev/null +++ b/ortools/linear_solver/samples/CloneModelMbTest.bintest @@ -0,0 +1 @@ +RUN: $(CloneModelMb) diff --git a/ortools/linear_solver/samples/SimpleLpProgramMbTest.bintest b/ortools/linear_solver/samples/SimpleLpProgramMbTest.bintest new file mode 100644 index 00000000000..663992df3a3 --- /dev/null +++ b/ortools/linear_solver/samples/SimpleLpProgramMbTest.bintest @@ -0,0 +1 @@ +RUN: $(SimpleLpProgramMb) diff --git a/ortools/linear_solver/samples/SimpleMipProgramMbTest.bintest b/ortools/linear_solver/samples/SimpleMipProgramMbTest.bintest new file mode 100644 index 00000000000..332f65ea24d --- /dev/null +++ b/ortools/linear_solver/samples/SimpleMipProgramMbTest.bintest @@ -0,0 +1 @@ +RUN: $(SimpleMipProgramMb) diff --git a/ortools/math_opt/core/c_api/BUILD.bazel b/ortools/math_opt/core/c_api/BUILD.bazel index e77505e7feb..ee31a18d862 100644 --- a/ortools/math_opt/core/c_api/BUILD.bazel +++ b/ortools/math_opt/core/c_api/BUILD.bazel @@ -14,7 +14,7 @@ load("@rules_cc//cc:cc_binary.bzl", "cc_binary") load("@rules_cc//cc:cc_library.bzl", "cc_library") load("@rules_cc//cc:cc_test.bzl", "cc_test") -load("//bazel:run_binary_test.bzl", "run_binary_test") +load("//tools/testing:bintest.bzl", "bintest") cc_library( name = "solver", @@ -75,11 +75,8 @@ cc_binary( ], ) -run_binary_test( +bintest( name = "cpp_example_test", - binary = ":cpp_example", - grep_lines = [ - "Termination is optimal: 1", - "Objective value: 1", - ], + srcs = [":cpp_example_test.bintest"], + named_data = {"cpp_example": ":cpp_example"}, ) diff --git a/ortools/math_opt/core/c_api/cpp_example_test.bintest b/ortools/math_opt/core/c_api/cpp_example_test.bintest new file mode 100644 index 00000000000..92cf27cd7b7 --- /dev/null +++ b/ortools/math_opt/core/c_api/cpp_example_test.bintest @@ -0,0 +1,3 @@ +RUN: $(cpp_example) +CHECK: "Termination is optimal: 1" +CHECK: "Objective value: 1" diff --git a/ortools/routing/samples/BUILD.bazel b/ortools/routing/samples/BUILD.bazel index d814a71e63a..12ff84117ae 100644 --- a/ortools/routing/samples/BUILD.bazel +++ b/ortools/routing/samples/BUILD.bazel @@ -15,7 +15,7 @@ load("@rules_cc//cc:cc_binary.bzl", "cc_binary") load("@rules_cc//cc:cc_test.bzl", "cc_test") -load("//bazel:run_binary_test.bzl", "run_binary_test") +load("//tools/testing:bintest.bzl", "bintest") package(default_visibility = ["//visibility:public"]) @@ -60,11 +60,11 @@ cc_binary( ], ) -run_binary_test( +bintest( name = "cvrptw_test", size = "large", - args = ["--vrp_use_deterministic_random_seed"], - binary = ":cvrptw", + srcs = ["cvrptw_test.bintest"], + named_data = {"cvrptw": ":cvrptw"}, ) cc_binary( @@ -87,19 +87,20 @@ cc_binary( ], ) -run_binary_test( +bintest( name = "cvrp_disjoint_tw_test", size = "large", - args = ["--vrp_use_deterministic_random_seed"], - binary = ":cvrp_disjoint_tw", + srcs = ["cvrp_disjoint_tw_test.bintest"], + named_data = {"cvrp_disjoint_tw": ":cvrp_disjoint_tw"}, ) # This test is temporarily down because the time dependent functionality is # being revised. -# run_binary_test( +# bintest( # name = "cvrptw_with_time_dependent_costs_test", +# srcs = ["cvrptw_with_time_dependent_costs_test.bintest"], # size = "large", -# binary = ":cvrptw_with_time_dependent_costs", +# named_data = {"cvrptw_with_time_dependent_costs": ":cvrptw_with_time_dependent_costs"}, # ) cc_binary( @@ -144,11 +145,11 @@ cc_binary( ], ) -run_binary_test( +bintest( name = "cvrptw_with_resources_test", size = "large", - args = ["--vrp_use_deterministic_random_seed"], - binary = ":cvrptw_with_resources", + srcs = ["cvrptw_with_resources_test.bintest"], + named_data = {"cvrptw_with_resources": ":cvrptw_with_resources"}, ) cc_binary( @@ -172,11 +173,11 @@ cc_binary( ], ) -run_binary_test( +bintest( name = "cvrptw_with_stop_times_and_resources_test", size = "large", - args = ["--vrp_use_deterministic_random_seed"], - binary = ":cvrptw_with_stop_times_and_resources", + srcs = ["cvrptw_with_stop_times_and_resources_test.bintest"], + named_data = {"cvrptw_with_stop_times_and_resources": ":cvrptw_with_stop_times_and_resources"}, ) cc_binary( @@ -199,14 +200,11 @@ cc_binary( ], ) -run_binary_test( +bintest( name = "cvrptw_with_refueling_test", size = "large", - args = [ - "--vrp_use_deterministic_random_seed", - "--cp_random_seed=144", - ], - binary = ":cvrptw_with_refueling", + srcs = ["cvrptw_with_refueling_test.bintest"], + named_data = {"cvrptw_with_refueling": ":cvrptw_with_refueling"}, ) cc_test( diff --git a/ortools/routing/samples/cvrp_disjoint_tw_test.bintest b/ortools/routing/samples/cvrp_disjoint_tw_test.bintest new file mode 100644 index 00000000000..bcd3d72e9e4 --- /dev/null +++ b/ortools/routing/samples/cvrp_disjoint_tw_test.bintest @@ -0,0 +1 @@ +RUN: $(cvrp_disjoint_tw) --vrp_use_deterministic_random_seed diff --git a/ortools/routing/samples/cvrptw_test.bintest b/ortools/routing/samples/cvrptw_test.bintest new file mode 100644 index 00000000000..f7c5020d13c --- /dev/null +++ b/ortools/routing/samples/cvrptw_test.bintest @@ -0,0 +1 @@ +RUN: $(cvrptw) --vrp_use_deterministic_random_seed diff --git a/ortools/routing/samples/cvrptw_with_precedences_test.bintest b/ortools/routing/samples/cvrptw_with_precedences_test.bintest new file mode 100644 index 00000000000..4fcb6348339 --- /dev/null +++ b/ortools/routing/samples/cvrptw_with_precedences_test.bintest @@ -0,0 +1 @@ +RUN: $(cvrptw_with_precedences) --vrp_use_deterministic_random_seed diff --git a/ortools/routing/samples/cvrptw_with_refueling_test.bintest b/ortools/routing/samples/cvrptw_with_refueling_test.bintest new file mode 100644 index 00000000000..6fb876f5c82 --- /dev/null +++ b/ortools/routing/samples/cvrptw_with_refueling_test.bintest @@ -0,0 +1 @@ +RUN: $(cvrptw_with_refueling) --vrp_use_deterministic_random_seed --cp_random_seed=144 diff --git a/ortools/routing/samples/cvrptw_with_resources_test.bintest b/ortools/routing/samples/cvrptw_with_resources_test.bintest new file mode 100644 index 00000000000..b699662c6a8 --- /dev/null +++ b/ortools/routing/samples/cvrptw_with_resources_test.bintest @@ -0,0 +1 @@ +RUN: $(cvrptw_with_resources) --vrp_use_deterministic_random_seed diff --git a/ortools/routing/samples/cvrptw_with_stop_times_and_resources_test.bintest b/ortools/routing/samples/cvrptw_with_stop_times_and_resources_test.bintest new file mode 100644 index 00000000000..fe75f294fa6 --- /dev/null +++ b/ortools/routing/samples/cvrptw_with_stop_times_and_resources_test.bintest @@ -0,0 +1 @@ +RUN: $(cvrptw_with_stop_times_and_resources) --vrp_use_deterministic_random_seed diff --git a/ortools/sat/docs/channeling.md b/ortools/sat/docs/channeling.md index 28cb3b4cbf1..53bb49397c4 100644 --- a/ortools/sat/docs/channeling.md +++ b/ortools/sat/docs/channeling.md @@ -524,8 +524,8 @@ To make this more concrete, let's say you have 10 bins of capacity 100, and items to pack into the bins. You would like to maximize the number of bins that can accept one emergency load of size 20. -To do this, you need to maximize the number of bins that have a load less -than 80. In the code below, channeling is used to link the *load* and *slack* +To do this, you need to maximize the number of bins that have a load less than +80. In the code below, channeling is used to link the *load* and *slack* variables together: ### Python code diff --git a/ortools/sat/docs/scheduling.md b/ortools/sat/docs/scheduling.md index 60f193b2376..8a336fea033 100644 --- a/ortools/sat/docs/scheduling.md +++ b/ortools/sat/docs/scheduling.md @@ -14,8 +14,8 @@ exclusivity between tasks, and temporal relations between tasks. ## Interval variables Intervals are constraints containing three constant of affine expressions -(start, size, and end). Creating an interval constraint will enforce that -`start + size == end`. +(start, size, and end). Creating an interval constraint will enforce that `start ++ size == end`. The more general API uses three expressions to define the interval. If the size is fixed, a simpler API uses the start expression and the fixed size. @@ -2266,13 +2266,15 @@ of the start of the task. This is implemented using channeling constraints. The following code displays: - start=8 duration=3 across=0 - start=9 duration=3 across=0 - start=10 duration=3 across=0 - start=11 duration=4 across=1 - start=12 duration=4 across=1 - start=14 duration=3 across=0 - start=15 duration=3 across=0 +``` +start=8 duration=3 across=0 +start=9 duration=3 across=0 +start=10 duration=3 across=0 +start=11 duration=4 across=1 +start=12 duration=4 across=1 +start=14 duration=3 across=0 +start=15 duration=3 across=0 +``` ### Python code diff --git a/ortools/sat/python/BUILD.bazel b/ortools/sat/python/BUILD.bazel index e607ddf06be..2230b4742c6 100644 --- a/ortools/sat/python/BUILD.bazel +++ b/ortools/sat/python/BUILD.bazel @@ -138,7 +138,7 @@ py_library( py_test( name = "cp_model_test", - size = "small", + size = "medium", srcs = ["cp_model_test.py"], deps = [ ":cp_model", diff --git a/ortools/sat/samples/AssignmentGroupsSatTest.bintest b/ortools/sat/samples/AssignmentGroupsSatTest.bintest new file mode 100644 index 00000000000..f625fc9c303 --- /dev/null +++ b/ortools/sat/samples/AssignmentGroupsSatTest.bintest @@ -0,0 +1 @@ +RUN: $(AssignmentGroupsSat) diff --git a/ortools/sat/samples/AssignmentSatTest.bintest b/ortools/sat/samples/AssignmentSatTest.bintest new file mode 100644 index 00000000000..5aa64bfc6ca --- /dev/null +++ b/ortools/sat/samples/AssignmentSatTest.bintest @@ -0,0 +1 @@ +RUN: $(AssignmentSat) diff --git a/ortools/sat/samples/AssignmentTaskSizesSatTest.bintest b/ortools/sat/samples/AssignmentTaskSizesSatTest.bintest new file mode 100644 index 00000000000..5f312b1ebc1 --- /dev/null +++ b/ortools/sat/samples/AssignmentTaskSizesSatTest.bintest @@ -0,0 +1 @@ +RUN: $(AssignmentTaskSizesSat) diff --git a/ortools/sat/samples/AssignmentTeamsSatTest.bintest b/ortools/sat/samples/AssignmentTeamsSatTest.bintest new file mode 100644 index 00000000000..cf7d2a1abb1 --- /dev/null +++ b/ortools/sat/samples/AssignmentTeamsSatTest.bintest @@ -0,0 +1 @@ +RUN: $(AssignmentTeamsSat) diff --git a/ortools/sat/samples/AssumptionsSampleSatTest.bintest b/ortools/sat/samples/AssumptionsSampleSatTest.bintest new file mode 100644 index 00000000000..7fad3a5dbd8 --- /dev/null +++ b/ortools/sat/samples/AssumptionsSampleSatTest.bintest @@ -0,0 +1 @@ +RUN: $(AssumptionsSampleSat) diff --git a/ortools/sat/samples/BUILD.bazel b/ortools/sat/samples/BUILD.bazel index a0c458a6855..1fde7cb8338 100644 --- a/ortools/sat/samples/BUILD.bazel +++ b/ortools/sat/samples/BUILD.bazel @@ -16,7 +16,7 @@ load("@rules_cc//cc:cc_test.bzl", "cc_test") load("@rules_go//go:def.bzl", "go_binary") load("@rules_java//java:java_binary.bzl", "java_binary") load("@rules_python//python:py_test.bzl", "py_test") -load("//bazel:run_binary_test.bzl", "run_binary_test") +load("//tools/testing:bintest.bzl", "bintest") py_test( name = "all_different_except_zero_sample_sat_py3", @@ -1046,9 +1046,10 @@ java_binary( ], ) -run_binary_test( +bintest( name = "AssignmentGroupsSatTest", - binary = ":AssignmentGroupsSat", + srcs = ["AssignmentGroupsSatTest.bintest"], + named_data = {"AssignmentGroupsSat": ":AssignmentGroupsSat"}, ) java_binary( @@ -1062,9 +1063,10 @@ java_binary( ], ) -run_binary_test( +bintest( name = "AssignmentSatTest", - binary = ":AssignmentSat", + srcs = ["AssignmentSatTest.bintest"], + named_data = {"AssignmentSat": ":AssignmentSat"}, ) java_binary( @@ -1078,9 +1080,10 @@ java_binary( ], ) -run_binary_test( +bintest( name = "AssignmentTaskSizesSatTest", - binary = ":AssignmentTaskSizesSat", + srcs = ["AssignmentTaskSizesSatTest.bintest"], + named_data = {"AssignmentTaskSizesSat": ":AssignmentTaskSizesSat"}, ) java_binary( @@ -1094,9 +1097,10 @@ java_binary( ], ) -run_binary_test( +bintest( name = "AssignmentTeamsSatTest", - binary = ":AssignmentTeamsSat", + srcs = ["AssignmentTeamsSatTest.bintest"], + named_data = {"AssignmentTeamsSat": ":AssignmentTeamsSat"}, ) java_binary( @@ -1110,9 +1114,10 @@ java_binary( ], ) -run_binary_test( +bintest( name = "AssumptionsSampleSatTest", - binary = ":AssumptionsSampleSat", + srcs = ["AssumptionsSampleSatTest.bintest"], + named_data = {"AssumptionsSampleSat": ":AssumptionsSampleSat"}, ) java_binary( @@ -1126,9 +1131,10 @@ java_binary( ], ) -run_binary_test( +bintest( name = "BinPackingProblemSatTest", - binary = ":BinPackingProblemSat", + srcs = ["BinPackingProblemSatTest.bintest"], + named_data = {"BinPackingProblemSat": ":BinPackingProblemSat"}, ) java_binary( @@ -1141,9 +1147,10 @@ java_binary( ], ) -run_binary_test( +bintest( name = "BoolOrSampleSatTest", - binary = ":BoolOrSampleSat", + srcs = ["BoolOrSampleSatTest.bintest"], + named_data = {"BoolOrSampleSat": ":BoolOrSampleSat"}, ) java_binary( @@ -1158,9 +1165,10 @@ java_binary( ], ) -run_binary_test( +bintest( name = "ChannelingSampleSatTest", - binary = ":ChannelingSampleSat", + srcs = ["ChannelingSampleSatTest.bintest"], + named_data = {"ChannelingSampleSat": ":ChannelingSampleSat"}, ) java_binary( @@ -1174,9 +1182,10 @@ java_binary( ], ) -run_binary_test( +bintest( name = "CloneModelSampleSatTest", - binary = ":CloneModelSampleSat", + srcs = ["CloneModelSampleSatTest.bintest"], + named_data = {"CloneModelSampleSat": ":CloneModelSampleSat"}, ) java_binary( @@ -1190,9 +1199,10 @@ java_binary( ], ) -run_binary_test( +bintest( name = "CpIsFunSatTest", - binary = ":CpIsFunSat", + srcs = ["CpIsFunSatTest.bintest"], + named_data = {"CpIsFunSat": ":CpIsFunSat"}, ) java_binary( @@ -1206,9 +1216,10 @@ java_binary( ], ) -run_binary_test( +bintest( name = "CpSatExampleTest", - binary = ":CpSatExample", + srcs = ["CpSatExampleTest.bintest"], + named_data = {"CpSatExample": ":CpSatExample"}, ) java_binary( @@ -1223,9 +1234,10 @@ java_binary( ], ) -run_binary_test( +bintest( name = "EarlinessTardinessCostSampleSatTest", - binary = ":EarlinessTardinessCostSampleSat", + srcs = ["EarlinessTardinessCostSampleSatTest.bintest"], + named_data = {"EarlinessTardinessCostSampleSat": ":EarlinessTardinessCostSampleSat"}, ) java_binary( @@ -1238,9 +1250,10 @@ java_binary( ], ) -run_binary_test( +bintest( name = "IntervalSampleSatTest", - binary = ":IntervalSampleSat", + srcs = ["IntervalSampleSatTest.bintest"], + named_data = {"IntervalSampleSat": ":IntervalSampleSat"}, ) java_binary( @@ -1253,9 +1266,10 @@ java_binary( ], ) -run_binary_test( +bintest( name = "LiteralSampleSatTest", - binary = ":LiteralSampleSat", + srcs = ["LiteralSampleSatTest.bintest"], + named_data = {"LiteralSampleSat": ":LiteralSampleSat"}, ) java_binary( @@ -1269,9 +1283,10 @@ java_binary( ], ) -run_binary_test( +bintest( name = "MinimalJobshopSatTest", - binary = ":MinimalJobshopSat", + srcs = ["MinimalJobshopSatTest.bintest"], + named_data = {"MinimalJobshopSat": ":MinimalJobshopSat"}, ) java_binary( @@ -1285,9 +1300,10 @@ java_binary( ], ) -run_binary_test( +bintest( name = "MultipleKnapsackSatTest", - binary = ":MultipleKnapsackSat", + srcs = ["MultipleKnapsackSatTest.bintest"], + named_data = {"MultipleKnapsackSat": ":MultipleKnapsackSat"}, ) java_binary( @@ -1301,9 +1317,10 @@ java_binary( ], ) -run_binary_test( +bintest( name = "NQueensSatTest", - binary = ":NQueensSat", + srcs = ["NQueensSatTest.bintest"], + named_data = {"NQueensSat": ":NQueensSat"}, ) java_binary( @@ -1317,9 +1334,10 @@ java_binary( ], ) -run_binary_test( +bintest( name = "NoOverlapSampleSatTest", - binary = ":NoOverlapSampleSat", + srcs = ["NoOverlapSampleSatTest.bintest"], + named_data = {"NoOverlapSampleSat": ":NoOverlapSampleSat"}, ) java_binary( @@ -1333,9 +1351,10 @@ java_binary( ], ) -run_binary_test( +bintest( name = "NonLinearSatTest", - binary = ":NonLinearSat", + srcs = ["NonLinearSatTest.bintest"], + named_data = {"NonLinearSat": ":NonLinearSat"}, ) java_binary( @@ -1349,9 +1368,10 @@ java_binary( ], ) -run_binary_test( +bintest( name = "NursesSatTest", - binary = ":NursesSat", + srcs = ["NursesSatTest.bintest"], + named_data = {"NursesSat": ":NursesSat"}, ) java_binary( @@ -1364,9 +1384,10 @@ java_binary( ], ) -run_binary_test( +bintest( name = "OptionalIntervalSampleSatTest", - binary = ":OptionalIntervalSampleSat", + srcs = ["OptionalIntervalSampleSatTest.bintest"], + named_data = {"OptionalIntervalSampleSat": ":OptionalIntervalSampleSat"}, ) java_binary( @@ -1380,9 +1401,10 @@ java_binary( ], ) -run_binary_test( +bintest( name = "RabbitsAndPheasantsSatTest", - binary = ":RabbitsAndPheasantsSat", + srcs = ["RabbitsAndPheasantsSatTest.bintest"], + named_data = {"RabbitsAndPheasantsSat": ":RabbitsAndPheasantsSat"}, ) java_binary( @@ -1396,9 +1418,10 @@ java_binary( ], ) -run_binary_test( +bintest( name = "RankingSampleSatTest", - binary = ":RankingSampleSat", + srcs = ["RankingSampleSatTest.bintest"], + named_data = {"RankingSampleSat": ":RankingSampleSat"}, ) java_binary( @@ -1411,9 +1434,10 @@ java_binary( ], ) -run_binary_test( +bintest( name = "ReifiedSampleSatTest", - binary = ":ReifiedSampleSat", + srcs = ["ReifiedSampleSatTest.bintest"], + named_data = {"ReifiedSampleSat": ":ReifiedSampleSat"}, ) java_binary( @@ -1427,9 +1451,10 @@ java_binary( ], ) -run_binary_test( +bintest( name = "ScheduleRequestsSatTest", - binary = ":ScheduleRequestsSat", + srcs = ["ScheduleRequestsSatTest.bintest"], + named_data = {"ScheduleRequestsSat": ":ScheduleRequestsSat"}, ) java_binary( @@ -1443,9 +1468,10 @@ java_binary( ], ) -run_binary_test( +bintest( name = "SearchForAllSolutionsSampleSatTest", - binary = ":SearchForAllSolutionsSampleSat", + srcs = ["SearchForAllSolutionsSampleSatTest.bintest"], + named_data = {"SearchForAllSolutionsSampleSat": ":SearchForAllSolutionsSampleSat"}, ) java_binary( @@ -1459,9 +1485,10 @@ java_binary( ], ) -run_binary_test( +bintest( name = "SimpleSatProgramTest", - binary = ":SimpleSatProgram", + srcs = ["SimpleSatProgramTest.bintest"], + named_data = {"SimpleSatProgram": ":SimpleSatProgram"}, ) java_binary( @@ -1475,9 +1502,10 @@ java_binary( ], ) -run_binary_test( +bintest( name = "SolutionHintingSampleSatTest", - binary = ":SolutionHintingSampleSat", + srcs = ["SolutionHintingSampleSatTest.bintest"], + named_data = {"SolutionHintingSampleSat": ":SolutionHintingSampleSat"}, ) java_binary( @@ -1491,9 +1519,10 @@ java_binary( ], ) -run_binary_test( +bintest( name = "SolveAndPrintIntermediateSolutionsSampleSatTest", - binary = ":SolveAndPrintIntermediateSolutionsSampleSat", + srcs = ["SolveAndPrintIntermediateSolutionsSampleSatTest.bintest"], + named_data = {"SolveAndPrintIntermediateSolutionsSampleSat": ":SolveAndPrintIntermediateSolutionsSampleSat"}, ) java_binary( @@ -1507,9 +1536,10 @@ java_binary( ], ) -run_binary_test( +bintest( name = "SolveWithTimeLimitSampleSatTest", - binary = ":SolveWithTimeLimitSampleSat", + srcs = ["SolveWithTimeLimitSampleSatTest.bintest"], + named_data = {"SolveWithTimeLimitSampleSat": ":SolveWithTimeLimitSampleSat"}, ) java_binary( @@ -1525,9 +1555,10 @@ java_binary( ], ) -run_binary_test( +bintest( name = "StepFunctionSampleSatTest", - binary = ":StepFunctionSampleSat", + srcs = ["StepFunctionSampleSatTest.bintest"], + named_data = {"StepFunctionSampleSat": ":StepFunctionSampleSat"}, ) java_binary( @@ -1541,7 +1572,8 @@ java_binary( ], ) -run_binary_test( +bintest( name = "StopAfterNSolutionsSampleSatTest", - binary = ":StopAfterNSolutionsSampleSat", + srcs = ["StopAfterNSolutionsSampleSatTest.bintest"], + named_data = {"StopAfterNSolutionsSampleSat": ":StopAfterNSolutionsSampleSat"}, ) diff --git a/ortools/sat/samples/BinPackingProblemSatTest.bintest b/ortools/sat/samples/BinPackingProblemSatTest.bintest new file mode 100644 index 00000000000..9669ea85301 --- /dev/null +++ b/ortools/sat/samples/BinPackingProblemSatTest.bintest @@ -0,0 +1 @@ +RUN: $(BinPackingProblemSat) diff --git a/ortools/sat/samples/BoolOrSampleSatTest.bintest b/ortools/sat/samples/BoolOrSampleSatTest.bintest new file mode 100644 index 00000000000..dbc32c3c743 --- /dev/null +++ b/ortools/sat/samples/BoolOrSampleSatTest.bintest @@ -0,0 +1 @@ +RUN: $(BoolOrSampleSat) diff --git a/ortools/sat/samples/ChannelingSampleSatTest.bintest b/ortools/sat/samples/ChannelingSampleSatTest.bintest new file mode 100644 index 00000000000..9d92868476d --- /dev/null +++ b/ortools/sat/samples/ChannelingSampleSatTest.bintest @@ -0,0 +1 @@ +RUN: $(ChannelingSampleSat) diff --git a/ortools/sat/samples/CloneModelSampleSatTest.bintest b/ortools/sat/samples/CloneModelSampleSatTest.bintest new file mode 100644 index 00000000000..94c93cd09f7 --- /dev/null +++ b/ortools/sat/samples/CloneModelSampleSatTest.bintest @@ -0,0 +1 @@ +RUN: $(CloneModelSampleSat) diff --git a/ortools/sat/samples/CpIsFunSatTest.bintest b/ortools/sat/samples/CpIsFunSatTest.bintest new file mode 100644 index 00000000000..f9162043c9d --- /dev/null +++ b/ortools/sat/samples/CpIsFunSatTest.bintest @@ -0,0 +1 @@ +RUN: $(CpIsFunSat) diff --git a/ortools/sat/samples/CpSatExampleTest.bintest b/ortools/sat/samples/CpSatExampleTest.bintest new file mode 100644 index 00000000000..88f9dd7842a --- /dev/null +++ b/ortools/sat/samples/CpSatExampleTest.bintest @@ -0,0 +1 @@ +RUN: $(CpSatExample) diff --git a/ortools/sat/samples/EarlinessTardinessCostSampleSatTest.bintest b/ortools/sat/samples/EarlinessTardinessCostSampleSatTest.bintest new file mode 100644 index 00000000000..776b428f2cb --- /dev/null +++ b/ortools/sat/samples/EarlinessTardinessCostSampleSatTest.bintest @@ -0,0 +1 @@ +RUN: $(EarlinessTardinessCostSampleSat) diff --git a/ortools/sat/samples/IntervalSampleSatTest.bintest b/ortools/sat/samples/IntervalSampleSatTest.bintest new file mode 100644 index 00000000000..41ec2dce98b --- /dev/null +++ b/ortools/sat/samples/IntervalSampleSatTest.bintest @@ -0,0 +1 @@ +RUN: $(IntervalSampleSat) diff --git a/ortools/sat/samples/LiteralSampleSatTest.bintest b/ortools/sat/samples/LiteralSampleSatTest.bintest new file mode 100644 index 00000000000..1c4892f5c9f --- /dev/null +++ b/ortools/sat/samples/LiteralSampleSatTest.bintest @@ -0,0 +1 @@ +RUN: $(LiteralSampleSat) diff --git a/ortools/sat/samples/MinimalJobshopSatTest.bintest b/ortools/sat/samples/MinimalJobshopSatTest.bintest new file mode 100644 index 00000000000..46e31b0b0e8 --- /dev/null +++ b/ortools/sat/samples/MinimalJobshopSatTest.bintest @@ -0,0 +1 @@ +RUN: $(MinimalJobshopSat) diff --git a/ortools/sat/samples/MultipleKnapsackSatTest.bintest b/ortools/sat/samples/MultipleKnapsackSatTest.bintest new file mode 100644 index 00000000000..606d4b4fc05 --- /dev/null +++ b/ortools/sat/samples/MultipleKnapsackSatTest.bintest @@ -0,0 +1 @@ +RUN: $(MultipleKnapsackSat) diff --git a/ortools/sat/samples/NQueensSatTest.bintest b/ortools/sat/samples/NQueensSatTest.bintest new file mode 100644 index 00000000000..8c1aab0fed0 --- /dev/null +++ b/ortools/sat/samples/NQueensSatTest.bintest @@ -0,0 +1 @@ +RUN: $(NQueensSat) diff --git a/ortools/sat/samples/NoOverlapSampleSatTest.bintest b/ortools/sat/samples/NoOverlapSampleSatTest.bintest new file mode 100644 index 00000000000..7937a6d4c6f --- /dev/null +++ b/ortools/sat/samples/NoOverlapSampleSatTest.bintest @@ -0,0 +1 @@ +RUN: $(NoOverlapSampleSat) diff --git a/ortools/sat/samples/NonLinearSatTest.bintest b/ortools/sat/samples/NonLinearSatTest.bintest new file mode 100644 index 00000000000..2e6af3f6b73 --- /dev/null +++ b/ortools/sat/samples/NonLinearSatTest.bintest @@ -0,0 +1 @@ +RUN: $(NonLinearSat) diff --git a/ortools/sat/samples/NursesSatTest.bintest b/ortools/sat/samples/NursesSatTest.bintest new file mode 100644 index 00000000000..6509325ad06 --- /dev/null +++ b/ortools/sat/samples/NursesSatTest.bintest @@ -0,0 +1 @@ +RUN: $(NursesSat) diff --git a/ortools/sat/samples/OptionalIntervalSampleSatTest.bintest b/ortools/sat/samples/OptionalIntervalSampleSatTest.bintest new file mode 100644 index 00000000000..d48ffd7ad59 --- /dev/null +++ b/ortools/sat/samples/OptionalIntervalSampleSatTest.bintest @@ -0,0 +1 @@ +RUN: $(OptionalIntervalSampleSat) diff --git a/ortools/sat/samples/RabbitsAndPheasantsSatTest.bintest b/ortools/sat/samples/RabbitsAndPheasantsSatTest.bintest new file mode 100644 index 00000000000..0732a387ddb --- /dev/null +++ b/ortools/sat/samples/RabbitsAndPheasantsSatTest.bintest @@ -0,0 +1 @@ +RUN: $(RabbitsAndPheasantsSat) diff --git a/ortools/sat/samples/RankingSampleSatTest.bintest b/ortools/sat/samples/RankingSampleSatTest.bintest new file mode 100644 index 00000000000..ea24bdd25e5 --- /dev/null +++ b/ortools/sat/samples/RankingSampleSatTest.bintest @@ -0,0 +1 @@ +RUN: $(RankingSampleSat) diff --git a/ortools/sat/samples/ReifiedSampleSatTest.bintest b/ortools/sat/samples/ReifiedSampleSatTest.bintest new file mode 100644 index 00000000000..2c8de55e8ad --- /dev/null +++ b/ortools/sat/samples/ReifiedSampleSatTest.bintest @@ -0,0 +1 @@ +RUN: $(ReifiedSampleSat) diff --git a/ortools/sat/samples/ScheduleRequestsSatTest.bintest b/ortools/sat/samples/ScheduleRequestsSatTest.bintest new file mode 100644 index 00000000000..bfe90f49c9d --- /dev/null +++ b/ortools/sat/samples/ScheduleRequestsSatTest.bintest @@ -0,0 +1 @@ +RUN: $(ScheduleRequestsSat) diff --git a/ortools/sat/samples/SearchForAllSolutionsSampleSatTest.bintest b/ortools/sat/samples/SearchForAllSolutionsSampleSatTest.bintest new file mode 100644 index 00000000000..783ddf6d490 --- /dev/null +++ b/ortools/sat/samples/SearchForAllSolutionsSampleSatTest.bintest @@ -0,0 +1 @@ +RUN: $(SearchForAllSolutionsSampleSat) diff --git a/ortools/sat/samples/SimpleSatProgramTest.bintest b/ortools/sat/samples/SimpleSatProgramTest.bintest new file mode 100644 index 00000000000..e55bf14ef70 --- /dev/null +++ b/ortools/sat/samples/SimpleSatProgramTest.bintest @@ -0,0 +1 @@ +RUN: $(SimpleSatProgram) diff --git a/ortools/sat/samples/SolutionHintingSampleSatTest.bintest b/ortools/sat/samples/SolutionHintingSampleSatTest.bintest new file mode 100644 index 00000000000..fef6413c5dc --- /dev/null +++ b/ortools/sat/samples/SolutionHintingSampleSatTest.bintest @@ -0,0 +1 @@ +RUN: $(SolutionHintingSampleSat) diff --git a/ortools/sat/samples/SolveAndPrintIntermediateSolutionsSampleSatTest.bintest b/ortools/sat/samples/SolveAndPrintIntermediateSolutionsSampleSatTest.bintest new file mode 100644 index 00000000000..891ed37e691 --- /dev/null +++ b/ortools/sat/samples/SolveAndPrintIntermediateSolutionsSampleSatTest.bintest @@ -0,0 +1 @@ +RUN: $(SolveAndPrintIntermediateSolutionsSampleSat) diff --git a/ortools/sat/samples/SolveWithTimeLimitSampleSatTest.bintest b/ortools/sat/samples/SolveWithTimeLimitSampleSatTest.bintest new file mode 100644 index 00000000000..049deecb0fe --- /dev/null +++ b/ortools/sat/samples/SolveWithTimeLimitSampleSatTest.bintest @@ -0,0 +1 @@ +RUN: $(SolveWithTimeLimitSampleSat) diff --git a/ortools/sat/samples/StepFunctionSampleSatTest.bintest b/ortools/sat/samples/StepFunctionSampleSatTest.bintest new file mode 100644 index 00000000000..fc98cf7a59c --- /dev/null +++ b/ortools/sat/samples/StepFunctionSampleSatTest.bintest @@ -0,0 +1 @@ +RUN: $(StepFunctionSampleSat) diff --git a/ortools/sat/samples/StopAfterNSolutionsSampleSatTest.bintest b/ortools/sat/samples/StopAfterNSolutionsSampleSatTest.bintest new file mode 100644 index 00000000000..75c8a4132ab --- /dev/null +++ b/ortools/sat/samples/StopAfterNSolutionsSampleSatTest.bintest @@ -0,0 +1 @@ +RUN: $(StopAfterNSolutionsSampleSat) diff --git a/ortools/set_cover/samples/BUILD.bazel b/ortools/set_cover/samples/BUILD.bazel index b4515ce2640..ba4284be4a9 100644 --- a/ortools/set_cover/samples/BUILD.bazel +++ b/ortools/set_cover/samples/BUILD.bazel @@ -13,7 +13,7 @@ load("@rules_cc//cc:cc_binary.bzl", "cc_binary") 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"]) @@ -31,10 +31,11 @@ cc_binary( ], ) -run_binary_test( +bintest( name = "set_cover_cc_test", size = "small", - binary = ":set_cover_cc", + srcs = [":set_cover_cc_test.bintest"], + named_data = {"set_cover_cc": ":set_cover_cc"}, ) py_binary( @@ -47,8 +48,9 @@ py_binary( ], ) -run_binary_test( +bintest( name = "set_cover_py_test", size = "small", - binary = ":set_cover_py3", + srcs = [":set_cover_py_test.bintest"], + named_data = {"set_cover_py3": ":set_cover_py3"}, ) diff --git a/ortools/set_cover/samples/set_cover_cc_test.bintest b/ortools/set_cover/samples/set_cover_cc_test.bintest new file mode 100644 index 00000000000..a0555f56891 --- /dev/null +++ b/ortools/set_cover/samples/set_cover_cc_test.bintest @@ -0,0 +1 @@ +RUN: $(set_cover_cc) diff --git a/ortools/set_cover/samples/set_cover_py_test.bintest b/ortools/set_cover/samples/set_cover_py_test.bintest new file mode 100644 index 00000000000..5da2026573b --- /dev/null +++ b/ortools/set_cover/samples/set_cover_py_test.bintest @@ -0,0 +1 @@ +RUN: $(set_cover_py3) diff --git a/tools/build/BUILD.bazel b/tools/build/BUILD.bazel new file mode 100644 index 00000000000..6b8d65e59e0 --- /dev/null +++ b/tools/build/BUILD.bazel @@ -0,0 +1,26 @@ +# 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. + +load("@pip_deps//:requirements.bzl", "requirement") +load("@rules_python//python:py_binary.bzl", "py_binary") + +package( + default_applicable_licenses = ["//third_party/ortools:license"], + default_visibility = ["//visibility:public"], +) + +py_binary( + name = "bazel2cmake", + srcs = ["bazel2cmake.py"], + deps = [requirement("absl-py")], +) diff --git a/tools/build/bazel2cmake.py b/tools/build/bazel2cmake.py new file mode 100644 index 00000000000..dfaef7f25d3 --- /dev/null +++ b/tools/build/bazel2cmake.py @@ -0,0 +1,273 @@ +#!/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. + +"""Converts bazel BUILD files to CMakeBazel.txt snippets. + +This script processes BUILD.bazel files and generates CMakeBazel.txt files +containing CMake commands that mirror the bazel build rules. + +Starlark is a subset of Python which allows "executing" them as Python code. +This script "executes" the BUILD.bazel file but provides different definitions +for the target we wants to export to CMake. These new definitions are +responsible for exporting writing the CMake equivalent of Bazel commands. +""" + +from collections.abc import Sequence +import dataclasses +import os + +from absl import app + +# The following global variables are used to interact with the "exec" call. +CURRENT_CMAKE_PIECES = None # The generated cmake is appended to this list. +ROOT_FOLDER = os.getcwd() # The project root. +CURRENT_FOLDER = None # The folder of currently processed BUILD.bazel file. +CURRENT_TARGET_PREFIX = None # The prefix to use for exported targets. + + +@dataclasses.dataclass +class Label: + """Helper class to manipulate bazel labels.""" + + path: str + root: str + cmake_root: str + + def __init__(self, label: str): + """Creates a Label object from a string.""" + if label.startswith("//"): + self.path = label[2:].replace(":", "/") + self.root = ROOT_FOLDER + self.cmake_root = "${CMAKE_SOURCE_DIR}" + elif label.startswith(":"): + self.path = label[1:] + self.root = CURRENT_FOLDER + self.cmake_root = "${CMAKE_CURRENT_SOURCE_DIR}" + else: + assert not label.startswith("/") + self.path = label + self.root = CURRENT_FOLDER + self.cmake_root = "${CMAKE_CURRENT_SOURCE_DIR}" + + def is_file(self) -> bool: + """Returns true if the label is a file.""" + return os.path.isfile(os.path.join(self.root, self.path)) + + def is_target(self) -> bool: + """Returns true if the label is a target.""" + return not self.is_file() + + def as_cmake_target(self) -> str: + """Returns the label as a cmake target.""" + assert self.is_target() + return f"$" + + def as_cmake_file(self) -> str: + """Returns the label as a cmake file.""" + assert self.is_file() + return os.path.join(self.cmake_root, self.path) + + def as_cmake(self) -> str: + """Returns the label as a cmake string.""" + return self.as_cmake_file() if self.is_file() else self.as_cmake_target() + + def as_target_name(self) -> str: + """Returns the label as a target name.""" + assert self.is_target() + return CURRENT_TARGET_PREFIX + self.path + + +@dataclasses.dataclass +class Attr: + """Helper class to manipulate cmake attributes.""" + + name: str + values: list[str] + + def __init__(self, name: str, *values: str): + """Creates an Attr object from a name and a list of values.""" + self.name = name + self.values = values + + def __str__(self): + return f" {self.name} {" ".join(self.values)}" + + def __bool__(self): + return bool(self.values) + + +def name_attr(name: str) -> Attr: + """Returns a NAME attribute.""" + return Attr("NAME", Label(name).as_target_name()) + + +def sources_attr(srcs: Sequence[str], hdrs: Sequence[str]) -> Attr: + """Returns a SOURCES attribute.""" + values = sorted(srcs + hdrs) + return Attr("SOURCES", *values) + + +def link_libraries_attr(deps: Sequence[str]) -> Attr: + """Returns a LINK_LIBRARIES attribute.""" + values = [] + for dep in deps: + if not dep.startswith(":"): + continue + label = Label(dep) + if label.is_target(): + values.append(label.as_target_name()) + return Attr("LINK_LIBRARIES", *values) + + +def type_attr(value: str) -> Attr: + """Returns a TYPE attribute.""" + return Attr("TYPE", value) + + +def env_attr(named_data: dict[str, str]) -> Attr: + """Returns an ENVIRONMENT attribute.""" + values = [] + for key, target in named_data.items(): + label = Label(target) + values.append(f"BINTEST_{key}={label.as_cmake()}") + return Attr("ENVIRONMENT", *values) + + +def script_attr(script: str) -> Attr: + """Returns a SCRIPT attribute.""" + label = Label(script) + assert label.is_file() + return Attr("SCRIPT", label.as_cmake_file()) + + +def add_call(call: str, attrs: Sequence[Attr]) -> str: + """Adds a cmake call to the current cmake pieces.""" + CURRENT_CMAKE_PIECES.append( + f"""{call}( +{'\n'.join(str(a) for a in filter(None, attrs))} +)""" + ) + + +# The functions below are the one replacing the bazel functions. + + +def cc_library( + name, srcs=[], hdrs=[], deps=[], **kwargs +): # pylint: disable=dangerous-default-value + """Adds a cc_library to the current cmake pieces.""" + del kwargs + add_call( + "ortools_cxx_library", + [ + name_attr(name), + sources_attr(srcs, hdrs), + link_libraries_attr(deps), + type_attr("INTERFACE" if not srcs else "SHARED"), + ], + ) + + +def cc_test( + name, srcs=[], hdrs=[], deps=[], **kwargs +): # pylint: disable=dangerous-default-value + """Adds a cc_test to the current cmake pieces.""" + del kwargs + add_call( + "ortools_cxx_test", + [name_attr(name), sources_attr(srcs, hdrs), link_libraries_attr(deps)], + ) + + +def cc_binary( + name, srcs=[], hdrs=[], deps=[], **kwargs +): # pylint: disable=dangerous-default-value + """Adds a cc_binary to the current cmake pieces.""" + del kwargs + add_call( + "ortools_cxx_binary", + [name_attr(name), sources_attr(srcs, hdrs), link_libraries_attr(deps)], + ) + + +def bintest( + name, srcs=[], named_data={}, **kwargs +): # pylint: disable=dangerous-default-value + """Adds a bintest to the current cmake pieces.""" + del kwargs + add_call( + "ortools_cxx_bintest", + [name_attr(name), script_attr(srcs[0]), env_attr(named_data)], + ) + + +# The functions above are the only one accessible when executing the bazel file. +EXEC_GLOBALS = { + "bintest": bintest, + "cc_binary": cc_binary, + "cc_library": cc_library, + "cc_test": cc_test, +} | { + # The function below are ignored and doesn't produce any CMake commands. + name: lambda *kargs, **kwargs: None + for name in [ + # keep sorted go/buildifier#keep-sorted + "build_test", + "cc_proto_library", + "cc_stubby_library", + "java_proto_library", + "java_stubby_library", + "load", + "package", + "proto_library", + "sh_binary", + "sh_test", + ] +} + + +def process_file(prefix: str, file: str): + """Processes a BUILD file and generates a CMakeBazel.txt file.""" + assert os.path.isfile(file) + assert os.path.basename(file) == "BUILD.bazel" + with open(file, "r") as f: + lines = f.read() + global CURRENT_CMAKE_PIECES + CURRENT_CMAKE_PIECES = [] + CURRENT_CMAKE_PIECES.append( + f"# This file is auto generated by bazel2cmake.py from {file}\n" + "# Don't edit manually, your changes will be lost.\n" + "# You can update this file by running:\n" + f"# python3 tools/build/bazel2cmake.py {file}\n" + ) + global CURRENT_FOLDER + CURRENT_FOLDER = os.path.dirname(file) + global CURRENT_TARGET_PREFIX + CURRENT_TARGET_PREFIX = prefix + exec(lines, EXEC_GLOBALS) # pylint: disable=exec-used + output_file = os.path.join(os.path.dirname(file), "CMakeBazel.txt") + with open(output_file, "w") as f: + f.write("\n\n".join(CURRENT_CMAKE_PIECES)) + + +def main(argv: Sequence[str]) -> None: + if len(argv) > 1: + print("bazel2cmake takes no arguments") + return + # TODO: Add more bazel files to autogenerate. + process_file("bzl_cc_example_", "examples/cpp/BUILD.bazel") + + +if __name__ == "__main__": + app.run(main) diff --git a/tools/testing/BUILD.bazel b/tools/testing/BUILD.bazel new file mode 100644 index 00000000000..93fc2058792 --- /dev/null +++ b/tools/testing/BUILD.bazel @@ -0,0 +1,134 @@ +# 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. + +load("@pip_deps//:requirements.bzl", "requirement") +load("@rules_cc//cc:cc_binary.bzl", "cc_binary") +load("@rules_python//python:py_binary.bzl", "py_binary") +load("@rules_python//python:py_library.bzl", "py_library") +load("@rules_python//python:py_test.bzl", "py_test") +load("//tools/testing:bintest.bzl", "bintest", "py_bintest") + +package(default_visibility = ["//visibility:public"]) + +exports_files(["bintest_script_launcher.py"]) + +# The following libraries are used to implement the `bintest` and `py_bintest` rules. + +py_library( + name = "bintest_run_utils", + srcs = ["bintest_run_utils.py"], +) + +py_test( + name = "bintest_run_utils_test", + srcs = ["bintest_run_utils_test.py"], + deps = [ + ":bintest_run_utils", + requirement("absl-py"), + ], +) + +py_library( + name = "bintest_matchers", + srcs = ["bintest_matchers.py"], +) + +py_test( + name = "bintest_matchers_test", + srcs = ["bintest_matchers_test.py"], + deps = [ + ":bintest_matchers", + requirement("absl-py"), + ], +) + +py_library( + name = "binary_test", + srcs = ["binary_test.py"], + deps = [ + ":bintest_matchers", + ":bintest_run_utils", + requirement("absl-py"), + ], +) + +py_library( + name = "bintest_script_runner", + srcs = ["bintest_script_runner.py"], + deps = [ + ":bintest_matchers", + ":bintest_run_utils", + requirement("absl-py"), + ], +) + +py_binary( + name = "bintest_script_launcher", + srcs = ["bintest_script_launcher.py"], + deps = [":bintest_script_runner"], +) + +cc_binary( + name = "echo", + testonly = True, + srcs = ["echo.cc"], +) + +cc_binary( + name = "fail", + testonly = True, + srcs = ["fail.cc"], +) + +py_test( + name = "bintest_script_runner_test", + srcs = ["bintest_script_runner_test.py"], + data = [ + ":echo", + ":fail", + ], + env = { + "BINTEST_ECHO": "$(rootpath :echo)", + "BINTEST_FAIL": "$(rootpath :fail)", + }, + deps = [ + ":bintest_script_runner", + requirement("absl-py"), + ], +) + +# The following targets demonstrate the usage of the `bintest` and `py_bintest` rules. + +cc_binary( + name = "print_args", + srcs = ["print_args.cc"], +) + +bintest( + name = "print_args_bintest", + srcs = ["print_args.bintest"], + named_data = { + "print_args": ":print_args", + "data_file": ":print_args_data.txt", + }, +) + +py_bintest( + name = "print_args_test", + srcs = ["print_args_test.py"], + named_data = { + "print_args": ":print_args", + "data_file": ":print_args_data.txt", + }, + deps = [requirement("absl-py")], +) diff --git a/tools/testing/README.md b/tools/testing/README.md new file mode 100644 index 00000000000..0e2dbfba324 --- /dev/null +++ b/tools/testing/README.md @@ -0,0 +1,230 @@ +# Binary testing + +This folder contains facilities to **test executable files**. +We offer two APIs: + +* `bintest`: A simple scripting language to write simple tests such as + checking execution success or asserting the presence of text or numbers + within bounds, +* `py_bintest`: An extension of the unit testing framework that makes it easy + to invoke the binary under test, extract values from its output, and check + them within the unittest framework. + +## `bintest` + +It offers two commands `RUN:` and `CHECK:`. + +* The `RUN:` command executes the binary and asserts it ran successfully. \ + The passed arguments can use the `$(