move set_cover code in its own directory
This commit is contained in:
@@ -412,6 +412,7 @@ file(GLOB_RECURSE OR_TOOLS_PROTO_FILES RELATIVE ${PROJECT_SOURCE_DIR}
|
||||
"ortools/packing/*.proto"
|
||||
"ortools/sat/*.proto"
|
||||
"ortools/scheduling/*.proto"
|
||||
"ortools/set_cover/*.proto"
|
||||
"ortools/util/*.proto"
|
||||
)
|
||||
if(USE_PDLP OR BUILD_MATH_OPT)
|
||||
@@ -539,6 +540,7 @@ foreach(SUBPROJECT IN ITEMS
|
||||
packing
|
||||
routing
|
||||
scheduling
|
||||
set_cover
|
||||
port
|
||||
util)
|
||||
add_subdirectory(ortools/${SUBPROJECT})
|
||||
|
||||
@@ -151,6 +151,7 @@ file(GLOB_RECURSE OR_TOOLS_PROTO_PY_FILES RELATIVE ${PROJECT_SOURCE_DIR}
|
||||
"ortools/routing/*.proto"
|
||||
"ortools/sat/*.proto"
|
||||
"ortools/scheduling/*.proto"
|
||||
"ortools/set_cover/*.proto"
|
||||
"ortools/util/*.proto"
|
||||
)
|
||||
list(REMOVE_ITEM OR_TOOLS_PROTO_PY_FILES "ortools/constraint_solver/demon_profiler.proto")
|
||||
@@ -290,6 +291,7 @@ foreach(SUBPROJECT IN ITEMS
|
||||
routing
|
||||
sat
|
||||
scheduling
|
||||
set_cover
|
||||
util)
|
||||
add_subdirectory(ortools/${SUBPROJECT}/python)
|
||||
endforeach()
|
||||
@@ -351,6 +353,8 @@ file(GENERATE OUTPUT ${PYTHON_PROJECT_DIR}/sat/python/__init__.py CONTENT "")
|
||||
file(GENERATE OUTPUT ${PYTHON_PROJECT_DIR}/sat/colab/__init__.py CONTENT "")
|
||||
file(GENERATE OUTPUT ${PYTHON_PROJECT_DIR}/scheduling/__init__.py CONTENT "")
|
||||
file(GENERATE OUTPUT ${PYTHON_PROJECT_DIR}/scheduling/python/__init__.py CONTENT "")
|
||||
file(GENERATE OUTPUT ${PYTHON_PROJECT_DIR}/set_cover/__init__.py CONTENT "")
|
||||
file(GENERATE OUTPUT ${PYTHON_PROJECT_DIR}/set_cover/python/__init__.py CONTENT "")
|
||||
file(GENERATE OUTPUT ${PYTHON_PROJECT_DIR}/util/__init__.py CONTENT "")
|
||||
file(GENERATE OUTPUT ${PYTHON_PROJECT_DIR}/util/python/__init__.py CONTENT "")
|
||||
|
||||
@@ -624,8 +628,6 @@ add_custom_command(
|
||||
$<TARGET_FILE:init_pybind11> ${PYTHON_PROJECT}/init/python
|
||||
COMMAND ${CMAKE_COMMAND} -E copy
|
||||
$<TARGET_FILE:knapsack_solver_pybind11> ${PYTHON_PROJECT}/algorithms/python
|
||||
COMMAND ${CMAKE_COMMAND} -E copy
|
||||
$<TARGET_FILE:set_cover_pybind11> ${PYTHON_PROJECT}/algorithms/python
|
||||
COMMAND ${CMAKE_COMMAND} -E copy
|
||||
$<TARGET_FILE:linear_sum_assignment_pybind11> ${PYTHON_PROJECT}/graph/python
|
||||
COMMAND ${CMAKE_COMMAND} -E copy
|
||||
@@ -657,6 +659,8 @@ add_custom_command(
|
||||
$<TARGET_FILE:cp_model_helper_pybind11> ${PYTHON_PROJECT}/sat/python
|
||||
COMMAND ${CMAKE_COMMAND} -E copy
|
||||
$<TARGET_FILE:rcpsp_pybind11> ${PYTHON_PROJECT}/scheduling/python
|
||||
COMMAND ${CMAKE_COMMAND} -E copy
|
||||
$<TARGET_FILE:set_cover_pybind11> ${PYTHON_PROJECT}/set_cover/python
|
||||
COMMAND ${CMAKE_COMMAND} -E copy
|
||||
$<TARGET_FILE:sorted_interval_list_pybind11> ${PYTHON_PROJECT}/util/python
|
||||
COMMAND ${CMAKE_COMMAND} -E touch ${PROJECT_BINARY_DIR}/python/pybind11_timestamp
|
||||
@@ -665,7 +669,6 @@ add_custom_command(
|
||||
DEPENDS
|
||||
init_pybind11
|
||||
knapsack_solver_pybind11
|
||||
set_cover_pybind11
|
||||
linear_sum_assignment_pybind11
|
||||
max_flow_pybind11
|
||||
min_cost_flow_pybind11
|
||||
@@ -679,6 +682,7 @@ add_custom_command(
|
||||
$<TARGET_NAME_IF_EXISTS:pdlp_pybind11>
|
||||
cp_model_helper_pybind11
|
||||
rcpsp_pybind11
|
||||
set_cover_pybind11
|
||||
sorted_interval_list_pybind11
|
||||
WORKING_DIRECTORY python
|
||||
COMMAND_EXPAND_LISTS)
|
||||
@@ -704,7 +708,6 @@ add_custom_command(
|
||||
COMMAND ${CMAKE_COMMAND} -E remove -f stub_timestamp
|
||||
COMMAND ${stubgen_EXECUTABLE} -p ortools.init.python.init --output .
|
||||
COMMAND ${stubgen_EXECUTABLE} -p ortools.algorithms.python.knapsack_solver --output .
|
||||
COMMAND ${stubgen_EXECUTABLE} -p ortools.algorithms.python.set_cover --output .
|
||||
COMMAND ${stubgen_EXECUTABLE} -p ortools.graph.python.linear_sum_assignment --output .
|
||||
COMMAND ${stubgen_EXECUTABLE} -p ortools.graph.python.max_flow --output .
|
||||
COMMAND ${stubgen_EXECUTABLE} -p ortools.graph.python.min_cost_flow --output .
|
||||
@@ -719,6 +722,7 @@ add_custom_command(
|
||||
COMMAND ${stubgen_EXECUTABLE} -p ortools.routing.python.model --output .
|
||||
COMMAND ${stubgen_EXECUTABLE} -p ortools.sat.python.cp_model_helper --output .
|
||||
COMMAND ${stubgen_EXECUTABLE} -p ortools.scheduling.python.rcpsp --output .
|
||||
COMMAND ${stubgen_EXECUTABLE} -p ortools.set_cover.python.set_cover --output .
|
||||
COMMAND ${stubgen_EXECUTABLE} -p ortools.util.python.sorted_interval_list --output .
|
||||
COMMAND ${CMAKE_COMMAND} -E touch ${PROJECT_BINARY_DIR}/python/stub_timestamp
|
||||
MAIN_DEPENDENCY
|
||||
|
||||
@@ -250,163 +250,6 @@ cc_test(
|
||||
|
||||
# query matching library.
|
||||
|
||||
# Weighted set covering library.
|
||||
|
||||
proto_library(
|
||||
name = "set_cover_proto",
|
||||
srcs = ["set_cover.proto"],
|
||||
deps = [
|
||||
"//ortools/util:int128_proto",
|
||||
],
|
||||
)
|
||||
|
||||
cc_proto_library(
|
||||
name = "set_cover_cc_proto",
|
||||
deps = [":set_cover_proto"],
|
||||
)
|
||||
|
||||
py_proto_library(
|
||||
name = "set_cover_py_pb2",
|
||||
deps = [":set_cover_proto"],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "set_cover_lagrangian",
|
||||
srcs = ["set_cover_lagrangian.cc"],
|
||||
hdrs = ["set_cover_lagrangian.h"],
|
||||
deps = [
|
||||
":adjustable_k_ary_heap",
|
||||
":set_cover_invariant",
|
||||
":set_cover_model",
|
||||
"//ortools/base:threadpool",
|
||||
"@com_google_absl//absl/log:check",
|
||||
"@com_google_absl//absl/synchronization",
|
||||
],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "set_cover_model",
|
||||
srcs = ["set_cover_model.cc"],
|
||||
hdrs = ["set_cover_model.h"],
|
||||
deps = [
|
||||
":radix_sort",
|
||||
":set_cover_cc_proto",
|
||||
"//ortools/base:intops",
|
||||
"//ortools/base:strong_vector",
|
||||
"@com_google_absl//absl/log",
|
||||
"@com_google_absl//absl/log:check",
|
||||
"@com_google_absl//absl/numeric:bits",
|
||||
"@com_google_absl//absl/random",
|
||||
"@com_google_absl//absl/random:distributions",
|
||||
"@com_google_absl//absl/strings",
|
||||
"@com_google_absl//absl/strings:str_format",
|
||||
"@com_google_absl//absl/types:span",
|
||||
],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "set_cover_invariant",
|
||||
srcs = ["set_cover_invariant.cc"],
|
||||
hdrs = ["set_cover_invariant.h"],
|
||||
deps = [
|
||||
":set_cover_cc_proto",
|
||||
":set_cover_model",
|
||||
"//ortools/base",
|
||||
"//ortools/base:mathutil",
|
||||
"@com_google_absl//absl/log",
|
||||
"@com_google_absl//absl/log:check",
|
||||
"@com_google_absl//absl/types:span",
|
||||
],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "set_cover_heuristics",
|
||||
srcs = ["set_cover_heuristics.cc"],
|
||||
hdrs = ["set_cover_heuristics.h"],
|
||||
deps = [
|
||||
":adjustable_k_ary_heap",
|
||||
":set_cover_invariant",
|
||||
":set_cover_model",
|
||||
"//ortools/base",
|
||||
"@com_google_absl//absl/base",
|
||||
"@com_google_absl//absl/log",
|
||||
"@com_google_absl//absl/log:check",
|
||||
"@com_google_absl//absl/numeric:bits",
|
||||
"@com_google_absl//absl/random:distributions",
|
||||
"@com_google_absl//absl/types:span",
|
||||
],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "set_cover_mip",
|
||||
srcs = ["set_cover_mip.cc"],
|
||||
hdrs = ["set_cover_mip.h"],
|
||||
deps = [
|
||||
":set_cover_invariant",
|
||||
":set_cover_model",
|
||||
"//ortools/linear_solver",
|
||||
"//ortools/lp_data:base",
|
||||
"@com_google_absl//absl/log",
|
||||
"@com_google_absl//absl/log:check",
|
||||
"@com_google_absl//absl/types:span",
|
||||
],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "set_cover_reader",
|
||||
srcs = ["set_cover_reader.cc"],
|
||||
hdrs = ["set_cover_reader.h"],
|
||||
deps = [
|
||||
":set_cover_cc_proto",
|
||||
":set_cover_model",
|
||||
"//ortools/base:file",
|
||||
"//ortools/util:filelineiter",
|
||||
"@com_google_absl//absl/log",
|
||||
"@com_google_absl//absl/log:check",
|
||||
"@com_google_absl//absl/strings",
|
||||
"@com_google_absl//absl/strings:str_format",
|
||||
"@com_google_absl//absl/strings:string_view",
|
||||
],
|
||||
)
|
||||
|
||||
cc_binary(
|
||||
name = "set_cover_solve",
|
||||
srcs = ["set_cover_solve.cc"],
|
||||
deps = [
|
||||
":set_cover_heuristics",
|
||||
":set_cover_invariant",
|
||||
":set_cover_model",
|
||||
":set_cover_reader",
|
||||
"//ortools/base",
|
||||
"//ortools/base:timer",
|
||||
"@com_google_absl//absl/flags:flag",
|
||||
"@com_google_absl//absl/log",
|
||||
"@com_google_absl//absl/log:check",
|
||||
"@com_google_absl//absl/strings",
|
||||
"@com_google_absl//absl/time",
|
||||
],
|
||||
)
|
||||
|
||||
cc_test(
|
||||
name = "set_cover_test",
|
||||
size = "medium",
|
||||
timeout = "eternal",
|
||||
srcs = ["set_cover_test.cc"],
|
||||
deps = [
|
||||
":set_cover_cc_proto",
|
||||
":set_cover_heuristics",
|
||||
":set_cover_invariant",
|
||||
":set_cover_mip",
|
||||
":set_cover_model",
|
||||
"//ortools/base:gmock_main",
|
||||
"//ortools/base:parse_text_proto",
|
||||
"@com_google_absl//absl/log",
|
||||
"@com_google_absl//absl/log:check",
|
||||
"@com_google_absl//absl/strings",
|
||||
"@com_google_benchmark//:benchmark",
|
||||
],
|
||||
)
|
||||
|
||||
# Graph automorphism libraries.
|
||||
cc_library(
|
||||
name = "dense_doubly_linked_list",
|
||||
|
||||
@@ -36,7 +36,6 @@ target_link_libraries(${NAME} PRIVATE
|
||||
if(BUILD_TESTING)
|
||||
file(GLOB _TEST_SRCS "*_test.cc")
|
||||
list(FILTER _TEST_SRCS EXCLUDE REGEX ".*_stress_test.cc")
|
||||
list(FILTER _TEST_SRCS EXCLUDE REGEX "set_cover_test.cc")
|
||||
foreach(_FULL_FILE_NAME IN LISTS _TEST_SRCS)
|
||||
get_filename_component(_NAME ${_FULL_FILE_NAME} NAME_WE)
|
||||
get_filename_component(_FILE_NAME ${_FULL_FILE_NAME} NAME)
|
||||
|
||||
@@ -78,30 +78,3 @@ py_test(
|
||||
requirement("absl-py"),
|
||||
],
|
||||
)
|
||||
|
||||
# set_cover
|
||||
pybind_extension(
|
||||
name = "set_cover",
|
||||
srcs = ["set_cover.cc"],
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//ortools/algorithms:set_cover_cc_proto",
|
||||
"//ortools/algorithms:set_cover_heuristics",
|
||||
"//ortools/algorithms:set_cover_invariant",
|
||||
"//ortools/algorithms:set_cover_model",
|
||||
"//ortools/algorithms:set_cover_reader",
|
||||
"@com_google_absl//absl/strings",
|
||||
"@pybind11_protobuf//pybind11_protobuf:native_proto_caster",
|
||||
],
|
||||
)
|
||||
|
||||
py_test(
|
||||
name = "set_cover_test",
|
||||
srcs = ["set_cover_test.py"],
|
||||
python_version = "PY3",
|
||||
deps = [
|
||||
":set_cover",
|
||||
"//ortools/algorithms:set_cover_py_pb2",
|
||||
requirement("absl-py"),
|
||||
],
|
||||
)
|
||||
|
||||
@@ -30,33 +30,3 @@ endif()
|
||||
|
||||
target_link_libraries(knapsack_solver_pybind11 PRIVATE ${PROJECT_NAMESPACE}::ortools)
|
||||
add_library(${PROJECT_NAMESPACE}::knapsack_solver_pybind11 ALIAS knapsack_solver_pybind11)
|
||||
|
||||
# set_cover
|
||||
pybind11_add_module(set_cover_pybind11 MODULE set_cover.cc)
|
||||
set_target_properties(set_cover_pybind11 PROPERTIES
|
||||
LIBRARY_OUTPUT_NAME "set_cover")
|
||||
|
||||
# note: macOS is APPLE and also UNIX !
|
||||
if(APPLE)
|
||||
set_target_properties(set_cover_pybind11 PROPERTIES
|
||||
SUFFIX ".so"
|
||||
INSTALL_RPATH "@loader_path;@loader_path/../../../${PYTHON_PROJECT}/.libs"
|
||||
)
|
||||
elseif(UNIX)
|
||||
set_target_properties(set_cover_pybind11 PROPERTIES
|
||||
INSTALL_RPATH "$ORIGIN:$ORIGIN/../../../${PYTHON_PROJECT}/.libs"
|
||||
)
|
||||
endif()
|
||||
|
||||
target_link_libraries(set_cover_pybind11 PRIVATE
|
||||
${PROJECT_NAMESPACE}::ortools
|
||||
pybind11_native_proto_caster
|
||||
)
|
||||
add_library(${PROJECT_NAMESPACE}::set_cover_pybind11 ALIAS set_cover_pybind11)
|
||||
|
||||
if(BUILD_TESTING)
|
||||
file(GLOB PYTHON_SRCS "*_test.py")
|
||||
foreach(FILE_NAME IN LISTS PYTHON_SRCS)
|
||||
add_python_test(FILE_NAME ${FILE_NAME})
|
||||
endforeach()
|
||||
endif()
|
||||
|
||||
@@ -59,7 +59,6 @@ setup(
|
||||
],
|
||||
'@PYTHON_PROJECT@.algorithms.python':[
|
||||
'$<TARGET_FILE_NAME:knapsack_solver_pybind11>',
|
||||
'$<TARGET_FILE_NAME:set_cover_pybind11>',
|
||||
'*.pyi'
|
||||
],
|
||||
'@PYTHON_PROJECT@.bop':['*.pyi'],
|
||||
@@ -125,6 +124,10 @@ setup(
|
||||
'$<TARGET_FILE_NAME:rcpsp_pybind11>',
|
||||
'*.pyi'
|
||||
],
|
||||
'@PYTHON_PROJECT@.set_cover.python':[
|
||||
'$<TARGET_FILE_NAME:set_cover_pybind11>',
|
||||
'*.pyi'
|
||||
],
|
||||
'@PYTHON_PROJECT@.util.python':[
|
||||
'$<TARGET_FILE_NAME:sorted_interval_list_pybind11>',
|
||||
'*.pyi'
|
||||
|
||||
268
ortools/set_cover/BUILD.bazel
Normal file
268
ortools/set_cover/BUILD.bazel
Normal file
@@ -0,0 +1,268 @@
|
||||
# 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("@com_google_protobuf//bazel:cc_proto_library.bzl", "cc_proto_library")
|
||||
load("@com_google_protobuf//bazel:proto_library.bzl", "proto_library")
|
||||
load("@rules_cc//cc:defs.bzl", "cc_library")
|
||||
load("@rules_python//python:proto.bzl", "py_proto_library")
|
||||
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
# Description:
|
||||
# A solver for weighted set covering with side constraints
|
||||
|
||||
proto_library(
|
||||
name = "set_cover_proto",
|
||||
srcs = ["set_cover.proto"],
|
||||
deps = [
|
||||
"//ortools/util:int128_proto",
|
||||
],
|
||||
)
|
||||
|
||||
cc_proto_library(
|
||||
name = "set_cover_cc_proto",
|
||||
deps = [":set_cover_proto"],
|
||||
)
|
||||
|
||||
py_proto_library(
|
||||
name = "set_cover_py_pb2",
|
||||
deps = [":set_cover_proto"],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "set_cover_lagrangian",
|
||||
srcs = ["set_cover_lagrangian.cc"],
|
||||
hdrs = ["set_cover_lagrangian.h"],
|
||||
deps = [
|
||||
":set_cover_invariant",
|
||||
":set_cover_model",
|
||||
"//ortools/algorithms:adjustable_k_ary_heap",
|
||||
"//ortools/base:threadpool",
|
||||
"@com_google_absl//absl/log:check",
|
||||
"@com_google_absl//absl/synchronization",
|
||||
],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "set_cover_model",
|
||||
srcs = ["set_cover_model.cc"],
|
||||
hdrs = ["set_cover_model.h"],
|
||||
deps = [
|
||||
":set_cover_cc_proto",
|
||||
"//ortools/algorithms:radix_sort",
|
||||
"//ortools/base:intops",
|
||||
"//ortools/base:strong_vector",
|
||||
"@com_google_absl//absl/log",
|
||||
"@com_google_absl//absl/log:check",
|
||||
"@com_google_absl//absl/numeric:bits",
|
||||
"@com_google_absl//absl/random",
|
||||
"@com_google_absl//absl/random:distributions",
|
||||
"@com_google_absl//absl/strings",
|
||||
"@com_google_absl//absl/strings:str_format",
|
||||
"@com_google_absl//absl/types:span",
|
||||
],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "set_cover_invariant",
|
||||
srcs = ["set_cover_invariant.cc"],
|
||||
hdrs = ["set_cover_invariant.h"],
|
||||
deps = [
|
||||
":set_cover_cc_proto",
|
||||
":set_cover_model",
|
||||
"//ortools/base",
|
||||
"//ortools/base:mathutil",
|
||||
"@com_google_absl//absl/log",
|
||||
"@com_google_absl//absl/log:check",
|
||||
"@com_google_absl//absl/types:span",
|
||||
],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "set_cover_heuristics",
|
||||
srcs = ["set_cover_heuristics.cc"],
|
||||
hdrs = ["set_cover_heuristics.h"],
|
||||
deps = [
|
||||
":set_cover_invariant",
|
||||
":set_cover_model",
|
||||
"//ortools/algorithms:adjustable_k_ary_heap",
|
||||
"//ortools/base",
|
||||
"@com_google_absl//absl/base",
|
||||
"@com_google_absl//absl/log",
|
||||
"@com_google_absl//absl/log:check",
|
||||
"@com_google_absl//absl/numeric:bits",
|
||||
"@com_google_absl//absl/random:distributions",
|
||||
"@com_google_absl//absl/types:span",
|
||||
],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "set_cover_mip",
|
||||
srcs = ["set_cover_mip.cc"],
|
||||
hdrs = ["set_cover_mip.h"],
|
||||
deps = [
|
||||
":set_cover_invariant",
|
||||
":set_cover_model",
|
||||
"//ortools/linear_solver",
|
||||
"//ortools/lp_data:base",
|
||||
"@com_google_absl//absl/log",
|
||||
"@com_google_absl//absl/log:check",
|
||||
"@com_google_absl//absl/types:span",
|
||||
],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "set_cover_reader",
|
||||
srcs = ["set_cover_reader.cc"],
|
||||
hdrs = ["set_cover_reader.h"],
|
||||
deps = [
|
||||
":set_cover_cc_proto",
|
||||
":set_cover_model",
|
||||
"//ortools/base:file",
|
||||
"//ortools/util:filelineiter",
|
||||
"@com_google_absl//absl/log",
|
||||
"@com_google_absl//absl/log:check",
|
||||
"@com_google_absl//absl/strings",
|
||||
"@com_google_absl//absl/strings:str_format",
|
||||
"@com_google_absl//absl/strings:string_view",
|
||||
],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "assignment",
|
||||
srcs = ["assignment.cc"],
|
||||
hdrs = ["assignment.h"],
|
||||
deps = [
|
||||
":capacity_invariant",
|
||||
":set_cover_invariant",
|
||||
":set_cover_model",
|
||||
"//ortools/base:mathutil",
|
||||
"@com_google_absl//absl/log",
|
||||
"@com_google_absl//absl/log:check",
|
||||
],
|
||||
)
|
||||
|
||||
cc_test(
|
||||
name = "assignment_test",
|
||||
srcs = ["assignment_test.cc"],
|
||||
deps = [
|
||||
":assignment",
|
||||
":set_cover_invariant",
|
||||
":set_cover_model",
|
||||
"//ortools/base:gmock_main",
|
||||
"@com_google_absl//absl/log:check",
|
||||
],
|
||||
)
|
||||
|
||||
cc_binary(
|
||||
name = "set_cover_solve",
|
||||
srcs = ["set_cover_solve.cc"],
|
||||
deps = [
|
||||
":set_cover_heuristics",
|
||||
":set_cover_invariant",
|
||||
":set_cover_model",
|
||||
":set_cover_reader",
|
||||
"//ortools/base",
|
||||
"//ortools/base:timer",
|
||||
"@com_google_absl//absl/flags:flag",
|
||||
"@com_google_absl//absl/log",
|
||||
"@com_google_absl//absl/log:check",
|
||||
"@com_google_absl//absl/strings",
|
||||
"@com_google_absl//absl/time",
|
||||
],
|
||||
)
|
||||
|
||||
cc_test(
|
||||
name = "set_cover_test",
|
||||
size = "medium",
|
||||
timeout = "eternal",
|
||||
srcs = ["set_cover_test.cc"],
|
||||
deps = [
|
||||
":set_cover_cc_proto",
|
||||
":set_cover_heuristics",
|
||||
":set_cover_invariant",
|
||||
":set_cover_mip",
|
||||
":set_cover_model",
|
||||
"//ortools/base:gmock_main",
|
||||
"//ortools/base:parse_text_proto",
|
||||
"@com_google_absl//absl/log",
|
||||
"@com_google_absl//absl/log:check",
|
||||
"@com_google_absl//absl/strings",
|
||||
"@com_google_benchmark//:benchmark",
|
||||
],
|
||||
)
|
||||
|
||||
# Side constraint: capacity.
|
||||
|
||||
proto_library(
|
||||
name = "capacity_proto",
|
||||
srcs = ["capacity.proto"],
|
||||
)
|
||||
|
||||
cc_proto_library(
|
||||
name = "capacity_cc_proto",
|
||||
deps = [":capacity_proto"],
|
||||
)
|
||||
|
||||
py_proto_library(
|
||||
name = "capacity_py_pb2",
|
||||
deps = [":capacity_proto"],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "capacity_model",
|
||||
srcs = ["capacity_model.cc"],
|
||||
hdrs = ["capacity_model.h"],
|
||||
deps = [
|
||||
":capacity_cc_proto",
|
||||
":set_cover_model",
|
||||
"//ortools/base:intops",
|
||||
"//ortools/base:strong_vector",
|
||||
"@com_google_absl//absl/log",
|
||||
"@com_google_absl//absl/log:check",
|
||||
],
|
||||
)
|
||||
|
||||
cc_test(
|
||||
name = "capacity_model_test",
|
||||
srcs = ["capacity_model_test.cc"],
|
||||
deps = [
|
||||
":capacity_model",
|
||||
":set_cover_model",
|
||||
"//ortools/base:gmock_main",
|
||||
"//ortools/base:message_matchers",
|
||||
],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "capacity_invariant",
|
||||
srcs = ["capacity_invariant.cc"],
|
||||
hdrs = ["capacity_invariant.h"],
|
||||
deps = [
|
||||
":capacity_model",
|
||||
":set_cover_model",
|
||||
"@com_google_absl//absl/log",
|
||||
"@com_google_absl//absl/log:check",
|
||||
],
|
||||
)
|
||||
|
||||
cc_test(
|
||||
name = "capacity_invariant_test",
|
||||
srcs = ["capacity_invariant_test.cc"],
|
||||
deps = [
|
||||
":capacity_invariant",
|
||||
":capacity_model",
|
||||
":set_cover_model",
|
||||
"//ortools/base:gmock_main",
|
||||
],
|
||||
)
|
||||
51
ortools/set_cover/CMakeLists.txt
Normal file
51
ortools/set_cover/CMakeLists.txt
Normal file
@@ -0,0 +1,51 @@
|
||||
# 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.
|
||||
|
||||
file(GLOB _SRCS "*.h" "*.cc")
|
||||
list(FILTER _SRCS EXCLUDE REGEX ".*/.*_test.cc")
|
||||
list(FILTER _SRCS EXCLUDE REGEX ".*/set_cover_solve.cc")
|
||||
|
||||
set(NAME ${PROJECT_NAME}_set_cover)
|
||||
|
||||
# Will be merge in libortools.so
|
||||
#add_library(${NAME} STATIC ${_SRCS})
|
||||
add_library(${NAME} OBJECT ${_SRCS})
|
||||
set_target_properties(${NAME} PROPERTIES
|
||||
POSITION_INDEPENDENT_CODE ON
|
||||
)
|
||||
target_include_directories(${NAME} PRIVATE
|
||||
${PROJECT_SOURCE_DIR}
|
||||
${PROJECT_BINARY_DIR})
|
||||
target_link_libraries(${NAME} PRIVATE
|
||||
protobuf::libprotobuf
|
||||
${PROJECT_NAMESPACE}::ortools_proto)
|
||||
#add_library(${PROJECT_NAMESPACE}::set_cover ALIAS ${NAME})
|
||||
|
||||
if(BUILD_TESTING)
|
||||
file(GLOB _TEST_SRCS "*_test.cc")
|
||||
foreach(_FULL_FILE_NAME IN LISTS _TEST_SRCS)
|
||||
get_filename_component(_NAME ${_FULL_FILE_NAME} NAME_WE)
|
||||
get_filename_component(_FILE_NAME ${_FULL_FILE_NAME} NAME)
|
||||
ortools_cxx_test(
|
||||
NAME
|
||||
set_cover_${_NAME}
|
||||
SOURCES
|
||||
${_FILE_NAME}
|
||||
LINK_LIBRARIES
|
||||
benchmark::benchmark
|
||||
GTest::gtest
|
||||
GTest::gtest_main
|
||||
GTest::gmock
|
||||
)
|
||||
endforeach()
|
||||
endif()
|
||||
126
ortools/set_cover/assignment.cc
Normal file
126
ortools/set_cover/assignment.cc
Normal file
@@ -0,0 +1,126 @@
|
||||
// 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.
|
||||
|
||||
#include "ortools/set_cover/assignment.h"
|
||||
|
||||
#include "absl/log/check.h"
|
||||
#include "ortools/base/logging.h"
|
||||
#include "ortools/base/mathutil.h"
|
||||
#include "ortools/set_cover/capacity_invariant.h"
|
||||
#include "ortools/set_cover/set_cover_invariant.h"
|
||||
#include "ortools/set_cover/set_cover_model.h"
|
||||
|
||||
namespace operations_research {
|
||||
|
||||
void SetCoverAssignment::Clear() {
|
||||
cost_ = Cost(0.0);
|
||||
values_.assign(model_.subset_costs().size(), false);
|
||||
DCHECK_EQ(values_.size(), model_.subset_costs().size())
|
||||
<< "The cost vector (length: " << model_.subset_costs().size()
|
||||
<< ") is inconsistent with the assignment (length: " << values_.size()
|
||||
<< ")";
|
||||
}
|
||||
|
||||
void SetCoverAssignment::AttachInvariant(SetCoverInvariant* i) {
|
||||
CHECK(constraint_ == nullptr);
|
||||
constraint_ = i;
|
||||
}
|
||||
|
||||
void SetCoverAssignment::AttachInvariant(CapacityInvariant* i) {
|
||||
CHECK(constraint_ != nullptr);
|
||||
side_constraints_.push_back(i);
|
||||
// TODO(user): call i->SetAssignment or similar so that each and every
|
||||
// constraint uses the same solution storage.
|
||||
}
|
||||
|
||||
void SetCoverAssignment::SetValue(
|
||||
SubsetIndex subset, bool is_selected,
|
||||
SetCoverInvariant::ConsistencyLevel set_cover_consistency) {
|
||||
DVLOG(1) << "[Assignment] Subset " << subset << " becoming " << is_selected
|
||||
<< "; used to be " << values_[subset];
|
||||
|
||||
DCHECK(CheckConsistency());
|
||||
if (values_[subset] == is_selected) return;
|
||||
|
||||
values_[subset] = is_selected;
|
||||
if (is_selected) {
|
||||
cost_ += model_.subset_costs()[subset];
|
||||
if (constraint_) {
|
||||
constraint_->Select(subset, set_cover_consistency);
|
||||
}
|
||||
for (CapacityInvariant* const capacity_constraint : side_constraints_) {
|
||||
capacity_constraint->Select(subset);
|
||||
}
|
||||
} else {
|
||||
cost_ -= model_.subset_costs()[subset];
|
||||
if (constraint_) {
|
||||
constraint_->Deselect(subset, set_cover_consistency);
|
||||
}
|
||||
for (CapacityInvariant* const capacity_constraint : side_constraints_) {
|
||||
capacity_constraint->Deselect(subset);
|
||||
}
|
||||
}
|
||||
DCHECK(CheckConsistency());
|
||||
}
|
||||
|
||||
SetCoverSolutionResponse SetCoverAssignment::ExportSolutionAsProto() const {
|
||||
SetCoverSolutionResponse message;
|
||||
message.set_num_subsets(values_.size());
|
||||
message.set_cost(cost_);
|
||||
for (SubsetIndex subset(0);
|
||||
subset < SubsetIndex(model_.subset_costs().size()); ++subset) {
|
||||
if (values_[subset]) {
|
||||
message.add_subset(subset.value());
|
||||
}
|
||||
}
|
||||
return message;
|
||||
}
|
||||
|
||||
void SetCoverAssignment::LoadAssignment(const SubsetBoolVector& solution) {
|
||||
DCHECK_EQ(solution.size(), values_.size());
|
||||
values_ = solution;
|
||||
cost_ = ComputeCost(values_);
|
||||
}
|
||||
|
||||
void SetCoverAssignment::ImportSolutionFromProto(
|
||||
const SetCoverSolutionResponse& message) {
|
||||
values_.resize(SubsetIndex(message.num_subsets()), false);
|
||||
cost_ = Cost(0.0);
|
||||
for (auto s : message.subset()) {
|
||||
SubsetIndex subset(s);
|
||||
values_[subset] = true;
|
||||
cost_ += model_.subset_costs()[subset];
|
||||
}
|
||||
CHECK(MathUtil::AlmostEquals(message.cost(), cost_));
|
||||
DCHECK(CheckConsistency());
|
||||
}
|
||||
|
||||
bool SetCoverAssignment::CheckConsistency() const {
|
||||
Cost cst = ComputeCost(values_);
|
||||
CHECK(MathUtil::AlmostEquals(cost_, cst));
|
||||
return true;
|
||||
}
|
||||
|
||||
Cost SetCoverAssignment::ComputeCost(const SubsetBoolVector& choices) const {
|
||||
Cost cst = 0.0;
|
||||
SubsetIndex subset(0);
|
||||
for (const bool b : choices) {
|
||||
if (b) {
|
||||
cst += model_.subset_costs()[subset];
|
||||
}
|
||||
++subset;
|
||||
}
|
||||
return cst;
|
||||
}
|
||||
|
||||
} // namespace operations_research
|
||||
104
ortools/set_cover/assignment.h
Normal file
104
ortools/set_cover/assignment.h
Normal file
@@ -0,0 +1,104 @@
|
||||
// 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.
|
||||
|
||||
#ifndef OR_TOOLS_SET_COVER_ASSIGNMENT_H_
|
||||
#define OR_TOOLS_SET_COVER_ASSIGNMENT_H_
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "ortools/set_cover/capacity_invariant.h"
|
||||
#include "ortools/set_cover/set_cover_invariant.h"
|
||||
#include "ortools/set_cover/set_cover_model.h"
|
||||
|
||||
namespace operations_research {
|
||||
|
||||
// `SetCoverAssignment` stores a possibly partial, possibly infeasible solution
|
||||
// to a `SetCoverModel`. It only stores a solution and no metadata,
|
||||
// so that it can be shared efficiently among constraints.
|
||||
//
|
||||
// This class is equivalent to an `Assignment` object in the CP/routing solver.
|
||||
// (//ortools/routing).
|
||||
class SetCoverAssignment {
|
||||
public:
|
||||
// Constructs an empty set covering assignment.
|
||||
//
|
||||
// The model size or costs must not change after the invariant was built.
|
||||
// The caller must guarantee that the model outlives the assignment without
|
||||
// changing its costs.
|
||||
explicit SetCoverAssignment(const SetCoverModel& m)
|
||||
: model_(m), constraint_(nullptr), side_constraints_({}) {
|
||||
Clear();
|
||||
}
|
||||
|
||||
// Clears the current assignment.
|
||||
void Clear();
|
||||
|
||||
// Adds a constraint to the problem. At least one set-covering constraint is
|
||||
// required; use side constraints as required (no set-covering constraint can
|
||||
// be a side constraint).
|
||||
void AttachInvariant(SetCoverInvariant* i);
|
||||
void AttachInvariant(CapacityInvariant* i);
|
||||
|
||||
// Returns the cost of current solution.
|
||||
Cost cost() const { return cost_; }
|
||||
|
||||
// Returns the subset assignment vector.
|
||||
const SubsetBoolVector& assignment() const { return values_; }
|
||||
|
||||
// Sets the subset's assignment to the given bool.
|
||||
void SetValue(SubsetIndex subset, bool is_selected,
|
||||
SetCoverInvariant::ConsistencyLevel set_cover_consistency);
|
||||
|
||||
// Returns the current solution as a proto.
|
||||
SetCoverSolutionResponse ExportSolutionAsProto() const;
|
||||
|
||||
// Loads the solution and recomputes the data in the invariant.
|
||||
//
|
||||
// The given assignment must fit the model of this assignment.
|
||||
void LoadAssignment(const SubsetBoolVector& solution);
|
||||
|
||||
// Imports the solution from a proto.
|
||||
//
|
||||
// The given assignment must fit the model of this assignment.
|
||||
void ImportSolutionFromProto(const SetCoverSolutionResponse& message);
|
||||
|
||||
// Checks the consistency of the solution (between the selected subsets and
|
||||
// the solution cost).
|
||||
bool CheckConsistency() const;
|
||||
|
||||
private:
|
||||
// Computes the cost for the given choices.
|
||||
Cost ComputeCost(const SubsetBoolVector& choices) const;
|
||||
|
||||
// The weighted set covering model on which the solver is run.
|
||||
const SetCoverModel& model_;
|
||||
|
||||
// Current cost of the assignment.
|
||||
Cost cost_;
|
||||
|
||||
// Current assignment. Takes |S| bits.
|
||||
SubsetBoolVector values_;
|
||||
|
||||
// Constraints that this assignment must respect. The constraints are checked
|
||||
// every time the assignment changes (with the methods `Flip`, `Select`, and
|
||||
// `Deselect`).
|
||||
//
|
||||
// For now, the only side constraints are capacity constraints.
|
||||
SetCoverInvariant* constraint_;
|
||||
// TODO(user): merge the several constraints into one invariant.
|
||||
std::vector<CapacityInvariant*> side_constraints_;
|
||||
};
|
||||
|
||||
} // namespace operations_research
|
||||
|
||||
#endif // OR_TOOLS_SET_COVER_ASSIGNMENT_H_
|
||||
140
ortools/set_cover/assignment_test.cc
Normal file
140
ortools/set_cover/assignment_test.cc
Normal file
@@ -0,0 +1,140 @@
|
||||
// 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.
|
||||
|
||||
#include "ortools/set_cover/assignment.h"
|
||||
|
||||
#include "absl/log/check.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "ortools/set_cover/set_cover_invariant.h"
|
||||
#include "ortools/set_cover/set_cover_model.h"
|
||||
|
||||
namespace operations_research {
|
||||
namespace {
|
||||
|
||||
SetCoverModel MakeBasicModel() {
|
||||
// 3 elements, 4 subsets (all of unitary cost).
|
||||
// Optimal cost: 2 (subsets #0 and #1).
|
||||
SetCoverModel model;
|
||||
model.AddEmptySubset(1);
|
||||
model.AddElementToLastSubset(0);
|
||||
model.AddEmptySubset(1);
|
||||
model.AddElementToLastSubset(1);
|
||||
model.AddElementToLastSubset(2);
|
||||
model.AddEmptySubset(1);
|
||||
model.AddElementToLastSubset(1);
|
||||
model.AddEmptySubset(1);
|
||||
model.AddElementToLastSubset(2);
|
||||
CHECK(model.ComputeFeasibility());
|
||||
return model;
|
||||
}
|
||||
|
||||
TEST(SetCoverAssignment, EmbryonicModelHasZeroCost) {
|
||||
SetCoverModel model;
|
||||
model.AddEmptySubset(1);
|
||||
model.AddElementToLastSubset(0);
|
||||
SetCoverAssignment assignment(model);
|
||||
|
||||
EXPECT_TRUE(assignment.CheckConsistency());
|
||||
EXPECT_EQ(assignment.cost(), 0.0);
|
||||
EXPECT_EQ(assignment.assignment(), SubsetBoolVector(1, false));
|
||||
}
|
||||
|
||||
TEST(SetCoverAssignment, BasicModelHasCost) {
|
||||
SetCoverModel model = MakeBasicModel();
|
||||
ASSERT_EQ(model.num_subsets(), 4);
|
||||
ASSERT_EQ(model.num_elements(), 3);
|
||||
SetCoverAssignment assignment(model);
|
||||
|
||||
EXPECT_TRUE(assignment.CheckConsistency());
|
||||
EXPECT_EQ(assignment.cost(), 0.0);
|
||||
EXPECT_EQ(assignment.assignment(), SubsetBoolVector(4, false));
|
||||
|
||||
assignment.SetValue(SubsetIndex(0), true,
|
||||
SetCoverInvariant::ConsistencyLevel::kInconsistent);
|
||||
assignment.SetValue(SubsetIndex(1), true,
|
||||
SetCoverInvariant::ConsistencyLevel::kInconsistent);
|
||||
|
||||
EXPECT_TRUE(assignment.CheckConsistency());
|
||||
EXPECT_EQ(assignment.cost(), 2.0);
|
||||
EXPECT_EQ(assignment.assignment(),
|
||||
SubsetBoolVector({true, true, false, false}));
|
||||
|
||||
assignment.SetValue(SubsetIndex(1), false,
|
||||
SetCoverInvariant::ConsistencyLevel::kInconsistent);
|
||||
|
||||
EXPECT_TRUE(assignment.CheckConsistency());
|
||||
EXPECT_EQ(assignment.cost(), 1.0);
|
||||
EXPECT_EQ(assignment.assignment(),
|
||||
SubsetBoolVector({true, false, false, false}));
|
||||
}
|
||||
|
||||
TEST(SetCoverAssignment, BasicModelWorksWithSetCoverInvariant) {
|
||||
// Changes to the solution imply changes in the invariant.
|
||||
SetCoverModel model = MakeBasicModel();
|
||||
ASSERT_EQ(model.num_subsets(), 4);
|
||||
ASSERT_EQ(model.num_elements(), 3);
|
||||
SetCoverAssignment assignment(model);
|
||||
|
||||
SetCoverInvariant inv(&model);
|
||||
assignment.AttachInvariant(&inv);
|
||||
|
||||
ASSERT_EQ(assignment.assignment(), SubsetBoolVector(4, false));
|
||||
EXPECT_EQ(inv.num_uncovered_elements(), 3);
|
||||
|
||||
assignment.SetValue(SubsetIndex(0), true,
|
||||
SetCoverInvariant::ConsistencyLevel::kRedundancy);
|
||||
assignment.SetValue(SubsetIndex(1), true,
|
||||
SetCoverInvariant::ConsistencyLevel::kRedundancy);
|
||||
|
||||
EXPECT_EQ(inv.num_uncovered_elements(), 0);
|
||||
}
|
||||
|
||||
TEST(SetCoverAssignment, ImportFromVector) {
|
||||
SetCoverModel model = MakeBasicModel();
|
||||
ASSERT_EQ(model.num_subsets(), 4);
|
||||
ASSERT_EQ(model.num_elements(), 3);
|
||||
const SubsetBoolVector reference_assignment = {true, false, false, false};
|
||||
|
||||
SetCoverAssignment assignment(model);
|
||||
assignment.LoadAssignment(reference_assignment);
|
||||
EXPECT_TRUE(assignment.CheckConsistency());
|
||||
EXPECT_EQ(assignment.cost(), 1.0);
|
||||
EXPECT_EQ(assignment.assignment(), reference_assignment);
|
||||
}
|
||||
|
||||
TEST(SetCoverAssignment, ExportImportAllPullTogetherAsATeam) {
|
||||
SetCoverModel model = MakeBasicModel();
|
||||
ASSERT_EQ(model.num_subsets(), 4);
|
||||
ASSERT_EQ(model.num_elements(), 3);
|
||||
|
||||
SetCoverAssignment assignment_1(model);
|
||||
assignment_1.SetValue(SubsetIndex(0), true,
|
||||
SetCoverInvariant::ConsistencyLevel::kInconsistent);
|
||||
assignment_1.SetValue(SubsetIndex(1), true,
|
||||
SetCoverInvariant::ConsistencyLevel::kInconsistent);
|
||||
ASSERT_EQ(assignment_1.cost(), 2.0);
|
||||
ASSERT_EQ(assignment_1.assignment(),
|
||||
SubsetBoolVector({true, true, false, false}));
|
||||
ASSERT_TRUE(assignment_1.CheckConsistency());
|
||||
|
||||
SetCoverSolutionResponse message = assignment_1.ExportSolutionAsProto();
|
||||
SetCoverAssignment assignment_2(model);
|
||||
assignment_2.ImportSolutionFromProto(message);
|
||||
|
||||
EXPECT_EQ(assignment_1.cost(), assignment_2.cost());
|
||||
EXPECT_EQ(assignment_1.assignment(), assignment_2.assignment());
|
||||
EXPECT_TRUE(assignment_2.CheckConsistency());
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace operations_research
|
||||
75
ortools/set_cover/capacity.proto
Normal file
75
ortools/set_cover/capacity.proto
Normal file
@@ -0,0 +1,75 @@
|
||||
// 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;
|
||||
|
||||
option java_package = "com.google.ortools.setcover";
|
||||
option java_multiple_files = true;
|
||||
|
||||
// Represents a capacity constraint to be used in conjunction with a
|
||||
// SetCoverProto. This constraint only considers one dimension.
|
||||
//
|
||||
// Such a capacity constraint mathematically looks like:
|
||||
// min_capacity <= \sum_{e in elements} weight_e * x_e <= max_capacity
|
||||
// where either `min_capacity` or `max_capacity` can be omitted. `x_e` indicates
|
||||
// for a given solution `x` whether the element `e` is selected and counts for
|
||||
// this capacity constraint (`x_e == 1`) or not (`x_e == 0`). The weights are
|
||||
// given in `capacity_term`, each of them being a reference to an element being
|
||||
// present in a subset (in set-covering parlance) and its weight.
|
||||
//
|
||||
// For instance, this constraint can be used together with a set-covering
|
||||
// problem where parcels (element) must be covered by trucks (subsets) while
|
||||
// respecting truck capacities (this object). Each element can be covered by a
|
||||
// given set of trucks (set-covering problem); if an element is taken within a
|
||||
// truck, it uses some capacity for this truck (such as weight).
|
||||
//
|
||||
// In particular, this representation does not imply that a given element must
|
||||
// have the same weight in all the capacity constraints of a set-covering
|
||||
// problem (e.g., the same parcel might have different weights depending on
|
||||
// which truck is being considered).
|
||||
message CapacityConstraintProto {
|
||||
message CapacityTerm {
|
||||
// The subset this weight corresponds to (index of the subset in the
|
||||
// `subset` repeated field in `SetCoverProto`).
|
||||
int32 subset = 1;
|
||||
|
||||
message ElementWeightPair {
|
||||
// The element this weight corresponds to (value of `element` in
|
||||
// `SetCoverProto.Subset`).
|
||||
int32 element = 1;
|
||||
|
||||
// The weight of the element.
|
||||
double weight = 2;
|
||||
}
|
||||
|
||||
repeated ElementWeightPair element_weights = 2;
|
||||
}
|
||||
|
||||
// The list of terms in the constraint.
|
||||
//
|
||||
// The list is supposed to be in canonical form, which means it is sorted
|
||||
// first by increasing subset index then increasing element index.
|
||||
// No duplicate term is allowed (two terms for the same element in the same
|
||||
// subset).
|
||||
repeated CapacityTerm capacity_term = 1;
|
||||
|
||||
// The minimum amount of resource that must be consumed. At least one of
|
||||
// `min_capacity` and `max_capacity` must be present.
|
||||
double min_capacity = 2;
|
||||
|
||||
// The maximum amount of resource that can be consumed. At least one of
|
||||
// `min_capacity` and `max_capacity` must be present.
|
||||
double max_capacity = 3;
|
||||
}
|
||||
112
ortools/set_cover/capacity_invariant.cc
Normal file
112
ortools/set_cover/capacity_invariant.cc
Normal file
@@ -0,0 +1,112 @@
|
||||
// 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.
|
||||
|
||||
#include "ortools/set_cover/capacity_invariant.h"
|
||||
|
||||
#include "absl/log/check.h"
|
||||
#include "ortools/base/logging.h"
|
||||
#include "ortools/set_cover/capacity_model.h"
|
||||
#include "ortools/set_cover/set_cover_model.h"
|
||||
|
||||
namespace operations_research {
|
||||
|
||||
void CapacityInvariant::Clear() {
|
||||
current_slack_ = 0.0;
|
||||
is_selected_.assign(set_cover_model_->num_subsets(), false);
|
||||
}
|
||||
|
||||
double CapacityInvariant::ComputeSlackChange(const SubsetIndex subset) const {
|
||||
double slack_change = 0.0;
|
||||
for (CapacityTermIndex term : model_->TermRange()) {
|
||||
if (model_->GetTermSubsetIndex(term) == subset) {
|
||||
// Hypothesis: GetTermSubsetIndex(term) is an element of the subset.
|
||||
// This information is stored in a SetCoverModel instance.
|
||||
slack_change += model_->GetTermCapacityWeight(term);
|
||||
}
|
||||
}
|
||||
return slack_change;
|
||||
}
|
||||
|
||||
bool CapacityInvariant::SlackChangeFitsConstraint(
|
||||
const double slack_change) const {
|
||||
const double new_slack = current_slack_ + slack_change;
|
||||
return new_slack >= model_->GetMinimumCapacity() &&
|
||||
new_slack <= model_->GetMaximumCapacity();
|
||||
}
|
||||
|
||||
bool CapacityInvariant::Flip(SubsetIndex subset) {
|
||||
DCHECK_LT(subset.value(), set_cover_model_->num_subsets())
|
||||
<< "Invalid subset: " << subset;
|
||||
return !is_selected_[subset] ? Select(subset) : Deselect(subset);
|
||||
}
|
||||
|
||||
bool CapacityInvariant::CanFlip(SubsetIndex subset) const {
|
||||
DCHECK_LT(subset.value(), set_cover_model_->num_subsets())
|
||||
<< "Invalid subset: " << subset;
|
||||
return !is_selected_[subset] ? CanSelect(subset) : CanDeselect(subset);
|
||||
}
|
||||
|
||||
bool CapacityInvariant::Select(SubsetIndex subset) {
|
||||
DVLOG(1) << "[Capacity constraint] Selecting subset " << subset;
|
||||
DCHECK(!is_selected_[subset]);
|
||||
|
||||
const double slack_change = ComputeSlackChange(subset);
|
||||
if (!SlackChangeFitsConstraint(slack_change)) {
|
||||
DVLOG(1) << "[Capacity constraint] Selecting subset " << subset
|
||||
<< ": infeasible";
|
||||
return false;
|
||||
}
|
||||
|
||||
is_selected_[subset] = true;
|
||||
current_slack_ += slack_change;
|
||||
DVLOG(1) << "[Capacity constraint] New slack: " << current_slack_;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CapacityInvariant::CanSelect(SubsetIndex subset) const {
|
||||
DVLOG(1) << "[Capacity constraint] Can select subset " << subset << "?";
|
||||
DCHECK(!is_selected_[subset]);
|
||||
|
||||
const double slack_change = ComputeSlackChange(subset);
|
||||
DVLOG(1) << "[Capacity constraint] New slack if selecting: "
|
||||
<< current_slack_ + slack_change;
|
||||
return SlackChangeFitsConstraint(slack_change);
|
||||
}
|
||||
|
||||
bool CapacityInvariant::Deselect(SubsetIndex subset) {
|
||||
DVLOG(1) << "[Capacity constraint] Deselecting subset " << subset;
|
||||
DCHECK(is_selected_[subset]);
|
||||
|
||||
const double slack_change = -ComputeSlackChange(subset);
|
||||
if (!SlackChangeFitsConstraint(slack_change)) {
|
||||
DVLOG(1) << "[Capacity constraint] Deselecting subset " << subset
|
||||
<< ": infeasible";
|
||||
return false;
|
||||
}
|
||||
|
||||
is_selected_[subset] = false;
|
||||
current_slack_ += slack_change;
|
||||
DVLOG(1) << "[Capacity constraint] New slack: " << current_slack_;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CapacityInvariant::CanDeselect(SubsetIndex subset) const {
|
||||
DVLOG(1) << "[Capacity constraint] Can deselect subset " << subset << "?";
|
||||
DCHECK(is_selected_[subset]);
|
||||
|
||||
const double slack_change = -ComputeSlackChange(subset);
|
||||
DVLOG(1) << "[Capacity constraint] New slack if deselecting: "
|
||||
<< current_slack_ + slack_change;
|
||||
return SlackChangeFitsConstraint(slack_change);
|
||||
}
|
||||
} // namespace operations_research
|
||||
107
ortools/set_cover/capacity_invariant.h
Normal file
107
ortools/set_cover/capacity_invariant.h
Normal file
@@ -0,0 +1,107 @@
|
||||
// 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.
|
||||
|
||||
#ifndef OR_TOOLS_SET_COVER_CAPACITY_INVARIANT_H_
|
||||
#define OR_TOOLS_SET_COVER_CAPACITY_INVARIANT_H_
|
||||
|
||||
#include "absl/log/check.h"
|
||||
#include "ortools/set_cover/capacity_model.h"
|
||||
#include "ortools/set_cover/set_cover_model.h"
|
||||
|
||||
namespace operations_research {
|
||||
class CapacityInvariant {
|
||||
public:
|
||||
// Constructs an empty capacity invariant state.
|
||||
// The model may not change after the invariant was built.
|
||||
explicit CapacityInvariant(CapacityModel* m, SetCoverModel* sc)
|
||||
: model_(m), set_cover_model_(sc) {
|
||||
DCHECK(model_->ComputeFeasibility());
|
||||
Clear();
|
||||
}
|
||||
|
||||
// Clears the invariant.
|
||||
void Clear();
|
||||
|
||||
// Returns `true` when the constraint is not violated by this flipping move
|
||||
// and incrementally updates the invariant. Otherwise, returns `false` and
|
||||
// does not change the object.
|
||||
//
|
||||
// Flips is_selected_[subset] to its negation, by calling Select or Deselect
|
||||
// depending on value.
|
||||
bool Flip(SubsetIndex subset);
|
||||
|
||||
// Returns `true` when the constraint would not be violated if this flipping
|
||||
// move is performed. Otherwise, returns `false`. The object never changes.
|
||||
bool CanFlip(SubsetIndex subset) const;
|
||||
|
||||
// Returns `true` when the constraint is not violated by selecting all of the
|
||||
// items in the subset and incrementally updates the invariant. Otherwise,
|
||||
// returns `false` and does not change the object. (If the subset is already
|
||||
// selected, the behavior is undefined.)
|
||||
bool Select(SubsetIndex subset);
|
||||
|
||||
// Returns `true` when the constraint would not be violated by selecting all
|
||||
// of the items in the subset. Otherwise, returns `false`. The object never
|
||||
// changes. (If the subset is already selected, the behavior is undefined.)
|
||||
bool CanSelect(SubsetIndex subset) const;
|
||||
|
||||
// Returns `true` when the constraint is not violated by unselecting all of
|
||||
// the items in the subset and incrementally updates the invariant. Otherwise,
|
||||
// returns `false` and does not change the object. (If the subset is already
|
||||
// not selected, the behavior is undefined.)
|
||||
bool Deselect(SubsetIndex subset);
|
||||
|
||||
// Returns `true` when the constraint would not be violated by unselecting all
|
||||
// of the items in the subset. Otherwise, returns `false`. The object never
|
||||
// changes. (If the subset is already not selected, the behavior is
|
||||
// undefined.)
|
||||
bool CanDeselect(SubsetIndex subset) const;
|
||||
|
||||
// TODO(user): implement the functions where you only select/deselect an
|
||||
// item of a subset (instead of all items at once). The behavior gets much
|
||||
// more interesting: if two subsets cover one item and the two item-subset
|
||||
// combinations are terms in this capacity constraint, only one of them counts
|
||||
// towards the capacity.
|
||||
//
|
||||
// The solver is not yet ready for this move: you need to
|
||||
// decide which subset covers a given item, instead of ensuring that an item
|
||||
// is covered by at least one subset. Currently, we could aggregate the terms
|
||||
// per subset to make the code much faster when (de)selecting at the cost of
|
||||
// increased initialization times.
|
||||
|
||||
private:
|
||||
// The capacity-constraint model on which the invariant runs.
|
||||
CapacityModel* model_;
|
||||
|
||||
// The set-cover model on which the invariant runs.
|
||||
SetCoverModel* set_cover_model_;
|
||||
|
||||
// Current slack of the constraint.
|
||||
operations_research::CapacityWeight current_slack_;
|
||||
|
||||
// Current solution assignment.
|
||||
// TODO(user): reuse the assignment of a SetCoverInvariant.
|
||||
SubsetBoolVector is_selected_;
|
||||
|
||||
// Determines the change in slack when (de)selecting the given subset.
|
||||
// The returned value is nonnegative; add it to the slack when selecting
|
||||
// and subtract it when deselecting.
|
||||
double ComputeSlackChange(SubsetIndex subset) const;
|
||||
|
||||
// Determines whether the given slack change violates the constraint
|
||||
// (`false`) or not (`true`).
|
||||
bool SlackChangeFitsConstraint(double slack_change) const;
|
||||
};
|
||||
} // namespace operations_research
|
||||
|
||||
#endif // OR_TOOLS_SET_COVER_CAPACITY_INVARIANT_H_
|
||||
55
ortools/set_cover/capacity_invariant_test.cc
Normal file
55
ortools/set_cover/capacity_invariant_test.cc
Normal file
@@ -0,0 +1,55 @@
|
||||
// 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.
|
||||
|
||||
#include "ortools/set_cover/capacity_invariant.h"
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include "ortools/set_cover/capacity_model.h"
|
||||
#include "ortools/set_cover/set_cover_model.h"
|
||||
|
||||
namespace operations_research {
|
||||
namespace {
|
||||
|
||||
TEST(CapacityModel, ChecksConstraintViolation) {
|
||||
// Compatibility constraint: choose either of the two subsets.
|
||||
SetCoverModel sc;
|
||||
sc.AddEmptySubset(1.0);
|
||||
sc.AddElementToLastSubset(ElementIndex(0));
|
||||
sc.AddEmptySubset(1.0);
|
||||
sc.AddElementToLastSubset(ElementIndex(0));
|
||||
CapacityModel m(0.0, 1.0);
|
||||
m.AddTerm(SubsetIndex(0), ElementIndex(0), CapacityWeight(1.0));
|
||||
m.AddTerm(SubsetIndex(1), ElementIndex(0), CapacityWeight(1.0));
|
||||
EXPECT_TRUE(m.ComputeFeasibility());
|
||||
|
||||
CapacityInvariant cinv(&m, &sc);
|
||||
// Current assignment: [false, false]. Current activation: 0.
|
||||
EXPECT_TRUE(cinv.CanFlip(SubsetIndex(0))); // All moves are possible.
|
||||
EXPECT_TRUE(cinv.CanFlip(SubsetIndex(1)));
|
||||
|
||||
EXPECT_TRUE(cinv.Flip(SubsetIndex(0))); // Select returns true.
|
||||
EXPECT_TRUE(cinv.CanFlip(SubsetIndex(0))); // Undoing: still valid.
|
||||
EXPECT_FALSE(cinv.CanFlip(SubsetIndex(1))); // Impossible move.
|
||||
EXPECT_FALSE(cinv.Flip(SubsetIndex(1))); // Select returns false.
|
||||
// Current assignment: [true, false]. Current activation: 1.
|
||||
|
||||
EXPECT_TRUE(cinv.Flip(SubsetIndex(0))); // Deselect returns true.
|
||||
EXPECT_TRUE(cinv.CanFlip(SubsetIndex(0))); // Undoing: still valid.
|
||||
EXPECT_TRUE(cinv.CanFlip(SubsetIndex(1))); // Valid when 0 not selected.
|
||||
EXPECT_TRUE(cinv.Flip(SubsetIndex(1))); // Select returns true.
|
||||
EXPECT_FALSE(cinv.CanFlip(SubsetIndex(0))); // Impossible move.
|
||||
EXPECT_TRUE(cinv.CanFlip(SubsetIndex(1))); // Undoing: still valid.
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace operations_research
|
||||
131
ortools/set_cover/capacity_model.cc
Normal file
131
ortools/set_cover/capacity_model.cc
Normal file
@@ -0,0 +1,131 @@
|
||||
// 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.
|
||||
|
||||
#include "ortools/set_cover/capacity_model.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
#include <numeric>
|
||||
#include <vector>
|
||||
|
||||
#include "absl/log/check.h"
|
||||
#include "ortools/base/logging.h"
|
||||
#include "ortools/set_cover/set_cover_model.h"
|
||||
|
||||
namespace operations_research {
|
||||
void CapacityModel::AddTerm(SubsetIndex subset, ElementIndex element,
|
||||
CapacityWeight weight) {
|
||||
CHECK(std::isfinite(weight));
|
||||
|
||||
subsets_.push_back(subset);
|
||||
elements_.push_back(element);
|
||||
weights_.push_back(weight);
|
||||
|
||||
CHECK_EQ(elements_.size(), subsets_.size());
|
||||
CHECK_EQ(elements_.size(), weights_.size());
|
||||
}
|
||||
|
||||
void CapacityModel::SetMinimumCapacity(CapacityWeight min_capacity) {
|
||||
CHECK(!std::isnan(min_capacity));
|
||||
CHECK_NE(min_capacity, std::numeric_limits<CapacityWeight>::max());
|
||||
min_capacity_ = min_capacity;
|
||||
}
|
||||
|
||||
void CapacityModel::SetMaximumCapacity(CapacityWeight max_capacity) {
|
||||
CHECK(!std::isnan(max_capacity));
|
||||
CHECK_NE(max_capacity, std::numeric_limits<CapacityWeight>::min());
|
||||
max_capacity_ = max_capacity;
|
||||
}
|
||||
|
||||
bool CapacityModel::ComputeFeasibility() const {
|
||||
if (weights_.empty()) {
|
||||
// A sum of zero terms is zero.
|
||||
return min_capacity_ <= 0.0 && max_capacity_ >= 0.0;
|
||||
}
|
||||
|
||||
// Compute the minimum and maximum constraint activations.
|
||||
CapacityWeight min_activation = 0.0;
|
||||
CapacityWeight max_activation = 0.0;
|
||||
for (const CapacityWeight weight : weights_) {
|
||||
if (weight < 0.0) {
|
||||
min_activation += weight;
|
||||
} else {
|
||||
max_activation += weight;
|
||||
}
|
||||
}
|
||||
|
||||
DVLOG(1) << "[Capacity constraint] Activation bounds: [" << min_activation
|
||||
<< ", " << max_activation << "]";
|
||||
DVLOG(1) << "[Capacity constraint] Capacity bounds: [" << min_capacity_
|
||||
<< ", " << max_capacity_ << "]";
|
||||
return min_activation <= max_capacity_ && max_activation >= min_capacity_;
|
||||
}
|
||||
|
||||
std::vector<CapacityTermIndex> CapacityModel::CanonicalIndexing() {
|
||||
std::vector<CapacityTermIndex> idx(num_terms());
|
||||
std::iota(idx.begin(), idx.end(), CapacityTermIndex(0));
|
||||
// TODO(user): use RadixSort when it's available. The implementation in
|
||||
// radix_sort.h does not support a lambda for comparing.
|
||||
std::sort(idx.begin(), idx.end(),
|
||||
[&](CapacityTermIndex lhs, CapacityTermIndex rhs) -> bool {
|
||||
return subsets_[lhs] < subsets_[rhs]
|
||||
? true
|
||||
: elements_[lhs] < elements_[rhs];
|
||||
});
|
||||
return idx;
|
||||
}
|
||||
|
||||
CapacityConstraintProto CapacityModel::ExportModelAsProto() {
|
||||
CapacityConstraintProto proto;
|
||||
proto.set_min_capacity(min_capacity_);
|
||||
proto.set_max_capacity(max_capacity_);
|
||||
|
||||
CapacityConstraintProto::CapacityTerm* current_term = nullptr;
|
||||
|
||||
for (CapacityTermIndex i : CanonicalIndexing()) {
|
||||
if (current_term == nullptr ||
|
||||
current_term->subset() != subsets_[i].value()) {
|
||||
current_term = proto.add_capacity_term();
|
||||
current_term->set_subset(subsets_[i].value());
|
||||
}
|
||||
DCHECK(current_term != nullptr);
|
||||
|
||||
CapacityConstraintProto::CapacityTerm::ElementWeightPair* pair =
|
||||
current_term->add_element_weights();
|
||||
pair->set_element(elements_[i].value());
|
||||
pair->set_weight(weights_[i]);
|
||||
}
|
||||
|
||||
return proto;
|
||||
}
|
||||
|
||||
void CapacityModel::ImportModelFromProto(const CapacityConstraintProto& proto) {
|
||||
elements_.clear();
|
||||
subsets_.clear();
|
||||
weights_.clear();
|
||||
|
||||
SetMinimumCapacity(proto.min_capacity());
|
||||
SetMaximumCapacity(proto.max_capacity());
|
||||
|
||||
ReserveNumTerms(proto.capacity_term_size());
|
||||
for (const CapacityConstraintProto::CapacityTerm& term :
|
||||
proto.capacity_term()) {
|
||||
for (const CapacityConstraintProto::CapacityTerm::ElementWeightPair& pair :
|
||||
term.element_weights()) {
|
||||
AddTerm(SubsetIndex(term.subset()), ElementIndex(pair.element()),
|
||||
pair.weight());
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace operations_research
|
||||
156
ortools/set_cover/capacity_model.h
Normal file
156
ortools/set_cover/capacity_model.h
Normal file
@@ -0,0 +1,156 @@
|
||||
// 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.
|
||||
|
||||
#ifndef OR_TOOLS_SET_COVER_CAPACITY_MODEL_H_
|
||||
#define OR_TOOLS_SET_COVER_CAPACITY_MODEL_H_
|
||||
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
#include <vector>
|
||||
|
||||
#include "absl/log/check.h"
|
||||
#include "ortools/base/strong_int.h"
|
||||
#include "ortools/base/strong_vector.h"
|
||||
#include "ortools/set_cover/capacity.pb.h"
|
||||
#include "ortools/set_cover/set_cover_model.h"
|
||||
|
||||
// Representation class for the capacity side-constraint for a weighted
|
||||
// set-covering problem.
|
||||
//
|
||||
// This constraint restricts the selection of elements within subsets that
|
||||
// respect the constraint. Such a constraint can mix elements in any subset.
|
||||
//
|
||||
// Using the same mixed-integer-programming formulation as `set_cover_model.h`,
|
||||
// this class corresponds to the following constraint:
|
||||
// min_capacity <= \sum_{e in elements} weight_e * x_e <= max_capacity
|
||||
|
||||
namespace operations_research {
|
||||
// Basic type for weights. For now, the same as `Cost` for the set covering.
|
||||
using CapacityWeight = double;
|
||||
|
||||
// Term index in a capacity constraint.
|
||||
DEFINE_STRONG_INT_TYPE(CapacityTermIndex, BaseInt);
|
||||
|
||||
// The terms are represented as three aligned vectors: the element, the subset,
|
||||
// and the weight. Each vector is indexed by the term.
|
||||
using CapacityElements =
|
||||
util_intops::StrongVector<CapacityTermIndex, ElementIndex>;
|
||||
using CapacitySubsets =
|
||||
util_intops::StrongVector<CapacityTermIndex, SubsetIndex>;
|
||||
using CapacityWeights =
|
||||
util_intops::StrongVector<CapacityTermIndex, CapacityWeight>;
|
||||
|
||||
// Main class for describing a single capacity constraint in the context of a
|
||||
// set-covering problem.
|
||||
class CapacityModel {
|
||||
public:
|
||||
// Builds an empty capacity constraint.
|
||||
//
|
||||
// Use either WithMinimumWeight or WithMaximumWeight to set only one of the
|
||||
// two bounds.
|
||||
CapacityModel(CapacityWeight min, CapacityWeight max)
|
||||
: elements_(),
|
||||
subsets_(),
|
||||
weights_(),
|
||||
min_capacity_(min),
|
||||
max_capacity_(max) {
|
||||
// At least one bound must be set. Otherwise, the constraint is vacuous.
|
||||
CHECK(!std::isnan(min_capacity_));
|
||||
CHECK(!std::isnan(max_capacity_));
|
||||
CHECK(min_capacity_ != std::numeric_limits<CapacityWeight>::min() ||
|
||||
max_capacity_ != std::numeric_limits<CapacityWeight>::max());
|
||||
}
|
||||
|
||||
static CapacityModel WithMinimumWeight(CapacityWeight min) {
|
||||
return CapacityModel(min, std::numeric_limits<CapacityWeight>::max());
|
||||
}
|
||||
|
||||
static CapacityModel WithMaximumWeight(CapacityWeight max) {
|
||||
return CapacityModel(std::numeric_limits<CapacityWeight>::min(), max);
|
||||
}
|
||||
|
||||
// Returns the current number of terms in the constraint.
|
||||
BaseInt num_terms() const { return elements_.size(); }
|
||||
|
||||
// Returns the range of terms.
|
||||
util_intops::StrongIntRange<CapacityTermIndex> TermRange() const {
|
||||
return util_intops::StrongIntRange<CapacityTermIndex>(
|
||||
CapacityTermIndex(num_terms()));
|
||||
}
|
||||
|
||||
// Adds a new term to the constraint.
|
||||
// This will CHECK-fail if the weight is infinite or a NaN.
|
||||
void AddTerm(SubsetIndex subset, ElementIndex element, CapacityWeight weight);
|
||||
|
||||
// Returns the element, subset, or capacity of the given term.
|
||||
ElementIndex GetTermElementIndex(CapacityTermIndex term) const {
|
||||
return elements_[term];
|
||||
}
|
||||
SubsetIndex GetTermSubsetIndex(CapacityTermIndex term) const {
|
||||
return subsets_[term];
|
||||
}
|
||||
CapacityWeight GetTermCapacityWeight(CapacityTermIndex term) const {
|
||||
return weights_[term];
|
||||
}
|
||||
|
||||
// Sets the lower/upper bounds for the constraint.
|
||||
// This will CHECK-fail if a capacity is a NaN.
|
||||
void SetMinimumCapacity(CapacityWeight min_capacity);
|
||||
void SetMaximumCapacity(CapacityWeight max_capacity);
|
||||
|
||||
// Returns the lower/upper bounds for the constraint.
|
||||
CapacityWeight GetMinimumCapacity() const { return min_capacity_; }
|
||||
CapacityWeight GetMaximumCapacity() const { return max_capacity_; }
|
||||
|
||||
// Returns true if the constraint is feasible, i.e. there is at least one
|
||||
// assignment that satisfies the constraint.
|
||||
bool ComputeFeasibility() const;
|
||||
|
||||
// Reserves num_terms terms in the model.
|
||||
void ReserveNumTerms(BaseInt num_terms) {
|
||||
ReserveNumTerms(CapacityTermIndex(num_terms));
|
||||
}
|
||||
|
||||
void ReserveNumTerms(CapacityTermIndex num_terms) {
|
||||
subsets_.reserve(num_terms);
|
||||
elements_.reserve(num_terms);
|
||||
weights_.reserve(num_terms);
|
||||
}
|
||||
|
||||
// Returns the model as a CapacityConstraintProto.
|
||||
//
|
||||
// The function is not const because the terms need to be sorted for the
|
||||
// representation as a protobuf to be canonical.
|
||||
CapacityConstraintProto ExportModelAsProto();
|
||||
|
||||
// Imports the model from a CapacityConstraintProto.
|
||||
void ImportModelFromProto(const CapacityConstraintProto& proto);
|
||||
|
||||
private:
|
||||
// The terms in the constraint.
|
||||
CapacityElements elements_;
|
||||
CapacitySubsets subsets_;
|
||||
CapacityWeights weights_;
|
||||
|
||||
// The bounds of the constraint. Both are always active at the same time.
|
||||
// An inactive constraint corresponds to a capacity set to ±∞.
|
||||
CapacityWeight min_capacity_;
|
||||
CapacityWeight max_capacity_;
|
||||
|
||||
// Returns a canonical indexing of the constraint, i.e. reading the terms in
|
||||
// this order yields the order that is explained in the proto.
|
||||
std::vector<CapacityTermIndex> CanonicalIndexing();
|
||||
};
|
||||
} // namespace operations_research
|
||||
|
||||
#endif // OR_TOOLS_SET_COVER_CAPACITY_MODEL_H_
|
||||
231
ortools/set_cover/capacity_model_test.cc
Normal file
231
ortools/set_cover/capacity_model_test.cc
Normal file
@@ -0,0 +1,231 @@
|
||||
// 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.
|
||||
|
||||
#include "ortools/set_cover/capacity_model.h"
|
||||
|
||||
#include <limits>
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include "ortools/base/gmock.h"
|
||||
#include "ortools/base/message_matchers.h"
|
||||
#include "ortools/set_cover/set_cover_model.h"
|
||||
|
||||
namespace operations_research {
|
||||
namespace {
|
||||
|
||||
using ::testing::EqualsProto;
|
||||
|
||||
TEST(CapacityModel, ConstructorRequiresOneBound) {
|
||||
EXPECT_DEATH(CapacityModel(std::numeric_limits<CapacityWeight>::min(),
|
||||
std::numeric_limits<CapacityWeight>::max()),
|
||||
"min");
|
||||
}
|
||||
|
||||
TEST(CapacityModel, ConstructorRejectsNaN) {
|
||||
EXPECT_DEATH(CapacityModel(std::numeric_limits<CapacityWeight>::quiet_NaN(),
|
||||
std::numeric_limits<CapacityWeight>::quiet_NaN()),
|
||||
"isnan");
|
||||
}
|
||||
|
||||
TEST(CapacityModel, WithMinimumWeightRequiresNonVacuousMinimum) {
|
||||
EXPECT_DEATH(CapacityModel::WithMinimumWeight(
|
||||
std::numeric_limits<CapacityWeight>::min()),
|
||||
"min");
|
||||
}
|
||||
|
||||
TEST(CapacityModel, WithMaximumWeightRequiresNonVacuousMaximum) {
|
||||
EXPECT_DEATH(CapacityModel::WithMaximumWeight(
|
||||
std::numeric_limits<CapacityWeight>::max()),
|
||||
"min");
|
||||
}
|
||||
|
||||
TEST(CapacityModel, WithMinimumWeightRejectsNaN) {
|
||||
EXPECT_DEATH(CapacityModel::WithMinimumWeight(
|
||||
std::numeric_limits<double>::quiet_NaN()),
|
||||
"isnan");
|
||||
}
|
||||
|
||||
TEST(CapacityModel, WithMaximumWeightRejectsNaN) {
|
||||
EXPECT_DEATH(CapacityModel::WithMaximumWeight(
|
||||
std::numeric_limits<double>::quiet_NaN()),
|
||||
"isnan");
|
||||
}
|
||||
|
||||
TEST(CapacityModel, SetMinimumCapacityRejectsNaN) {
|
||||
CapacityModel m(0.0, 1.0);
|
||||
EXPECT_DEATH(m.SetMinimumCapacity(std::numeric_limits<double>::quiet_NaN()),
|
||||
"isnan");
|
||||
}
|
||||
|
||||
TEST(CapacityModel, SetMinimumCapacityRejectsPlusInfinity) {
|
||||
CapacityModel m(0.0, 1.0);
|
||||
EXPECT_DEATH(m.SetMinimumCapacity(std::numeric_limits<CapacityWeight>::max()),
|
||||
"max");
|
||||
}
|
||||
|
||||
TEST(CapacityModel, SetMaximumCapacityRejectsNaN) {
|
||||
CapacityModel m(0.0, 1.0);
|
||||
EXPECT_DEATH(m.SetMaximumCapacity(std::numeric_limits<double>::quiet_NaN()),
|
||||
"isnan");
|
||||
}
|
||||
|
||||
TEST(CapacityModel, SetMaximumCapacityRejectsMinusInfinity) {
|
||||
CapacityModel m(0.0, 1.0);
|
||||
EXPECT_DEATH(m.SetMaximumCapacity(std::numeric_limits<CapacityWeight>::min()),
|
||||
"min");
|
||||
}
|
||||
|
||||
TEST(CapacityModel, AddTermRejectsNaN) {
|
||||
CapacityModel m(0.0, 1.0);
|
||||
EXPECT_DEATH(m.AddTerm(SubsetIndex(0), ElementIndex(0),
|
||||
std::numeric_limits<double>::quiet_NaN()),
|
||||
"isfinite");
|
||||
}
|
||||
|
||||
TEST(CapacityModel, AddTermRejectsPlusInf) {
|
||||
CapacityModel m(0.0, 1.0);
|
||||
EXPECT_DEATH(m.AddTerm(SubsetIndex(0), ElementIndex(0),
|
||||
std::numeric_limits<double>::infinity()),
|
||||
"isfinite");
|
||||
}
|
||||
|
||||
TEST(CapacityModel, AddTermRejectsMinusInf) {
|
||||
CapacityModel m(0.0, 1.0);
|
||||
EXPECT_DEATH(m.AddTerm(SubsetIndex(0), ElementIndex(0),
|
||||
-std::numeric_limits<double>::infinity()),
|
||||
"isfinite");
|
||||
}
|
||||
|
||||
TEST(CapacityModel, ComputeFeasibilityWithNoTerms) {
|
||||
CapacityModel m(0.0, 1.0);
|
||||
EXPECT_TRUE(m.ComputeFeasibility());
|
||||
|
||||
m.SetMinimumCapacity(-1.0);
|
||||
EXPECT_TRUE(m.ComputeFeasibility());
|
||||
|
||||
m.SetMaximumCapacity(0.0);
|
||||
EXPECT_TRUE(m.ComputeFeasibility());
|
||||
|
||||
m.SetMinimumCapacity(-2.0);
|
||||
m.SetMaximumCapacity(-1.0);
|
||||
EXPECT_FALSE(m.ComputeFeasibility());
|
||||
}
|
||||
|
||||
TEST(CapacityModel, ComputeFeasibilityWithOnlyPositiveWeights) {
|
||||
CapacityModel m(0.0, 1.0);
|
||||
m.AddTerm(SubsetIndex(0), ElementIndex(0), 1.0);
|
||||
m.AddTerm(SubsetIndex(0), ElementIndex(1), 2.0);
|
||||
m.AddTerm(SubsetIndex(0), ElementIndex(2), 3.0);
|
||||
// Activation bounds: [0.0, 6.0].
|
||||
EXPECT_TRUE(m.ComputeFeasibility());
|
||||
|
||||
m.SetMinimumCapacity(-1.0);
|
||||
EXPECT_TRUE(m.ComputeFeasibility());
|
||||
|
||||
m.SetMaximumCapacity(-1.0);
|
||||
EXPECT_FALSE(m.ComputeFeasibility());
|
||||
|
||||
m.SetMaximumCapacity(7.0);
|
||||
EXPECT_TRUE(m.ComputeFeasibility());
|
||||
|
||||
m.SetMinimumCapacity(7.0);
|
||||
EXPECT_FALSE(m.ComputeFeasibility());
|
||||
}
|
||||
|
||||
TEST(CapacityModel, ComputeFeasibilityWithOnlyNegativeWeights) {
|
||||
CapacityModel m(0.0, 1.0);
|
||||
m.AddTerm(SubsetIndex(0), ElementIndex(0), -1.0);
|
||||
m.AddTerm(SubsetIndex(0), ElementIndex(1), -2.0);
|
||||
m.AddTerm(SubsetIndex(0), ElementIndex(2), -3.0);
|
||||
// Activation bounds: [-6.0, 0.0].
|
||||
EXPECT_TRUE(m.ComputeFeasibility());
|
||||
|
||||
m.SetMaximumCapacity(1.0);
|
||||
EXPECT_TRUE(m.ComputeFeasibility());
|
||||
|
||||
m.SetMinimumCapacity(1.0);
|
||||
EXPECT_FALSE(m.ComputeFeasibility());
|
||||
|
||||
m.SetMinimumCapacity(-7.0);
|
||||
EXPECT_TRUE(m.ComputeFeasibility());
|
||||
|
||||
m.SetMaximumCapacity(-7.0);
|
||||
EXPECT_FALSE(m.ComputeFeasibility());
|
||||
}
|
||||
|
||||
TEST(CapacityModel, ComputeFeasibilityWithOnlyMixedWeights) {
|
||||
CapacityModel m(0.0, 1.0);
|
||||
m.AddTerm(SubsetIndex(0), ElementIndex(0), -1.0);
|
||||
m.AddTerm(SubsetIndex(0), ElementIndex(1), 2.0);
|
||||
m.AddTerm(SubsetIndex(0), ElementIndex(2), -3.0);
|
||||
// Activation bounds: [-4.0, 2.0].
|
||||
EXPECT_TRUE(m.ComputeFeasibility());
|
||||
|
||||
m.SetMaximumCapacity(3.0);
|
||||
EXPECT_TRUE(m.ComputeFeasibility());
|
||||
|
||||
m.SetMinimumCapacity(3.0);
|
||||
EXPECT_FALSE(m.ComputeFeasibility());
|
||||
|
||||
m.SetMinimumCapacity(-5.0);
|
||||
EXPECT_TRUE(m.ComputeFeasibility());
|
||||
|
||||
m.SetMaximumCapacity(-5.0);
|
||||
EXPECT_FALSE(m.ComputeFeasibility());
|
||||
}
|
||||
|
||||
// TEST(CapacityModel, ImportModelFromProto) {
|
||||
// CapacityModel m(0.0, 1.0);
|
||||
// EXPECT_THAT(m.ExportModelAsProto(), EqualsProto(R"pb(min_capacity: 0.0
|
||||
// max_capacity: 1.0)pb"));
|
||||
|
||||
// m.AddTerm(SubsetIndex(0), ElementIndex(0), 1.0);
|
||||
// EXPECT_THAT(m.ExportModelAsProto(),
|
||||
// EqualsProto(R"pb(min_capacity: 0.0
|
||||
// max_capacity: 1.0
|
||||
// capacity_term {
|
||||
// subset: 0
|
||||
// element_weights { element: 0 weight: 1.0 }
|
||||
// })pb"));
|
||||
|
||||
// m.AddTerm(SubsetIndex(0), ElementIndex(1), 1.0);
|
||||
// EXPECT_THAT(m.ExportModelAsProto(),
|
||||
// EqualsProto(
|
||||
// R"pb(min_capacity: 0.0
|
||||
// max_capacity: 1.0
|
||||
// capacity_term {
|
||||
// subset: 0
|
||||
// element_weights { element: 0 weight: 1.0 }
|
||||
// element_weights { element: 1 weight: 1.0 }
|
||||
// })pb"));
|
||||
// }
|
||||
|
||||
// TEST(CapacityModel, ImportModelFromProtoHasCanonicalOrder) {
|
||||
// // Reverse order for the terms compared to
|
||||
// // CapacityModel_ImportModelFromProto, same order in the proto.
|
||||
// CapacityModel m(0.0, 1.0);
|
||||
// m.AddTerm(SubsetIndex(0), ElementIndex(1), 1.0);
|
||||
// m.AddTerm(SubsetIndex(0), ElementIndex(0), 1.0);
|
||||
// EXPECT_THAT(m.ExportModelAsProto(),
|
||||
// EqualsProto(
|
||||
// R"pb(min_capacity: 0.0
|
||||
// max_capacity: 1.0
|
||||
// capacity_term {
|
||||
// subset: 0
|
||||
// element_weights { element: 0 weight: 1.0 }
|
||||
// element_weights { element: 1 weight: 1.0 }
|
||||
// })pb"));
|
||||
// }
|
||||
|
||||
} // namespace
|
||||
} // namespace operations_research
|
||||
44
ortools/set_cover/python/BUILD.bazel
Normal file
44
ortools/set_cover/python/BUILD.bazel
Normal file
@@ -0,0 +1,44 @@
|
||||
# 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.
|
||||
|
||||
# Python wrapper for ..
|
||||
load("@pip_deps//:requirements.bzl", "requirement")
|
||||
load("@pybind11_bazel//:build_defs.bzl", "pybind_extension")
|
||||
load("@rules_python//python:defs.bzl", "py_test")
|
||||
|
||||
# set_cover
|
||||
pybind_extension(
|
||||
name = "set_cover",
|
||||
srcs = ["set_cover.cc"],
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//ortools/set_cover:set_cover_cc_proto",
|
||||
"//ortools/set_cover:set_cover_heuristics",
|
||||
"//ortools/set_cover:set_cover_invariant",
|
||||
"//ortools/set_cover:set_cover_model",
|
||||
"//ortools/set_cover:set_cover_reader",
|
||||
"@com_google_absl//absl/strings",
|
||||
"@pybind11_protobuf//pybind11_protobuf:native_proto_caster",
|
||||
],
|
||||
)
|
||||
|
||||
py_test(
|
||||
name = "set_cover_test",
|
||||
srcs = ["set_cover_test.py"],
|
||||
python_version = "PY3",
|
||||
deps = [
|
||||
":set_cover",
|
||||
"//ortools/set_cover:set_cover_py_pb2",
|
||||
requirement("absl-py"),
|
||||
],
|
||||
)
|
||||
42
ortools/set_cover/python/CMakeLists.txt
Normal file
42
ortools/set_cover/python/CMakeLists.txt
Normal file
@@ -0,0 +1,42 @@
|
||||
# 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.
|
||||
|
||||
# set_cover
|
||||
pybind11_add_module(set_cover_pybind11 MODULE set_cover.cc)
|
||||
set_target_properties(set_cover_pybind11 PROPERTIES
|
||||
LIBRARY_OUTPUT_NAME "set_cover")
|
||||
|
||||
# note: macOS is APPLE and also UNIX !
|
||||
if(APPLE)
|
||||
set_target_properties(set_cover_pybind11 PROPERTIES
|
||||
SUFFIX ".so"
|
||||
INSTALL_RPATH "@loader_path;@loader_path/../../../${PYTHON_PROJECT}/.libs"
|
||||
)
|
||||
elseif(UNIX)
|
||||
set_target_properties(set_cover_pybind11 PROPERTIES
|
||||
INSTALL_RPATH "$ORIGIN:$ORIGIN/../../../${PYTHON_PROJECT}/.libs"
|
||||
)
|
||||
endif()
|
||||
|
||||
target_link_libraries(set_cover_pybind11 PRIVATE
|
||||
${PROJECT_NAMESPACE}::ortools
|
||||
pybind11_native_proto_caster
|
||||
)
|
||||
add_library(${PROJECT_NAMESPACE}::set_cover_pybind11 ALIAS set_cover_pybind11)
|
||||
|
||||
if(BUILD_TESTING)
|
||||
file(GLOB PYTHON_SRCS "*_test.py")
|
||||
foreach(FILE_NAME IN LISTS PYTHON_SRCS)
|
||||
add_python_test(FILE_NAME ${FILE_NAME})
|
||||
endforeach()
|
||||
endif()
|
||||
@@ -20,10 +20,10 @@
|
||||
#include <vector>
|
||||
|
||||
#include "absl/types/span.h"
|
||||
#include "ortools/algorithms/set_cover_heuristics.h"
|
||||
#include "ortools/algorithms/set_cover_invariant.h"
|
||||
#include "ortools/algorithms/set_cover_model.h"
|
||||
#include "ortools/algorithms/set_cover_reader.h"
|
||||
#include "ortools/set_cover/set_cover_heuristics.h"
|
||||
#include "ortools/set_cover/set_cover_invariant.h"
|
||||
#include "ortools/set_cover/set_cover_model.h"
|
||||
#include "ortools/set_cover/set_cover_reader.h"
|
||||
#include "pybind11/numpy.h"
|
||||
#include "pybind11/pybind11.h"
|
||||
#include "pybind11/pytypes.h"
|
||||
@@ -300,7 +300,7 @@ PYBIND11_MODULE(set_cover, m) {
|
||||
.def(
|
||||
"compute_coverage_in_focus",
|
||||
[](SetCoverInvariant& invariant,
|
||||
const std::vector<BaseInt>& focus) -> std::vector<BaseInt> {
|
||||
absl::Span<const BaseInt> focus) -> std::vector<BaseInt> {
|
||||
return invariant
|
||||
.ComputeCoverageInFocus(VectorIntToVectorSubsetIndex(focus))
|
||||
.get();
|
||||
@@ -321,7 +321,7 @@ PYBIND11_MODULE(set_cover, m) {
|
||||
.def("compress_trace", &SetCoverInvariant::CompressTrace)
|
||||
.def("load_solution",
|
||||
[](SetCoverInvariant& invariant,
|
||||
const std::vector<bool>& solution) -> void {
|
||||
absl::Span<const bool> solution) -> void {
|
||||
SubsetBoolVector sol(solution.begin(), solution.end());
|
||||
return invariant.LoadSolution(sol);
|
||||
})
|
||||
@@ -453,7 +453,7 @@ PYBIND11_MODULE(set_cover, m) {
|
||||
return heuristic.NextSolution(num_iterations);
|
||||
})
|
||||
.def("next_solution",
|
||||
[](SteepestSearch& heuristic, const std::vector<BaseInt>& focus,
|
||||
[](SteepestSearch& heuristic, absl::Span<const BaseInt> focus,
|
||||
int num_iterations) -> bool {
|
||||
return heuristic.NextSolution(VectorIntToVectorSubsetIndex(focus),
|
||||
num_iterations);
|
||||
@@ -512,7 +512,7 @@ PYBIND11_MODULE(set_cover, m) {
|
||||
return heuristic.NextSolution(num_iterations);
|
||||
})
|
||||
.def("next_solution",
|
||||
[](GuidedTabuSearch& heuristic, const std::vector<BaseInt>& focus,
|
||||
[](GuidedTabuSearch& heuristic, absl::Span<const BaseInt> focus,
|
||||
int num_iterations) -> bool {
|
||||
return heuristic.NextSolution(VectorIntToVectorSubsetIndex(focus),
|
||||
num_iterations);
|
||||
@@ -15,7 +15,7 @@
|
||||
from absl import app
|
||||
from absl.testing import absltest
|
||||
|
||||
from ortools.algorithms.python import set_cover
|
||||
from ortools.set_cover.python import set_cover
|
||||
|
||||
|
||||
def create_initial_cover_model():
|
||||
44
ortools/set_cover/samples/CMakeLists.txt
Normal file
44
ortools/set_cover/samples/CMakeLists.txt
Normal file
@@ -0,0 +1,44 @@
|
||||
# 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.
|
||||
|
||||
if(NOT BUILD_SAMPLES)
|
||||
return()
|
||||
endif()
|
||||
|
||||
if(BUILD_CXX_SAMPLES)
|
||||
file(GLOB CXX_SRCS "*.cc")
|
||||
foreach(SAMPLE IN LISTS CXX_SRCS)
|
||||
add_cxx_sample(FILE_NAME ${SAMPLE})
|
||||
endforeach()
|
||||
endif()
|
||||
|
||||
if(BUILD_PYTHON_SAMPLES)
|
||||
file(GLOB PYTHON_SRCS "*.py")
|
||||
foreach(SAMPLE IN LISTS PYTHON_SRCS)
|
||||
add_python_sample(FILE_NAME ${SAMPLE})
|
||||
endforeach()
|
||||
endif()
|
||||
|
||||
if(BUILD_JAVA_SAMPLES)
|
||||
file(GLOB JAVA_SRCS "*.java")
|
||||
foreach(SAMPLE IN LISTS JAVA_SRCS)
|
||||
add_java_sample(FILE_NAME ${SAMPLE})
|
||||
endforeach()
|
||||
endif()
|
||||
|
||||
if(BUILD_DOTNET_SAMPLES)
|
||||
file(GLOB DOTNET_SRCS "*.cs")
|
||||
foreach(SAMPLE IN LISTS DOTNET_SRCS)
|
||||
add_dotnet_sample(FILE_NAME ${SAMPLE})
|
||||
endforeach()
|
||||
endif()
|
||||
26
ortools/set_cover/samples/code_samples_cc_test.sh
Executable file
26
ortools/set_cover/samples/code_samples_cc_test.sh
Executable file
@@ -0,0 +1,26 @@
|
||||
#!/usr/bin/env bash
|
||||
# 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.
|
||||
|
||||
|
||||
source gbash.sh || exit
|
||||
source module gbash_unit.sh
|
||||
|
||||
DEFINE_string sample "" "sample code."
|
||||
|
||||
function test::operations_research_examples::code_samples_set_cover() {
|
||||
declare -r DIR="${TEST_SRCDIR}/ortools/set_cover/samples"
|
||||
EXPECT_SUCCEED "${DIR}/${FLAGS_sample}_cc"
|
||||
}
|
||||
|
||||
gbash::unit::main "$@"
|
||||
26
ortools/set_cover/samples/code_samples_py_test.sh
Executable file
26
ortools/set_cover/samples/code_samples_py_test.sh
Executable file
@@ -0,0 +1,26 @@
|
||||
#!/usr/bin/env bash
|
||||
# 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.
|
||||
|
||||
|
||||
source gbash.sh || exit
|
||||
source module gbash_unit.sh
|
||||
|
||||
DEFINE_string sample "" "sample code."
|
||||
|
||||
function test::operations_research_examples::code_samples_set_cover_py() {
|
||||
declare -r DIR="${TEST_SRCDIR}/ortools/set_cover/samples"
|
||||
EXPECT_SUCCEED "${DIR}/${FLAGS_sample}_py3"
|
||||
}
|
||||
|
||||
gbash::unit::main "$@"
|
||||
@@ -15,10 +15,10 @@
|
||||
// [START import]
|
||||
#include <cstdlib>
|
||||
|
||||
#include "ortools/algorithms/set_cover_heuristics.h"
|
||||
#include "ortools/algorithms/set_cover_invariant.h"
|
||||
#include "ortools/algorithms/set_cover_model.h"
|
||||
#include "ortools/base/logging.h"
|
||||
#include "ortools/set_cover/set_cover_heuristics.h"
|
||||
#include "ortools/set_cover/set_cover_invariant.h"
|
||||
#include "ortools/set_cover/set_cover_model.h"
|
||||
// [END import]
|
||||
|
||||
namespace operations_research {
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
# [START program]
|
||||
# [START import]
|
||||
from ortools.algorithms.python import set_cover
|
||||
from ortools.set_cover.python import set_cover
|
||||
# [END import]
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "ortools/algorithms/set_cover_heuristics.h"
|
||||
#include "ortools/set_cover/set_cover_heuristics.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <climits>
|
||||
@@ -28,9 +28,9 @@
|
||||
#include "absl/random/random.h"
|
||||
#include "absl/types/span.h"
|
||||
#include "ortools/algorithms/adjustable_k_ary_heap.h"
|
||||
#include "ortools/algorithms/set_cover_invariant.h"
|
||||
#include "ortools/algorithms/set_cover_model.h"
|
||||
#include "ortools/base/logging.h"
|
||||
#include "ortools/set_cover/set_cover_invariant.h"
|
||||
#include "ortools/set_cover/set_cover_model.h"
|
||||
|
||||
namespace operations_research {
|
||||
|
||||
@@ -11,15 +11,15 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef OR_TOOLS_ALGORITHMS_SET_COVER_HEURISTICS_H_
|
||||
#define OR_TOOLS_ALGORITHMS_SET_COVER_HEURISTICS_H_
|
||||
#ifndef OR_TOOLS_SET_COVER_SET_COVER_HEURISTICS_H_
|
||||
#define OR_TOOLS_SET_COVER_SET_COVER_HEURISTICS_H_
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "absl/types/span.h"
|
||||
#include "ortools/algorithms/adjustable_k_ary_heap.h"
|
||||
#include "ortools/algorithms/set_cover_invariant.h"
|
||||
#include "ortools/algorithms/set_cover_model.h"
|
||||
#include "ortools/set_cover/set_cover_invariant.h"
|
||||
#include "ortools/set_cover/set_cover_model.h"
|
||||
|
||||
namespace operations_research {
|
||||
|
||||
@@ -499,4 +499,4 @@ std::vector<SubsetIndex> ClearMostCoveredElements(
|
||||
SetCoverInvariant* inv);
|
||||
} // namespace operations_research
|
||||
|
||||
#endif // OR_TOOLS_ALGORITHMS_SET_COVER_HEURISTICS_H_
|
||||
#endif // OR_TOOLS_SET_COVER_SET_COVER_HEURISTICS_H_
|
||||
@@ -11,7 +11,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "ortools/algorithms/set_cover_invariant.h"
|
||||
#include "ortools/set_cover/set_cover_invariant.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <limits>
|
||||
@@ -20,9 +20,9 @@
|
||||
|
||||
#include "absl/log/check.h"
|
||||
#include "absl/types/span.h"
|
||||
#include "ortools/algorithms/set_cover_model.h"
|
||||
#include "ortools/base/logging.h"
|
||||
#include "ortools/base/mathutil.h"
|
||||
#include "ortools/set_cover/set_cover_model.h"
|
||||
|
||||
namespace operations_research {
|
||||
|
||||
@@ -11,16 +11,16 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef OR_TOOLS_ALGORITHMS_SET_COVER_INVARIANT_H_
|
||||
#define OR_TOOLS_ALGORITHMS_SET_COVER_INVARIANT_H_
|
||||
#ifndef OR_TOOLS_SET_COVER_SET_COVER_INVARIANT_H_
|
||||
#define OR_TOOLS_SET_COVER_SET_COVER_INVARIANT_H_
|
||||
|
||||
#include <tuple>
|
||||
#include <vector>
|
||||
|
||||
#include "absl/log/check.h"
|
||||
#include "absl/types/span.h"
|
||||
#include "ortools/algorithms/set_cover.pb.h"
|
||||
#include "ortools/algorithms/set_cover_model.h"
|
||||
#include "ortools/set_cover/set_cover.pb.h"
|
||||
#include "ortools/set_cover/set_cover_model.h"
|
||||
|
||||
namespace operations_research {
|
||||
|
||||
@@ -288,4 +288,4 @@ class SetCoverInvariant {
|
||||
};
|
||||
|
||||
} // namespace operations_research
|
||||
#endif // OR_TOOLS_ALGORITHMS_SET_COVER_INVARIANT_H_
|
||||
#endif // OR_TOOLS_SET_COVER_SET_COVER_INVARIANT_H_
|
||||
@@ -11,7 +11,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "ortools/algorithms/set_cover_lagrangian.h"
|
||||
#include "ortools/set_cover/set_cover_lagrangian.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdlib>
|
||||
@@ -22,9 +22,9 @@
|
||||
#include "absl/log/check.h"
|
||||
#include "absl/synchronization/blocking_counter.h"
|
||||
#include "ortools/algorithms/adjustable_k_ary_heap.h"
|
||||
#include "ortools/algorithms/set_cover_invariant.h"
|
||||
#include "ortools/algorithms/set_cover_model.h"
|
||||
#include "ortools/base/threadpool.h"
|
||||
#include "ortools/set_cover/set_cover_invariant.h"
|
||||
#include "ortools/set_cover/set_cover_model.h"
|
||||
|
||||
namespace operations_research {
|
||||
|
||||
@@ -99,7 +99,8 @@ void FillReducedCostsSlice(SubsetIndex slice_start, SubsetIndex slice_end,
|
||||
}
|
||||
|
||||
BaseInt BlockSize(BaseInt size, int num_threads) {
|
||||
return 1 + (size - 1) / num_threads;
|
||||
// Traditional formula to compute std::ceil(size / num_threads).
|
||||
return (size + num_threads - 1) / num_threads;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
@@ -11,17 +11,17 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef OR_TOOLS_ALGORITHMS_SET_COVER_LAGRANGIAN_H_
|
||||
#define OR_TOOLS_ALGORITHMS_SET_COVER_LAGRANGIAN_H_
|
||||
#ifndef OR_TOOLS_SET_COVER_SET_COVER_LAGRANGIAN_H_
|
||||
#define OR_TOOLS_SET_COVER_SET_COVER_LAGRANGIAN_H_
|
||||
|
||||
#include <memory>
|
||||
#include <new>
|
||||
#include <tuple>
|
||||
#include <vector>
|
||||
|
||||
#include "ortools/algorithms/set_cover_invariant.h"
|
||||
#include "ortools/algorithms/set_cover_model.h"
|
||||
#include "ortools/base/threadpool.h"
|
||||
#include "ortools/set_cover/set_cover_invariant.h"
|
||||
#include "ortools/set_cover/set_cover_model.h"
|
||||
|
||||
namespace operations_research {
|
||||
|
||||
@@ -160,4 +160,4 @@ class SetCoverLagrangian {
|
||||
|
||||
} // namespace operations_research
|
||||
|
||||
#endif // OR_TOOLS_ALGORITHMS_SET_COVER_LAGRANGIAN_H_
|
||||
#endif // OR_TOOLS_SET_COVER_SET_COVER_LAGRANGIAN_H_
|
||||
@@ -11,18 +11,18 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "ortools/algorithms/set_cover_mip.h"
|
||||
#include "ortools/set_cover/set_cover_mip.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
|
||||
#include "absl/log/check.h"
|
||||
#include "absl/types/span.h"
|
||||
#include "ortools/algorithms/set_cover_invariant.h"
|
||||
#include "ortools/algorithms/set_cover_model.h"
|
||||
#include "ortools/base/logging.h"
|
||||
#include "ortools/linear_solver/linear_solver.h"
|
||||
#include "ortools/lp_data/lp_types.h"
|
||||
#include "ortools/set_cover/set_cover_invariant.h"
|
||||
#include "ortools/set_cover/set_cover_model.h"
|
||||
|
||||
namespace operations_research {
|
||||
|
||||
@@ -11,12 +11,12 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef OR_TOOLS_ALGORITHMS_SET_COVER_MIP_H_
|
||||
#define OR_TOOLS_ALGORITHMS_SET_COVER_MIP_H_
|
||||
#ifndef OR_TOOLS_SET_COVER_SET_COVER_MIP_H_
|
||||
#define OR_TOOLS_SET_COVER_SET_COVER_MIP_H_
|
||||
|
||||
#include "absl/types/span.h"
|
||||
#include "ortools/algorithms/set_cover_invariant.h"
|
||||
#include "ortools/algorithms/set_cover_model.h"
|
||||
#include "ortools/set_cover/set_cover_invariant.h"
|
||||
#include "ortools/set_cover/set_cover_model.h"
|
||||
|
||||
namespace operations_research {
|
||||
enum class SetCoverMipSolver : int {
|
||||
@@ -67,4 +67,4 @@ class SetCoverMip {
|
||||
};
|
||||
} // namespace operations_research
|
||||
|
||||
#endif // OR_TOOLS_ALGORITHMS_SET_COVER_MIP_H_
|
||||
#endif // OR_TOOLS_SET_COVER_SET_COVER_MIP_H_
|
||||
@@ -11,7 +11,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "ortools/algorithms/set_cover_model.h"
|
||||
#include "ortools/set_cover/set_cover_model.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
@@ -31,8 +31,8 @@
|
||||
#include "absl/strings/str_format.h"
|
||||
#include "absl/types/span.h"
|
||||
#include "ortools/algorithms/radix_sort.h"
|
||||
#include "ortools/algorithms/set_cover.pb.h"
|
||||
#include "ortools/base/logging.h"
|
||||
#include "ortools/set_cover/set_cover.pb.h"
|
||||
|
||||
namespace operations_research {
|
||||
|
||||
@@ -11,8 +11,8 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef OR_TOOLS_ALGORITHMS_SET_COVER_MODEL_H_
|
||||
#define OR_TOOLS_ALGORITHMS_SET_COVER_MODEL_H_
|
||||
#ifndef OR_TOOLS_SET_COVER_SET_COVER_MODEL_H_
|
||||
#define OR_TOOLS_SET_COVER_SET_COVER_MODEL_H_
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
@@ -20,9 +20,9 @@
|
||||
|
||||
#include "absl/log/check.h"
|
||||
#include "absl/strings/str_cat.h"
|
||||
#include "ortools/algorithms/set_cover.pb.h"
|
||||
#include "ortools/base/strong_int.h"
|
||||
#include "ortools/base/strong_vector.h"
|
||||
#include "ortools/set_cover/set_cover.pb.h"
|
||||
|
||||
// Representation class for the weighted set-covering problem.
|
||||
//
|
||||
@@ -398,4 +398,4 @@ class IntersectingSubsetsIterator {
|
||||
|
||||
} // namespace operations_research
|
||||
|
||||
#endif // OR_TOOLS_ALGORITHMS_SET_COVER_MODEL_H_
|
||||
#endif // OR_TOOLS_SET_COVER_SET_COVER_MODEL_H_
|
||||
@@ -11,10 +11,11 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "ortools/algorithms/set_cover_reader.h"
|
||||
#include "ortools/set_cover/set_cover_reader.h"
|
||||
|
||||
#include <sys/types.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
@@ -28,12 +29,13 @@
|
||||
#include "absl/strings/str_format.h"
|
||||
#include "absl/strings/str_split.h"
|
||||
#include "absl/strings/string_view.h"
|
||||
#include "ortools/algorithms/set_cover.pb.h"
|
||||
#include "ortools/algorithms/set_cover_model.h"
|
||||
#include "ortools/base/file.h"
|
||||
#include "ortools/base/filesystem.h"
|
||||
#include "ortools/base/helpers.h"
|
||||
#include "ortools/base/logging.h"
|
||||
#include "ortools/base/options.h"
|
||||
#include "ortools/set_cover/set_cover.pb.h"
|
||||
#include "ortools/set_cover/set_cover_model.h"
|
||||
#include "ortools/util/filelineiter.h"
|
||||
|
||||
namespace operations_research {
|
||||
@@ -105,30 +107,42 @@ int64_t SetCoverReader::ParseNextInteger() {
|
||||
return value;
|
||||
}
|
||||
|
||||
namespace {
|
||||
double Percent(SubsetIndex subset, BaseInt total) {
|
||||
return subset.value() == 0 ? 0 : 100.0 * subset.value() / total;
|
||||
}
|
||||
|
||||
double Percent(ElementIndex element, BaseInt total) {
|
||||
return element.value() == 0 ? 0 : 100.0 * element.value() / total;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
// This is a row-based format where the elements are 1-indexed.
|
||||
SetCoverModel ReadOrlibScp(absl::string_view filename) {
|
||||
CHECK_OK(file::Exists(filename, file::Defaults()));
|
||||
SetCoverModel model;
|
||||
File* file(file::OpenOrDie(filename, "r", file::Defaults()));
|
||||
SetCoverReader reader(file);
|
||||
const ElementIndex num_rows(reader.ParseNextInteger());
|
||||
const SubsetIndex num_cols(reader.ParseNextInteger());
|
||||
model.ReserveNumSubsets(num_cols.value());
|
||||
model.ReserveNumSubsets(num_cols);
|
||||
for (SubsetIndex subset : SubsetRange(num_cols)) {
|
||||
const double cost(reader.ParseNextDouble());
|
||||
model.SetSubsetCost(subset.value(), cost);
|
||||
model.SetSubsetCost(subset, cost);
|
||||
}
|
||||
for (ElementIndex element : ElementRange(num_rows)) {
|
||||
LOG_EVERY_N_SEC(INFO, 5)
|
||||
<< absl::StrFormat("Reading element %d (%.1f%%)", element.value(),
|
||||
100.0 * element.value() / model.num_elements());
|
||||
Percent(element, model.num_elements()));
|
||||
const RowEntryIndex row_size(reader.ParseNextInteger());
|
||||
for (RowEntryIndex entry(0); entry < row_size; ++entry) {
|
||||
// Correct the 1-indexing.
|
||||
const int subset(reader.ParseNextInteger() - 1);
|
||||
model.AddElementToSubset(element.value(), subset);
|
||||
const SubsetIndex subset(reader.ParseNextInteger() - 1);
|
||||
model.AddElementToSubset(element, subset);
|
||||
}
|
||||
}
|
||||
LOG(INFO) << "Finished reading the model.";
|
||||
LOG(INFO) << "Read " << model.num_subsets() << " subsets, "
|
||||
<< model.num_elements() << " elements";
|
||||
file->Close(file::Defaults()).IgnoreError();
|
||||
model.CreateSparseRowView();
|
||||
return model;
|
||||
@@ -136,59 +150,96 @@ SetCoverModel ReadOrlibScp(absl::string_view filename) {
|
||||
|
||||
// This is a column-based format where the elements are 1-indexed.
|
||||
SetCoverModel ReadOrlibRail(absl::string_view filename) {
|
||||
CHECK_OK(file::Exists(filename, file::Defaults()));
|
||||
SetCoverModel model;
|
||||
File* file(file::OpenOrDie(filename, "r", file::Defaults()));
|
||||
SetCoverReader reader(file);
|
||||
const ElementIndex num_rows(reader.ParseNextInteger());
|
||||
const BaseInt num_cols(reader.ParseNextInteger());
|
||||
const SubsetIndex num_cols(reader.ParseNextInteger());
|
||||
model.ReserveNumSubsets(num_cols);
|
||||
for (BaseInt subset(0); subset < num_cols; ++subset) {
|
||||
for (SubsetIndex subset : SubsetRange(num_cols)) {
|
||||
LOG_EVERY_N_SEC(INFO, 5)
|
||||
<< absl::StrFormat("Reading subset %d (%.1f%%)", subset,
|
||||
100.0 * subset / model.num_subsets());
|
||||
<< absl::StrFormat("Reading subset %d (%.1f%%)", subset.value(),
|
||||
Percent(subset, model.num_subsets()));
|
||||
const double cost(reader.ParseNextDouble());
|
||||
model.SetSubsetCost(subset, cost);
|
||||
const ColumnEntryIndex column_size(reader.ParseNextInteger());
|
||||
model.ReserveNumElementsInSubset(column_size.value(), subset);
|
||||
model.ReserveNumElementsInSubset(column_size.value(), subset.value());
|
||||
for (const ColumnEntryIndex _ : ColumnEntryRange(column_size)) {
|
||||
// Correct the 1-indexing.
|
||||
const ElementIndex element(reader.ParseNextInteger() - 1);
|
||||
model.AddElementToSubset(element.value(), subset);
|
||||
model.AddElementToSubset(element, subset);
|
||||
}
|
||||
}
|
||||
LOG(INFO) << "Finished reading the model.";
|
||||
LOG(INFO) << "Read " << model.num_subsets() << " subsets, "
|
||||
<< model.num_elements() << " elements";
|
||||
file->Close(file::Defaults()).IgnoreError();
|
||||
model.CreateSparseRowView();
|
||||
return model;
|
||||
}
|
||||
|
||||
SetCoverModel ReadFimiDat(absl::string_view filename) {
|
||||
CHECK_OK(file::Exists(filename, file::Defaults()));
|
||||
SetCoverModel model;
|
||||
BaseInt subset(0);
|
||||
SubsetIndex subset(0);
|
||||
// Read the file once to discover the smallest element index.
|
||||
BaseInt smallest_element = std::numeric_limits<BaseInt>::max();
|
||||
BaseInt largest_element = 0;
|
||||
for (const std::string& line : FileLines(filename)) {
|
||||
std::vector<std::string> elements = absl::StrSplit(line, ' ');
|
||||
if (elements.back().empty() || elements.back()[0] == '\0') {
|
||||
elements.pop_back();
|
||||
}
|
||||
for (const std::string& number_str : elements) {
|
||||
BaseInt element;
|
||||
CHECK(absl::SimpleAtoi(number_str, &element));
|
||||
smallest_element = std::min(smallest_element, element);
|
||||
largest_element = std::max(largest_element, element);
|
||||
}
|
||||
}
|
||||
DLOG(INFO) << "Smallest element: " << smallest_element
|
||||
<< ", Largest element: " << largest_element;
|
||||
ElementBoolVector element_seen(largest_element + 1, false);
|
||||
for (const std::string& line : FileLines(filename)) {
|
||||
LOG_EVERY_N_SEC(INFO, 5)
|
||||
<< absl::StrFormat("Reading subset %d (%.1f%%)", subset,
|
||||
100.0 * subset / model.num_subsets());
|
||||
<< absl::StrFormat("Reading subset %d", subset.value());
|
||||
std::vector<std::string> elements = absl::StrSplit(line, ' ');
|
||||
if (elements.back().empty() || elements.back()[0] == '\0') {
|
||||
elements.pop_back();
|
||||
}
|
||||
model.AddEmptySubset(1);
|
||||
for (const std::string& number : elements) {
|
||||
BaseInt element;
|
||||
CHECK(absl::SimpleAtoi(number, &element));
|
||||
CHECK_GT(element, 0);
|
||||
// Correct the 1-indexing.
|
||||
model.AddElementToLastSubset(ElementIndex(element - 1));
|
||||
// As there can be repetitions in the data, we need to keep track of the
|
||||
// elements already added to the subset.
|
||||
std::vector<ElementIndex> elements_list;
|
||||
for (const std::string& number_str : elements) {
|
||||
BaseInt raw_element;
|
||||
CHECK(absl::SimpleAtoi(number_str, &raw_element));
|
||||
// Re-index the elements starting from 0.
|
||||
ElementIndex element(raw_element - smallest_element);
|
||||
if (element_seen[element]) {
|
||||
DLOG(INFO) << "Element " << element << " already in subset "
|
||||
<< subset.value();
|
||||
continue;
|
||||
}
|
||||
element_seen[element] = true;
|
||||
elements_list.push_back(element);
|
||||
CHECK_GE(element.value(), 0);
|
||||
model.AddElementToLastSubset(element);
|
||||
}
|
||||
// Clean up the list of elements.
|
||||
for (const ElementIndex element : elements_list) {
|
||||
element_seen[element] = false;
|
||||
}
|
||||
++subset;
|
||||
}
|
||||
LOG(INFO) << "Finished reading the model.";
|
||||
LOG(INFO) << "Read " << model.num_subsets() << " subsets, "
|
||||
<< model.num_elements() << " elements";
|
||||
model.CreateSparseRowView();
|
||||
return model;
|
||||
}
|
||||
|
||||
SetCoverModel ReadSetCoverProto(absl::string_view filename, bool binary) {
|
||||
CHECK_OK(file::Exists(filename, file::Defaults()));
|
||||
SetCoverModel model;
|
||||
SetCoverProto message;
|
||||
if (binary) {
|
||||
@@ -255,7 +306,7 @@ void WriteOrlibScp(const SetCoverModel& model, absl::string_view filename) {
|
||||
for (const ElementIndex element : model.ElementRange()) {
|
||||
LOG_EVERY_N_SEC(INFO, 5)
|
||||
<< absl::StrFormat("Writing element %d (%.1f%%)", element.value(),
|
||||
100.0 * element.value() / model.num_elements());
|
||||
Percent(element, model.num_elements()));
|
||||
formatter.Append(absl::StrCat(model.rows()[element].size(), "\n"));
|
||||
for (const SubsetIndex subset : model.rows()[element]) {
|
||||
formatter.Append(subset.value() + 1);
|
||||
@@ -276,7 +327,7 @@ void WriteOrlibRail(const SetCoverModel& model, absl::string_view filename) {
|
||||
for (const SubsetIndex subset : model.SubsetRange()) {
|
||||
LOG_EVERY_N_SEC(INFO, 5)
|
||||
<< absl::StrFormat("Writing subset %d (%.1f%%)", subset.value(),
|
||||
100.0 * subset.value() / model.num_subsets());
|
||||
Percent(subset, model.num_subsets()));
|
||||
formatter.Append(model.subset_costs()[subset]);
|
||||
formatter.Append(static_cast<BaseInt>(model.columns()[subset].size()));
|
||||
for (const ElementIndex element : model.columns()[subset]) {
|
||||
@@ -299,6 +350,7 @@ void WriteSetCoverProto(const SetCoverModel& model, absl::string_view filename,
|
||||
}
|
||||
|
||||
SubsetBoolVector ReadSetCoverSolutionText(absl::string_view filename) {
|
||||
CHECK_OK(file::Exists(filename, file::Defaults()));
|
||||
SubsetBoolVector solution;
|
||||
File* file(file::OpenOrDie(filename, "r", file::Defaults()));
|
||||
SetCoverReader reader(file);
|
||||
@@ -316,6 +368,7 @@ SubsetBoolVector ReadSetCoverSolutionText(absl::string_view filename) {
|
||||
|
||||
SubsetBoolVector ReadSetCoverSolutionProto(absl::string_view filename,
|
||||
bool binary) {
|
||||
CHECK_OK(file::Exists(filename, file::Defaults()));
|
||||
SubsetBoolVector solution;
|
||||
SetCoverSolutionResponse message;
|
||||
if (binary) {
|
||||
@@ -11,11 +11,11 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef OR_TOOLS_ALGORITHMS_SET_COVER_READER_H_
|
||||
#define OR_TOOLS_ALGORITHMS_SET_COVER_READER_H_
|
||||
#ifndef OR_TOOLS_SET_COVER_SET_COVER_READER_H_
|
||||
#define OR_TOOLS_SET_COVER_SET_COVER_READER_H_
|
||||
|
||||
#include "absl/strings/string_view.h"
|
||||
#include "ortools/algorithms/set_cover_model.h"
|
||||
#include "ortools/set_cover/set_cover_model.h"
|
||||
|
||||
namespace operations_research {
|
||||
|
||||
@@ -106,4 +106,4 @@ void WriteSetCoverSolutionProto(const SetCoverModel& model,
|
||||
|
||||
} // namespace operations_research
|
||||
|
||||
#endif // OR_TOOLS_ALGORITHMS_SET_COVER_READER_H_
|
||||
#endif // OR_TOOLS_SET_COVER_SET_COVER_READER_H_
|
||||
@@ -20,13 +20,13 @@
|
||||
#include "absl/strings/str_join.h"
|
||||
#include "absl/strings/string_view.h"
|
||||
#include "absl/time/time.h"
|
||||
#include "ortools/algorithms/set_cover_heuristics.h"
|
||||
#include "ortools/algorithms/set_cover_invariant.h"
|
||||
#include "ortools/algorithms/set_cover_model.h"
|
||||
#include "ortools/algorithms/set_cover_reader.h"
|
||||
#include "ortools/base/init_google.h"
|
||||
#include "ortools/base/logging.h"
|
||||
#include "ortools/base/timer.h"
|
||||
#include "ortools/set_cover/set_cover_heuristics.h"
|
||||
#include "ortools/set_cover/set_cover_invariant.h"
|
||||
#include "ortools/set_cover/set_cover_model.h"
|
||||
#include "ortools/set_cover/set_cover_reader.h"
|
||||
|
||||
ABSL_FLAG(std::string, input, "", "REQUIRED: Input file name.");
|
||||
ABSL_FLAG(std::string, input_fmt, "",
|
||||
@@ -19,14 +19,14 @@
|
||||
#include "absl/strings/str_cat.h"
|
||||
#include "benchmark/benchmark.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "ortools/algorithms/set_cover.pb.h"
|
||||
#include "ortools/algorithms/set_cover_heuristics.h"
|
||||
#include "ortools/algorithms/set_cover_invariant.h"
|
||||
#include "ortools/algorithms/set_cover_mip.h"
|
||||
#include "ortools/algorithms/set_cover_model.h"
|
||||
#include "ortools/base/gmock.h"
|
||||
#include "ortools/base/logging.h"
|
||||
#include "ortools/base/parse_text_proto.h"
|
||||
#include "ortools/set_cover/set_cover.pb.h"
|
||||
#include "ortools/set_cover/set_cover_heuristics.h"
|
||||
#include "ortools/set_cover/set_cover_invariant.h"
|
||||
#include "ortools/set_cover/set_cover_mip.h"
|
||||
#include "ortools/set_cover/set_cover_model.h"
|
||||
|
||||
namespace operations_research {
|
||||
namespace {
|
||||
Reference in New Issue
Block a user