move set_cover
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)
|
||||
@@ -524,6 +525,7 @@ foreach(SUBPROJECT IN ITEMS
|
||||
lp_data
|
||||
packing
|
||||
scheduling
|
||||
set_cover
|
||||
port
|
||||
util)
|
||||
add_subdirectory(ortools/${SUBPROJECT})
|
||||
|
||||
@@ -150,6 +150,7 @@ file(GLOB_RECURSE OR_TOOLS_PROTO_PY_FILES RELATIVE ${PROJECT_SOURCE_DIR}
|
||||
"ortools/packing/*.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")
|
||||
@@ -288,6 +289,7 @@ foreach(SUBPROJECT IN ITEMS
|
||||
constraint_solver
|
||||
sat
|
||||
scheduling
|
||||
set_cover
|
||||
util)
|
||||
add_subdirectory(ortools/${SUBPROJECT}/python)
|
||||
endforeach()
|
||||
@@ -345,6 +347,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 "")
|
||||
|
||||
@@ -645,6 +649,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
|
||||
@@ -653,7 +659,6 @@ add_custom_command(
|
||||
DEPENDS
|
||||
init_pybind11
|
||||
knapsack_solver_pybind11
|
||||
set_cover_pybind11
|
||||
linear_sum_assignment_pybind11
|
||||
max_flow_pybind11
|
||||
min_cost_flow_pybind11
|
||||
@@ -664,6 +669,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)
|
||||
@@ -689,7 +695,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 .
|
||||
@@ -701,6 +706,7 @@ add_custom_command(
|
||||
COMMAND ${stubgen_EXECUTABLE} -p ortools.pdlp.python.pdlp --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
|
||||
|
||||
@@ -12,10 +12,7 @@
|
||||
# limitations under the License.
|
||||
|
||||
load("@bazel_skylib//rules:common_settings.bzl", "bool_flag")
|
||||
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"])
|
||||
|
||||
@@ -68,6 +65,7 @@ cc_library(
|
||||
hdrs = ["binary_search.h"],
|
||||
deps = [
|
||||
"//ortools/base",
|
||||
"@com_google_absl//absl/base:log_severity",
|
||||
"@com_google_absl//absl/functional:function_ref",
|
||||
"@com_google_absl//absl/log:check",
|
||||
"@com_google_absl//absl/numeric:int128",
|
||||
@@ -166,10 +164,9 @@ cc_test(
|
||||
srcs = ["hungarian_test.cc"],
|
||||
deps = [
|
||||
":hungarian",
|
||||
"//ortools/base",
|
||||
"//ortools/base:gmock_main",
|
||||
"//ortools/base:map_util",
|
||||
"//ortools/base:types",
|
||||
"@com_google_absl//absl/base:core_headers",
|
||||
"@com_google_absl//absl/container:flat_hash_map",
|
||||
"@com_google_absl//absl/random:distributions",
|
||||
"@com_google_absl//absl/types:span",
|
||||
@@ -250,163 +247,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"),
|
||||
],
|
||||
)
|
||||
|
||||
@@ -31,29 +31,6 @@ 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)
|
||||
|
||||
@@ -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'],
|
||||
@@ -113,6 +112,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'
|
||||
|
||||
@@ -21,9 +21,6 @@
|
||||
|
||||
#include "absl/algorithm/container.h"
|
||||
#include "absl/log/check.h"
|
||||
#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/base/stl_util.h"
|
||||
#include "ortools/sat/diffn_util.h"
|
||||
@@ -33,6 +30,9 @@
|
||||
#include "ortools/sat/no_overlap_2d_helper.h"
|
||||
#include "ortools/sat/synchronization.h"
|
||||
#include "ortools/sat/util.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"
|
||||
|
||||
namespace operations_research {
|
||||
namespace sat {
|
||||
|
||||
287
ortools/set_cover/BUILD.bazel
Normal file
287
ortools/set_cover/BUILD.bazel
Normal file
@@ -0,0 +1,287 @@
|
||||
# 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("@com_google_protobuf//bazel:py_proto_library.bzl", "py_proto_library")
|
||||
load("@rules_cc//cc:defs.bzl", "cc_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 = "base_types",
|
||||
hdrs = ["base_types.h"],
|
||||
deps = [
|
||||
"//ortools/base:intops",
|
||||
"//ortools/base:strong_vector",
|
||||
],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "set_cover_lagrangian",
|
||||
srcs = ["set_cover_lagrangian.cc"],
|
||||
hdrs = ["set_cover_lagrangian.h"],
|
||||
deps = [
|
||||
":base_types",
|
||||
":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 = [
|
||||
":base_types",
|
||||
":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 = [
|
||||
":base_types",
|
||||
":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 = [
|
||||
":base_types",
|
||||
":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 = [
|
||||
":base_types",
|
||||
":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 = [
|
||||
":base_types",
|
||||
":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 = [
|
||||
":base_types",
|
||||
":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 = [
|
||||
":base_types",
|
||||
":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 = [
|
||||
":base_types",
|
||||
":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 = [
|
||||
":base_types",
|
||||
":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",
|
||||
"//ortools/base:gmock_main",
|
||||
],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "capacity_invariant",
|
||||
srcs = ["capacity_invariant.cc"],
|
||||
hdrs = ["capacity_invariant.h"],
|
||||
deps = [
|
||||
":base_types",
|
||||
":capacity_model",
|
||||
":set_cover_model",
|
||||
"//ortools/util:saturated_arithmetic",
|
||||
"@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()
|
||||
127
ortools/set_cover/assignment.cc
Normal file
127
ortools/set_cover/assignment.cc
Normal file
@@ -0,0 +1,127 @@
|
||||
// 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 "absl/log/log.h"
|
||||
#include "ortools/base/mathutil.h"
|
||||
#include "ortools/set_cover/base_types.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
|
||||
105
ortools/set_cover/assignment.h
Normal file
105
ortools/set_cover/assignment.h
Normal file
@@ -0,0 +1,105 @@
|
||||
// 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/base_types.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 {
|
||||
|
||||
// `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
|
||||
70
ortools/set_cover/base_types.h
Normal file
70
ortools/set_cover/base_types.h
Normal file
@@ -0,0 +1,70 @@
|
||||
// Copyright 2010-2025 Google LLC
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef OR_TOOLS_SET_COVER_BASE_TYPES_H_
|
||||
#define OR_TOOLS_SET_COVER_BASE_TYPES_H_
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include "ortools/base/strong_int.h"
|
||||
#include "ortools/base/strong_vector.h"
|
||||
|
||||
namespace operations_research {
|
||||
|
||||
// Basic non-strict type for cost. The speed penalty for using double is ~2%.
|
||||
using Cost = double;
|
||||
|
||||
// Base non-strict integer type for counting elements and subsets.
|
||||
// Using ints makes it possible to represent problems with more than 2 billion
|
||||
// (2e9) elements and subsets. If need arises one day, BaseInt can be split
|
||||
// into SubsetBaseInt and ElementBaseInt.
|
||||
// Quick testing has shown a slowdown of about 20-25% when using int64_t.
|
||||
using BaseInt = int32_t;
|
||||
|
||||
// We make heavy use of strong typing to avoid obvious mistakes.
|
||||
// Subset index.
|
||||
DEFINE_STRONG_INT_TYPE(SubsetIndex, BaseInt);
|
||||
|
||||
// Element index.
|
||||
DEFINE_STRONG_INT_TYPE(ElementIndex, BaseInt);
|
||||
|
||||
// Position in a vector. The vector may either represent a column, i.e. a
|
||||
// subset with all its elements, or a row, i,e. the list of subsets which
|
||||
// contain a given element.
|
||||
DEFINE_STRONG_INT_TYPE(ColumnEntryIndex, BaseInt);
|
||||
DEFINE_STRONG_INT_TYPE(RowEntryIndex, BaseInt);
|
||||
|
||||
using SubsetRange = util_intops::StrongIntRange<SubsetIndex>;
|
||||
using ElementRange = util_intops::StrongIntRange<ElementIndex>;
|
||||
using ColumnEntryRange = util_intops::StrongIntRange<ColumnEntryIndex>;
|
||||
|
||||
using SubsetCostVector = util_intops::StrongVector<SubsetIndex, Cost>;
|
||||
using ElementCostVector = util_intops::StrongVector<ElementIndex, Cost>;
|
||||
|
||||
using SparseColumn = util_intops::StrongVector<ColumnEntryIndex, ElementIndex>;
|
||||
using SparseRow = util_intops::StrongVector<RowEntryIndex, SubsetIndex>;
|
||||
|
||||
using ElementToIntVector = util_intops::StrongVector<ElementIndex, BaseInt>;
|
||||
using SubsetToIntVector = util_intops::StrongVector<SubsetIndex, BaseInt>;
|
||||
|
||||
// Views of the sparse vectors. These need not be aligned as it's their contents
|
||||
// that need to be aligned.
|
||||
using SparseColumnView = util_intops::StrongVector<SubsetIndex, SparseColumn>;
|
||||
using SparseRowView = util_intops::StrongVector<ElementIndex, SparseRow>;
|
||||
|
||||
using SubsetBoolVector = util_intops::StrongVector<SubsetIndex, bool>;
|
||||
using ElementBoolVector = util_intops::StrongVector<ElementIndex, bool>;
|
||||
|
||||
} // namespace operations_research
|
||||
|
||||
#endif // OR_TOOLS_SET_COVER_BASE_TYPES_H_
|
||||
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`).
|
||||
int64 subset = 1;
|
||||
|
||||
message ElementWeightPair {
|
||||
// The element this weight corresponds to (value of `element` in
|
||||
// `SetCoverProto.Subset`).
|
||||
int64 element = 1;
|
||||
|
||||
// The weight of the element.
|
||||
int64 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.
|
||||
int64 min_capacity = 2;
|
||||
|
||||
// The maximum amount of resource that can be consumed. At least one of
|
||||
// `min_capacity` and `max_capacity` must be present.
|
||||
int64 max_capacity = 3;
|
||||
}
|
||||
123
ortools/set_cover/capacity_invariant.cc
Normal file
123
ortools/set_cover/capacity_invariant.cc
Normal file
@@ -0,0 +1,123 @@
|
||||
// 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 <limits>
|
||||
|
||||
#include "absl/log/check.h"
|
||||
#include "absl/log/log.h"
|
||||
#include "ortools/set_cover/base_types.h"
|
||||
#include "ortools/set_cover/capacity_model.h"
|
||||
#include "ortools/set_cover/set_cover_model.h"
|
||||
#include "ortools/util/saturated_arithmetic.h"
|
||||
|
||||
namespace operations_research {
|
||||
|
||||
void CapacityInvariant::Clear() {
|
||||
current_slack_ = 0.0;
|
||||
is_selected_.assign(set_cover_model_->num_subsets(), false);
|
||||
}
|
||||
|
||||
namespace {
|
||||
// Returns true if the addition of `q` to `sum` overflows, for q >= 0.
|
||||
bool PositiveAddOverflows(CapacityWeight sum, CapacityWeight q) {
|
||||
DCHECK_GE(q, 0);
|
||||
return sum > std::numeric_limits<CapacityWeight>::max() - q;
|
||||
}
|
||||
|
||||
// Returns true if the addition of `q` to `sum` overflows, for q <= 0.
|
||||
bool NegativeAddOverflows(CapacityWeight sum, CapacityWeight q) {
|
||||
DCHECK_LE(q, 0);
|
||||
return sum < std::numeric_limits<CapacityWeight>::min() - q;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
CapacityWeight CapacityInvariant::ComputeSlackChange(
|
||||
const SubsetIndex subset) const {
|
||||
CapacityWeight slack_change = 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.
|
||||
const CapacityWeight term_weight = model_->GetTermCapacityWeight(term);
|
||||
// Make sure that the slack change will not overflow.
|
||||
CHECK(!PositiveAddOverflows(slack_change, term_weight));
|
||||
slack_change += term_weight;
|
||||
}
|
||||
}
|
||||
return slack_change;
|
||||
}
|
||||
|
||||
bool CapacityInvariant::SlackChangeFitsConstraint(
|
||||
CapacityWeight slack_change) const {
|
||||
CHECK(!AddOverflows(current_slack_, slack_change));
|
||||
const CapacityWeight new_slack = current_slack_ + slack_change;
|
||||
return new_slack >= model_->GetMinimumCapacity() &&
|
||||
new_slack <= model_->GetMaximumCapacity();
|
||||
}
|
||||
|
||||
bool CapacityInvariant::Select(SubsetIndex subset) {
|
||||
DVLOG(1) << "[Capacity constraint] Selecting subset " << subset;
|
||||
DCHECK(!is_selected_[subset]);
|
||||
|
||||
const CapacityWeight slack_change = ComputeSlackChange(subset);
|
||||
if (!SlackChangeFitsConstraint(slack_change)) {
|
||||
DVLOG(1) << "[Capacity constraint] Selecting subset " << subset
|
||||
<< ": infeasible";
|
||||
return false;
|
||||
}
|
||||
CHECK(!PositiveAddOverflows(current_slack_, slack_change));
|
||||
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 CapacityWeight 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 CapacityWeight slack_change = -ComputeSlackChange(subset);
|
||||
if (!SlackChangeFitsConstraint(slack_change)) {
|
||||
DVLOG(1) << "[Capacity constraint] Deselecting subset " << subset
|
||||
<< ": infeasible";
|
||||
return false;
|
||||
}
|
||||
CHECK(!NegativeAddOverflows(current_slack_, slack_change));
|
||||
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 CapacityWeight slack_change = -ComputeSlackChange(subset);
|
||||
DVLOG(1) << "[Capacity constraint] New slack if deselecting: "
|
||||
<< current_slack_ + slack_change;
|
||||
return SlackChangeFitsConstraint(slack_change);
|
||||
}
|
||||
} // namespace operations_research
|
||||
96
ortools/set_cover/capacity_invariant.h
Normal file
96
ortools/set_cover/capacity_invariant.h
Normal file
@@ -0,0 +1,96 @@
|
||||
// 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/base_types.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 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.
|
||||
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.
|
||||
CapacityWeight ComputeSlackChange(SubsetIndex subset) const;
|
||||
|
||||
// Determines whether the given slack change violates the constraint
|
||||
// (`false`) or not (`true`).
|
||||
bool SlackChangeFitsConstraint(CapacityWeight 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.CanSelect(SubsetIndex(0))); // All moves are possible.
|
||||
EXPECT_TRUE(cinv.CanSelect(SubsetIndex(1)));
|
||||
|
||||
EXPECT_TRUE(cinv.Select(SubsetIndex(0)));
|
||||
EXPECT_TRUE(cinv.CanDeselect(SubsetIndex(0))); // Undoing: still valid.
|
||||
EXPECT_FALSE(cinv.CanSelect(SubsetIndex(1))); // Impossible move.
|
||||
EXPECT_FALSE(cinv.Select(SubsetIndex(1)));
|
||||
// Current assignment: [true, false]. Current activation: 1.
|
||||
|
||||
EXPECT_TRUE(cinv.Deselect(SubsetIndex(0)));
|
||||
EXPECT_TRUE(cinv.CanSelect(SubsetIndex(0))); // Undoing: still valid.
|
||||
EXPECT_TRUE(cinv.CanSelect(SubsetIndex(1))); // Valid when 0 not selected.
|
||||
EXPECT_TRUE(cinv.Select(SubsetIndex(1)));
|
||||
EXPECT_FALSE(cinv.CanSelect(SubsetIndex(0))); // Impossible move.
|
||||
EXPECT_TRUE(cinv.CanDeselect(SubsetIndex(1))); // Undoing: still valid.
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace operations_research
|
||||
127
ortools/set_cover/capacity_model.cc
Normal file
127
ortools/set_cover/capacity_model.cc
Normal file
@@ -0,0 +1,127 @@
|
||||
// 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 <limits>
|
||||
#include <numeric>
|
||||
#include <vector>
|
||||
|
||||
#include "absl/log/check.h"
|
||||
#include "absl/log/log.h"
|
||||
#include "ortools/set_cover/base_types.h"
|
||||
#include "ortools/set_cover/set_cover_model.h"
|
||||
|
||||
namespace operations_research {
|
||||
void CapacityModel::AddTerm(SubsetIndex subset, ElementIndex element,
|
||||
CapacityWeight 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_NE(min_capacity, std::numeric_limits<CapacityWeight>::max());
|
||||
min_capacity_ = min_capacity;
|
||||
}
|
||||
|
||||
void CapacityModel::SetMaximumCapacity(CapacityWeight 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
|
||||
155
ortools/set_cover/capacity_model.h
Normal file
155
ortools/set_cover/capacity_model.h
Normal file
@@ -0,0 +1,155 @@
|
||||
// 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 <cstdint>
|
||||
#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/base_types.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 = int64_t;
|
||||
|
||||
// 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(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.
|
||||
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_
|
||||
135
ortools/set_cover/capacity_model_test.cc
Normal file
135
ortools/set_cover/capacity_model_test.cc
Normal file
@@ -0,0 +1,135 @@
|
||||
// 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"
|
||||
|
||||
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, 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, SetMinimumCapacityRejectsPlusInfinity) {
|
||||
CapacityModel m(0, 1);
|
||||
EXPECT_DEATH(m.SetMinimumCapacity(std::numeric_limits<CapacityWeight>::max()),
|
||||
"max");
|
||||
}
|
||||
|
||||
TEST(CapacityModel, SetMaximumCapacityRejectsMinusInfinity) {
|
||||
CapacityModel m(0, 1);
|
||||
EXPECT_DEATH(m.SetMaximumCapacity(std::numeric_limits<CapacityWeight>::min()),
|
||||
"min");
|
||||
}
|
||||
|
||||
TEST(CapacityModel, ComputeFeasibilityWithNoTerms) {
|
||||
CapacityModel m(0, 1);
|
||||
EXPECT_TRUE(m.ComputeFeasibility());
|
||||
|
||||
m.SetMinimumCapacity(-1);
|
||||
EXPECT_TRUE(m.ComputeFeasibility());
|
||||
|
||||
m.SetMaximumCapacity(0);
|
||||
EXPECT_TRUE(m.ComputeFeasibility());
|
||||
|
||||
m.SetMinimumCapacity(-2);
|
||||
m.SetMaximumCapacity(-1);
|
||||
EXPECT_FALSE(m.ComputeFeasibility());
|
||||
}
|
||||
|
||||
TEST(CapacityModel, ComputeFeasibilityWithOnlyPositiveWeights) {
|
||||
CapacityModel m(0, 1);
|
||||
m.AddTerm(SubsetIndex(0), ElementIndex(0), 1);
|
||||
m.AddTerm(SubsetIndex(0), ElementIndex(1), 2);
|
||||
m.AddTerm(SubsetIndex(0), ElementIndex(2), 3);
|
||||
// Activation bounds: [0, 6].
|
||||
EXPECT_TRUE(m.ComputeFeasibility());
|
||||
|
||||
m.SetMinimumCapacity(-1);
|
||||
EXPECT_TRUE(m.ComputeFeasibility());
|
||||
|
||||
m.SetMaximumCapacity(-1);
|
||||
EXPECT_FALSE(m.ComputeFeasibility());
|
||||
|
||||
m.SetMaximumCapacity(7);
|
||||
EXPECT_TRUE(m.ComputeFeasibility());
|
||||
|
||||
m.SetMinimumCapacity(7);
|
||||
EXPECT_FALSE(m.ComputeFeasibility());
|
||||
}
|
||||
|
||||
TEST(CapacityModel, ComputeFeasibilityWithOnlyNegativeWeights) {
|
||||
CapacityModel m(0, 1);
|
||||
m.AddTerm(SubsetIndex(0), ElementIndex(0), -1);
|
||||
m.AddTerm(SubsetIndex(0), ElementIndex(1), -2);
|
||||
m.AddTerm(SubsetIndex(0), ElementIndex(2), -3);
|
||||
// Activation bounds: [-6, 0].
|
||||
EXPECT_TRUE(m.ComputeFeasibility());
|
||||
|
||||
m.SetMaximumCapacity(1);
|
||||
EXPECT_TRUE(m.ComputeFeasibility());
|
||||
|
||||
m.SetMinimumCapacity(1);
|
||||
EXPECT_FALSE(m.ComputeFeasibility());
|
||||
|
||||
m.SetMinimumCapacity(-7);
|
||||
EXPECT_TRUE(m.ComputeFeasibility());
|
||||
|
||||
m.SetMaximumCapacity(-7);
|
||||
EXPECT_FALSE(m.ComputeFeasibility());
|
||||
}
|
||||
|
||||
TEST(CapacityModel, ComputeFeasibilityWithOnlyMixedWeights) {
|
||||
CapacityModel m(0, 1);
|
||||
m.AddTerm(SubsetIndex(0), ElementIndex(0), -1);
|
||||
m.AddTerm(SubsetIndex(0), ElementIndex(1), 2);
|
||||
m.AddTerm(SubsetIndex(0), ElementIndex(2), -3);
|
||||
// Activation bounds: [-4, 2].
|
||||
EXPECT_TRUE(m.ComputeFeasibility());
|
||||
|
||||
m.SetMaximumCapacity(3);
|
||||
EXPECT_TRUE(m.ComputeFeasibility());
|
||||
|
||||
m.SetMinimumCapacity(3);
|
||||
EXPECT_FALSE(m.ComputeFeasibility());
|
||||
|
||||
m.SetMinimumCapacity(-5);
|
||||
EXPECT_TRUE(m.ComputeFeasibility());
|
||||
|
||||
m.SetMaximumCapacity(-5);
|
||||
EXPECT_FALSE(m.ComputeFeasibility());
|
||||
}
|
||||
|
||||
} // 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()
|
||||
570
ortools/set_cover/python/set_cover.cc
Normal file
570
ortools/set_cover/python/set_cover.cc
Normal file
@@ -0,0 +1,570 @@
|
||||
// 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.
|
||||
|
||||
// A pybind11 wrapper for set_cover_*.
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstddef>
|
||||
#include <iterator>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "absl/types/span.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"
|
||||
#include "pybind11/stl.h"
|
||||
#include "pybind11_protobuf/native_proto_caster.h"
|
||||
|
||||
using ::operations_research::BaseInt;
|
||||
using ::operations_research::ClearRandomSubsets;
|
||||
using ::operations_research::ElementDegreeSolutionGenerator;
|
||||
using ::operations_research::ElementIndex;
|
||||
using ::operations_research::GreedySolutionGenerator;
|
||||
using ::operations_research::GuidedLocalSearch;
|
||||
using ::operations_research::GuidedTabuSearch;
|
||||
using ::operations_research::LazyElementDegreeSolutionGenerator;
|
||||
using ::operations_research::RandomSolutionGenerator;
|
||||
using ::operations_research::ReadFimiDat;
|
||||
using ::operations_research::ReadOrlibRail;
|
||||
using ::operations_research::ReadOrlibScp;
|
||||
using ::operations_research::ReadSetCoverProto;
|
||||
using ::operations_research::ReadSetCoverSolutionProto;
|
||||
using ::operations_research::ReadSetCoverSolutionText;
|
||||
using ::operations_research::WriteOrlibRail;
|
||||
using ::operations_research::WriteOrlibScp;
|
||||
using ::operations_research::WriteSetCoverProto;
|
||||
using ::operations_research::WriteSetCoverSolutionProto;
|
||||
using ::operations_research::WriteSetCoverSolutionText;
|
||||
|
||||
using ::operations_research::SetCoverDecision;
|
||||
using ::operations_research::SetCoverInvariant;
|
||||
using ::operations_research::SetCoverModel;
|
||||
using ::operations_research::SparseColumn;
|
||||
using ::operations_research::SparseRow;
|
||||
using ::operations_research::SteepestSearch;
|
||||
using ::operations_research::SubsetBoolVector;
|
||||
using ::operations_research::SubsetCostVector;
|
||||
using ::operations_research::SubsetIndex;
|
||||
using ::operations_research::TabuList;
|
||||
using ::operations_research::TrivialSolutionGenerator;
|
||||
|
||||
namespace py = pybind11;
|
||||
using ::py::arg;
|
||||
using ::py::make_iterator;
|
||||
|
||||
std::vector<SubsetIndex> VectorIntToVectorSubsetIndex(
|
||||
absl::Span<const BaseInt> ints) {
|
||||
std::vector<SubsetIndex> subs;
|
||||
std::transform(ints.begin(), ints.end(), subs.begin(),
|
||||
[](int subset) -> SubsetIndex { return SubsetIndex(subset); });
|
||||
return subs;
|
||||
}
|
||||
|
||||
SubsetCostVector VectorDoubleToSubsetCostVector(
|
||||
absl::Span<const double> doubles) {
|
||||
SubsetCostVector costs(doubles.begin(), doubles.end());
|
||||
return costs;
|
||||
}
|
||||
|
||||
class IntIterator {
|
||||
public:
|
||||
using value_type = int;
|
||||
using difference_type = std::ptrdiff_t;
|
||||
using pointer = int*;
|
||||
using reference = int&;
|
||||
using iterator_category = std::input_iterator_tag;
|
||||
|
||||
explicit IntIterator(int max_value)
|
||||
: max_value_(max_value), current_value_(0) {}
|
||||
|
||||
int operator*() const { return current_value_; }
|
||||
IntIterator& operator++() {
|
||||
++current_value_;
|
||||
return *this;
|
||||
}
|
||||
|
||||
static IntIterator begin(int max_value) { return IntIterator{max_value}; }
|
||||
static IntIterator end(int max_value) { return {max_value, max_value}; }
|
||||
|
||||
friend bool operator==(const IntIterator& lhs, const IntIterator& rhs) {
|
||||
return lhs.max_value_ == rhs.max_value_ &&
|
||||
lhs.current_value_ == rhs.current_value_;
|
||||
}
|
||||
|
||||
private:
|
||||
IntIterator(int max_value, int current_value)
|
||||
: max_value_(max_value), current_value_(current_value) {}
|
||||
|
||||
const int max_value_;
|
||||
int current_value_;
|
||||
};
|
||||
|
||||
PYBIND11_MODULE(set_cover, m) {
|
||||
pybind11_protobuf::ImportNativeProtoCasters();
|
||||
|
||||
// set_cover_model.h
|
||||
py::class_<SetCoverModel::Stats>(m, "SetCoverModelStats")
|
||||
.def_readwrite("min", &SetCoverModel::Stats::min)
|
||||
.def_readwrite("max", &SetCoverModel::Stats::max)
|
||||
.def_readwrite("median", &SetCoverModel::Stats::median)
|
||||
.def_readwrite("mean", &SetCoverModel::Stats::mean)
|
||||
.def_readwrite("stddev", &SetCoverModel::Stats::stddev)
|
||||
.def("debug_string", &SetCoverModel::Stats::DebugString);
|
||||
|
||||
py::class_<SetCoverModel>(m, "SetCoverModel")
|
||||
.def(py::init<>())
|
||||
.def_property_readonly("num_elements", &SetCoverModel::num_elements)
|
||||
.def_property_readonly("num_subsets", &SetCoverModel::num_subsets)
|
||||
.def_property_readonly("num_nonzeros", &SetCoverModel::num_nonzeros)
|
||||
.def_property_readonly("fill_rate", &SetCoverModel::FillRate)
|
||||
.def_property_readonly(
|
||||
"subset_costs",
|
||||
[](SetCoverModel& model) -> const std::vector<double>& {
|
||||
return model.subset_costs().get();
|
||||
})
|
||||
.def_property_readonly(
|
||||
"columns",
|
||||
[](SetCoverModel& model) -> std::vector<std::vector<BaseInt>> {
|
||||
// Due to the inner StrongVector, make a deep copy. Anyway,
|
||||
// columns() returns a const ref, so this keeps the semantics, not
|
||||
// the efficiency.
|
||||
std::vector<std::vector<BaseInt>> columns(model.columns().size());
|
||||
std::transform(
|
||||
model.columns().begin(), model.columns().end(), columns.begin(),
|
||||
[](const SparseColumn& column) -> std::vector<BaseInt> {
|
||||
std::vector<BaseInt> col(column.size());
|
||||
std::transform(column.begin(), column.end(), col.begin(),
|
||||
[](ElementIndex element) -> BaseInt {
|
||||
return element.value();
|
||||
});
|
||||
return col;
|
||||
});
|
||||
return columns;
|
||||
})
|
||||
.def_property_readonly(
|
||||
"rows",
|
||||
[](SetCoverModel& model) -> std::vector<std::vector<BaseInt>> {
|
||||
// Due to the inner StrongVector, make a deep copy. Anyway,
|
||||
// rows() returns a const ref, so this keeps the semantics, not
|
||||
// the efficiency.
|
||||
std::vector<std::vector<BaseInt>> rows(model.rows().size());
|
||||
std::transform(model.rows().begin(), model.rows().end(),
|
||||
rows.begin(),
|
||||
[](const SparseRow& row) -> std::vector<BaseInt> {
|
||||
std::vector<BaseInt> r(row.size());
|
||||
std::transform(row.begin(), row.end(), r.begin(),
|
||||
[](SubsetIndex element) -> BaseInt {
|
||||
return element.value();
|
||||
});
|
||||
return r;
|
||||
});
|
||||
return rows;
|
||||
})
|
||||
.def_property_readonly("row_view_is_valid",
|
||||
&SetCoverModel::row_view_is_valid)
|
||||
.def("SubsetRange",
|
||||
[](SetCoverModel& model) {
|
||||
return make_iterator<>(IntIterator::begin(model.num_subsets()),
|
||||
IntIterator::end(model.num_subsets()));
|
||||
})
|
||||
.def("ElementRange",
|
||||
[](SetCoverModel& model) {
|
||||
return make_iterator<>(IntIterator::begin(model.num_elements()),
|
||||
IntIterator::end(model.num_elements()));
|
||||
})
|
||||
.def_property_readonly("all_subsets",
|
||||
[](SetCoverModel& model) -> std::vector<BaseInt> {
|
||||
std::vector<BaseInt> subsets;
|
||||
std::transform(
|
||||
model.all_subsets().begin(),
|
||||
model.all_subsets().end(), subsets.begin(),
|
||||
[](const SubsetIndex element) -> BaseInt {
|
||||
return element.value();
|
||||
});
|
||||
return subsets;
|
||||
})
|
||||
.def("add_empty_subset", &SetCoverModel::AddEmptySubset, arg("cost"))
|
||||
.def(
|
||||
"add_element_to_last_subset",
|
||||
[](SetCoverModel& model, BaseInt element) {
|
||||
model.AddElementToLastSubset(element);
|
||||
},
|
||||
arg("element"))
|
||||
.def(
|
||||
"set_subset_cost",
|
||||
[](SetCoverModel& model, BaseInt subset, double cost) {
|
||||
model.SetSubsetCost(subset, cost);
|
||||
},
|
||||
arg("subset"), arg("cost"))
|
||||
.def(
|
||||
"add_element_to_subset",
|
||||
[](SetCoverModel& model, BaseInt element, BaseInt subset) {
|
||||
model.AddElementToSubset(element, subset);
|
||||
},
|
||||
arg("subset"), arg("cost"))
|
||||
.def("create_sparse_row_view", &SetCoverModel::CreateSparseRowView)
|
||||
.def("sort_elements_in_subsets", &SetCoverModel::SortElementsInSubsets)
|
||||
.def("compute_feasibility", &SetCoverModel::ComputeFeasibility)
|
||||
.def(
|
||||
"reserve_num_subsets",
|
||||
[](SetCoverModel& model, BaseInt num_subsets) {
|
||||
model.ReserveNumSubsets(num_subsets);
|
||||
},
|
||||
arg("num_subsets"))
|
||||
.def(
|
||||
"reserve_num_elements_in_subset",
|
||||
[](SetCoverModel& model, BaseInt num_elements, BaseInt subset) {
|
||||
model.ReserveNumElementsInSubset(num_elements, subset);
|
||||
},
|
||||
arg("num_elements"), arg("subset"))
|
||||
.def("export_model_as_proto", &SetCoverModel::ExportModelAsProto)
|
||||
.def("import_model_from_proto", &SetCoverModel::ImportModelFromProto)
|
||||
.def("compute_cost_stats", &SetCoverModel::ComputeCostStats)
|
||||
.def("compute_row_stats", &SetCoverModel::ComputeRowStats)
|
||||
.def("compute_column_stats", &SetCoverModel::ComputeColumnStats)
|
||||
.def("compute_row_deciles", &SetCoverModel::ComputeRowDeciles)
|
||||
.def("compute_column_deciles", &SetCoverModel::ComputeRowDeciles);
|
||||
|
||||
// TODO(user): wrap IntersectingSubsetsIterator.
|
||||
|
||||
// set_cover_invariant.h
|
||||
py::class_<SetCoverDecision>(m, "SetCoverDecision")
|
||||
.def(py::init<>())
|
||||
.def(py::init([](BaseInt subset, bool value) -> SetCoverDecision* {
|
||||
return new SetCoverDecision(SubsetIndex(subset), value);
|
||||
}),
|
||||
arg("subset"), arg("value"))
|
||||
.def("subset",
|
||||
[](const SetCoverDecision& decision) -> BaseInt {
|
||||
return decision.subset().value();
|
||||
})
|
||||
.def("decision", &SetCoverDecision::decision);
|
||||
|
||||
py::enum_<SetCoverInvariant::ConsistencyLevel>(m, "consistency_level")
|
||||
.value("COST_AND_COVERAGE",
|
||||
SetCoverInvariant::ConsistencyLevel::kCostAndCoverage)
|
||||
.value("FREE_AND_UNCOVERED",
|
||||
SetCoverInvariant::ConsistencyLevel::kFreeAndUncovered)
|
||||
.value("REDUNDANCY", SetCoverInvariant::ConsistencyLevel::kRedundancy);
|
||||
|
||||
py::class_<SetCoverInvariant>(m, "SetCoverInvariant")
|
||||
.def(py::init<SetCoverModel*>())
|
||||
.def("initialize", &SetCoverInvariant::Initialize)
|
||||
.def("clear", &SetCoverInvariant::Clear)
|
||||
.def("model", &SetCoverInvariant::model)
|
||||
.def_property(
|
||||
"model",
|
||||
// Expected semantics: give a pointer to Python **while
|
||||
// keeping ownership** in C++.
|
||||
[](SetCoverInvariant& invariant) -> std::shared_ptr<SetCoverModel> {
|
||||
// https://pybind11.readthedocs.io/en/stable/advanced/smart_ptrs.html#std-shared-ptr
|
||||
std::shared_ptr<SetCoverModel> ptr(invariant.model());
|
||||
return ptr;
|
||||
},
|
||||
[](SetCoverInvariant& invariant, const SetCoverModel& model) {
|
||||
*invariant.model() = model;
|
||||
})
|
||||
.def("cost", &SetCoverInvariant::cost)
|
||||
.def("num_uncovered_elements", &SetCoverInvariant::num_uncovered_elements)
|
||||
.def("is_selected",
|
||||
[](SetCoverInvariant& invariant) -> std::vector<bool> {
|
||||
return invariant.is_selected().get();
|
||||
})
|
||||
.def("num_free_elements",
|
||||
[](SetCoverInvariant& invariant) -> std::vector<BaseInt> {
|
||||
return invariant.num_free_elements().get();
|
||||
})
|
||||
.def("num_coverage_le_1_elements",
|
||||
[](SetCoverInvariant& invariant) -> std::vector<BaseInt> {
|
||||
return invariant.num_coverage_le_1_elements().get();
|
||||
})
|
||||
.def("coverage",
|
||||
[](SetCoverInvariant& invariant) -> std::vector<BaseInt> {
|
||||
return invariant.coverage().get();
|
||||
})
|
||||
.def(
|
||||
"compute_coverage_in_focus",
|
||||
[](SetCoverInvariant& invariant,
|
||||
absl::Span<const BaseInt> focus) -> std::vector<BaseInt> {
|
||||
return invariant
|
||||
.ComputeCoverageInFocus(VectorIntToVectorSubsetIndex(focus))
|
||||
.get();
|
||||
},
|
||||
arg("focus"))
|
||||
.def("is_redundant",
|
||||
[](SetCoverInvariant& invariant) -> std::vector<bool> {
|
||||
return invariant.is_redundant().get();
|
||||
})
|
||||
.def("trace", &SetCoverInvariant::trace)
|
||||
.def("clear_trace", &SetCoverInvariant::ClearTrace)
|
||||
.def("clear_removability_information",
|
||||
&SetCoverInvariant::ClearRemovabilityInformation)
|
||||
.def("newly_removable_subsets",
|
||||
&SetCoverInvariant::newly_removable_subsets)
|
||||
.def("newly_non_removable_subsets",
|
||||
&SetCoverInvariant::newly_non_removable_subsets)
|
||||
.def("compress_trace", &SetCoverInvariant::CompressTrace)
|
||||
.def("load_solution",
|
||||
[](SetCoverInvariant& invariant,
|
||||
absl::Span<const bool> solution) -> void {
|
||||
SubsetBoolVector sol(solution.begin(), solution.end());
|
||||
return invariant.LoadSolution(sol);
|
||||
})
|
||||
.def("check_consistency", &SetCoverInvariant::CheckConsistency)
|
||||
.def(
|
||||
"compute_is_redundant",
|
||||
[](SetCoverInvariant& invariant, BaseInt subset) -> bool {
|
||||
return invariant.ComputeIsRedundant(SubsetIndex(subset));
|
||||
},
|
||||
arg("subset"))
|
||||
.def("recompute", &SetCoverInvariant::Recompute)
|
||||
.def(
|
||||
"select",
|
||||
[](SetCoverInvariant& invariant, BaseInt subset,
|
||||
SetCoverInvariant::ConsistencyLevel consistency) {
|
||||
invariant.Select(SubsetIndex(subset), consistency);
|
||||
},
|
||||
arg("subset"), arg("consistency"))
|
||||
.def(
|
||||
"deselect",
|
||||
[](SetCoverInvariant& invariant, BaseInt subset,
|
||||
SetCoverInvariant::ConsistencyLevel consistency) {
|
||||
invariant.Deselect(SubsetIndex(subset), consistency);
|
||||
},
|
||||
arg("subset"), arg("consistency"))
|
||||
.def("export_solution_as_proto",
|
||||
&SetCoverInvariant::ExportSolutionAsProto)
|
||||
.def("import_solution_from_proto",
|
||||
&SetCoverInvariant::ImportSolutionFromProto);
|
||||
|
||||
// set_cover_heuristics.h
|
||||
py::class_<TrivialSolutionGenerator>(m, "TrivialSolutionGenerator")
|
||||
.def(py::init<SetCoverInvariant*>())
|
||||
.def("next_solution",
|
||||
[](TrivialSolutionGenerator& heuristic) -> bool {
|
||||
return heuristic.NextSolution();
|
||||
})
|
||||
.def("next_solution",
|
||||
[](TrivialSolutionGenerator& heuristic,
|
||||
absl::Span<const BaseInt> focus) -> bool {
|
||||
return heuristic.NextSolution(VectorIntToVectorSubsetIndex(focus));
|
||||
});
|
||||
|
||||
py::class_<RandomSolutionGenerator>(m, "RandomSolutionGenerator")
|
||||
.def(py::init<SetCoverInvariant*>())
|
||||
.def("next_solution",
|
||||
[](RandomSolutionGenerator& heuristic) -> bool {
|
||||
return heuristic.NextSolution();
|
||||
})
|
||||
.def("next_solution",
|
||||
[](RandomSolutionGenerator& heuristic,
|
||||
absl::Span<const BaseInt> focus) -> bool {
|
||||
return heuristic.NextSolution(VectorIntToVectorSubsetIndex(focus));
|
||||
});
|
||||
|
||||
py::class_<GreedySolutionGenerator>(m, "GreedySolutionGenerator")
|
||||
.def(py::init<SetCoverInvariant*>())
|
||||
.def("next_solution",
|
||||
[](GreedySolutionGenerator& heuristic) -> bool {
|
||||
return heuristic.NextSolution();
|
||||
})
|
||||
.def("next_solution",
|
||||
[](GreedySolutionGenerator& heuristic,
|
||||
absl::Span<const BaseInt> focus) -> bool {
|
||||
return heuristic.NextSolution(VectorIntToVectorSubsetIndex(focus));
|
||||
})
|
||||
.def("next_solution",
|
||||
[](GreedySolutionGenerator& heuristic,
|
||||
absl::Span<const BaseInt> focus,
|
||||
absl::Span<const double> costs) -> bool {
|
||||
return heuristic.NextSolution(
|
||||
VectorIntToVectorSubsetIndex(focus),
|
||||
VectorDoubleToSubsetCostVector(costs));
|
||||
});
|
||||
|
||||
py::class_<ElementDegreeSolutionGenerator>(m,
|
||||
"ElementDegreeSolutionGenerator")
|
||||
.def(py::init<SetCoverInvariant*>())
|
||||
.def("next_solution",
|
||||
[](ElementDegreeSolutionGenerator& heuristic) -> bool {
|
||||
return heuristic.NextSolution();
|
||||
})
|
||||
.def("next_solution",
|
||||
[](ElementDegreeSolutionGenerator& heuristic,
|
||||
absl::Span<const BaseInt> focus) -> bool {
|
||||
return heuristic.NextSolution(VectorIntToVectorSubsetIndex(focus));
|
||||
})
|
||||
.def("next_solution",
|
||||
[](ElementDegreeSolutionGenerator& heuristic,
|
||||
absl::Span<const BaseInt> focus,
|
||||
absl::Span<const double> costs) -> bool {
|
||||
return heuristic.NextSolution(
|
||||
VectorIntToVectorSubsetIndex(focus),
|
||||
VectorDoubleToSubsetCostVector(costs));
|
||||
});
|
||||
|
||||
py::class_<LazyElementDegreeSolutionGenerator>(
|
||||
m, "LazyElementDegreeSolutionGenerator")
|
||||
.def(py::init<SetCoverInvariant*>())
|
||||
.def("next_solution",
|
||||
[](LazyElementDegreeSolutionGenerator& heuristic) -> bool {
|
||||
return heuristic.NextSolution();
|
||||
})
|
||||
.def("next_solution",
|
||||
[](LazyElementDegreeSolutionGenerator& heuristic,
|
||||
absl::Span<const BaseInt> focus) -> bool {
|
||||
return heuristic.NextSolution(VectorIntToVectorSubsetIndex(focus));
|
||||
})
|
||||
.def("next_solution",
|
||||
[](LazyElementDegreeSolutionGenerator& heuristic,
|
||||
absl::Span<const BaseInt> focus,
|
||||
absl::Span<const double> costs) -> bool {
|
||||
return heuristic.NextSolution(
|
||||
VectorIntToVectorSubsetIndex(focus),
|
||||
VectorDoubleToSubsetCostVector(costs));
|
||||
});
|
||||
|
||||
py::class_<SteepestSearch>(m, "SteepestSearch")
|
||||
.def(py::init<SetCoverInvariant*>())
|
||||
.def("next_solution",
|
||||
[](SteepestSearch& heuristic, int num_iterations) -> bool {
|
||||
return heuristic.NextSolution(num_iterations);
|
||||
})
|
||||
.def("next_solution",
|
||||
[](SteepestSearch& heuristic, absl::Span<const BaseInt> focus,
|
||||
int num_iterations) -> bool {
|
||||
return heuristic.NextSolution(VectorIntToVectorSubsetIndex(focus),
|
||||
num_iterations);
|
||||
})
|
||||
.def("next_solution",
|
||||
[](SteepestSearch& heuristic, absl::Span<const BaseInt> focus,
|
||||
absl::Span<const double> costs, int num_iterations) -> bool {
|
||||
return heuristic.NextSolution(
|
||||
VectorIntToVectorSubsetIndex(focus),
|
||||
VectorDoubleToSubsetCostVector(costs), num_iterations);
|
||||
});
|
||||
|
||||
py::class_<GuidedLocalSearch>(m, "GuidedLocalSearch")
|
||||
.def(py::init<SetCoverInvariant*>())
|
||||
.def("initialize", &GuidedLocalSearch::Initialize)
|
||||
.def("next_solution",
|
||||
[](GuidedLocalSearch& heuristic, int num_iterations) -> bool {
|
||||
return heuristic.NextSolution(num_iterations);
|
||||
})
|
||||
.def("next_solution",
|
||||
[](GuidedLocalSearch& heuristic, absl::Span<const BaseInt> focus,
|
||||
int num_iterations) -> bool {
|
||||
return heuristic.NextSolution(VectorIntToVectorSubsetIndex(focus),
|
||||
num_iterations);
|
||||
});
|
||||
|
||||
// Specialization for T = SubsetIndex ~= BaseInt (aka int for Python, whatever
|
||||
// the size of BaseInt).
|
||||
// A base type doesn't work, because TabuList uses `T::value` in the
|
||||
// constructor.
|
||||
py::class_<TabuList<SubsetIndex>>(m, "TabuList")
|
||||
.def(py::init([](int size) -> TabuList<SubsetIndex>* {
|
||||
return new TabuList<SubsetIndex>(SubsetIndex(size));
|
||||
}),
|
||||
arg("size"))
|
||||
.def("size", &TabuList<SubsetIndex>::size)
|
||||
.def("init", &TabuList<SubsetIndex>::Init, arg("size"))
|
||||
.def(
|
||||
"add",
|
||||
[](TabuList<SubsetIndex>& list, BaseInt t) -> void {
|
||||
return list.Add(SubsetIndex(t));
|
||||
},
|
||||
arg("t"))
|
||||
.def(
|
||||
"contains",
|
||||
[](TabuList<SubsetIndex>& list, BaseInt t) -> bool {
|
||||
return list.Contains(SubsetIndex(t));
|
||||
},
|
||||
arg("t"));
|
||||
|
||||
py::class_<GuidedTabuSearch>(m, "GuidedTabuSearch")
|
||||
.def(py::init<SetCoverInvariant*>())
|
||||
.def("initialize", &GuidedTabuSearch::Initialize)
|
||||
.def("next_solution",
|
||||
[](GuidedTabuSearch& heuristic, int num_iterations) -> bool {
|
||||
return heuristic.NextSolution(num_iterations);
|
||||
})
|
||||
.def("next_solution",
|
||||
[](GuidedTabuSearch& heuristic, absl::Span<const BaseInt> focus,
|
||||
int num_iterations) -> bool {
|
||||
return heuristic.NextSolution(VectorIntToVectorSubsetIndex(focus),
|
||||
num_iterations);
|
||||
})
|
||||
.def("get_lagrangian_factor", &GuidedTabuSearch::SetLagrangianFactor,
|
||||
arg("factor"))
|
||||
.def("set_lagrangian_factor", &GuidedTabuSearch::GetLagrangianFactor)
|
||||
.def("set_epsilon", &GuidedTabuSearch::SetEpsilon, arg("r"))
|
||||
.def("get_epsilon", &GuidedTabuSearch::GetEpsilon)
|
||||
.def("set_penalty_factor", &GuidedTabuSearch::SetPenaltyFactor,
|
||||
arg("factor"))
|
||||
.def("get_penalty_factor", &GuidedTabuSearch::GetPenaltyFactor)
|
||||
.def("set_tabu_list_size", &GuidedTabuSearch::SetTabuListSize,
|
||||
arg("size"))
|
||||
.def("get_tabu_list_size", &GuidedTabuSearch::GetTabuListSize);
|
||||
|
||||
m.def(
|
||||
"clear_random_subsets",
|
||||
[](BaseInt num_subsets, SetCoverInvariant* inv) -> std::vector<BaseInt> {
|
||||
const std::vector<SubsetIndex> cleared =
|
||||
ClearRandomSubsets(num_subsets, inv);
|
||||
return {cleared.begin(), cleared.end()};
|
||||
});
|
||||
m.def("clear_random_subsets",
|
||||
[](const std::vector<BaseInt>& focus, BaseInt num_subsets,
|
||||
SetCoverInvariant* inv) -> std::vector<BaseInt> {
|
||||
const std::vector<SubsetIndex> cleared = ClearRandomSubsets(
|
||||
VectorIntToVectorSubsetIndex(focus), num_subsets, inv);
|
||||
return {cleared.begin(), cleared.end()};
|
||||
});
|
||||
|
||||
m.def(
|
||||
"clear_most_covered_elements",
|
||||
[](BaseInt num_subsets, SetCoverInvariant* inv) -> std::vector<BaseInt> {
|
||||
const std::vector<SubsetIndex> cleared =
|
||||
ClearMostCoveredElements(num_subsets, inv);
|
||||
return {cleared.begin(), cleared.end()};
|
||||
});
|
||||
m.def("clear_most_covered_elements",
|
||||
[](absl::Span<const BaseInt> focus, BaseInt num_subsets,
|
||||
SetCoverInvariant* inv) -> std::vector<BaseInt> {
|
||||
const std::vector<SubsetIndex> cleared = ClearMostCoveredElements(
|
||||
VectorIntToVectorSubsetIndex(focus), num_subsets, inv);
|
||||
return {cleared.begin(), cleared.end()};
|
||||
});
|
||||
|
||||
// set_cover_reader.h
|
||||
m.def("read_orlib_scp", &ReadOrlibScp);
|
||||
m.def("read_orlib_rail", &ReadOrlibRail);
|
||||
m.def("read_fimi_dat", &ReadFimiDat);
|
||||
m.def("read_set_cover_proto", &ReadSetCoverProto);
|
||||
m.def("write_orlib_scp", &WriteOrlibScp);
|
||||
m.def("write_orlib_rail", &WriteOrlibRail);
|
||||
m.def("write_set_cover_proto", &WriteSetCoverProto);
|
||||
m.def("write_set_cover_solution_text", &WriteSetCoverSolutionText);
|
||||
m.def("write_set_cover_solution_proto", &WriteSetCoverSolutionProto);
|
||||
m.def("read_set_cover_solution_text", &ReadSetCoverSolutionText);
|
||||
m.def("read_set_cover_solution_proto", &ReadSetCoverSolutionProto);
|
||||
|
||||
// set_cover_lagrangian.h
|
||||
// TODO(user): add support for SetCoverLagrangian.
|
||||
}
|
||||
228
ortools/set_cover/python/set_cover_test.py
Normal file
228
ortools/set_cover/python/set_cover_test.py
Normal file
@@ -0,0 +1,228 @@
|
||||
#!/usr/bin/env python3
|
||||
# Copyright 2010-2025 Google LLC
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from absl import app
|
||||
from absl.testing import absltest
|
||||
|
||||
from ortools.set_cover.python import set_cover
|
||||
|
||||
|
||||
def create_initial_cover_model():
|
||||
model = set_cover.SetCoverModel()
|
||||
model.add_empty_subset(1.0)
|
||||
model.add_element_to_last_subset(0)
|
||||
model.add_empty_subset(1.0)
|
||||
model.add_element_to_last_subset(1)
|
||||
model.add_element_to_last_subset(2)
|
||||
model.add_empty_subset(1.0)
|
||||
model.add_element_to_last_subset(1)
|
||||
model.add_empty_subset(1.0)
|
||||
model.add_element_to_last_subset(2)
|
||||
return model
|
||||
|
||||
|
||||
def create_knights_cover_model(num_rows: int, num_cols: int) -> set_cover.SetCoverModel:
|
||||
model = set_cover.SetCoverModel()
|
||||
knight_row_move = [2, 1, -1, -2, -2, -1, 1, 2]
|
||||
knight_col_move = [1, 2, 2, 1, -1, -2, -2, -1]
|
||||
|
||||
for row in range(num_rows):
|
||||
for col in range(num_cols):
|
||||
model.add_empty_subset(1.0)
|
||||
model.add_element_to_last_subset(row * num_cols + col)
|
||||
|
||||
for i in range(8):
|
||||
new_row = row + knight_row_move[i]
|
||||
new_col = col + knight_col_move[i]
|
||||
if 0 <= new_row < num_rows and 0 <= new_col < num_cols:
|
||||
model.add_element_to_last_subset(new_row * num_cols + new_col)
|
||||
|
||||
return model
|
||||
|
||||
|
||||
# This test case is mostly a Python port of set_cover_test.cc.
|
||||
class SetCoverTest(absltest.TestCase):
|
||||
|
||||
def test_save_reload(self):
|
||||
model = create_knights_cover_model(10, 10)
|
||||
model.sort_elements_in_subsets()
|
||||
proto = model.export_model_as_proto()
|
||||
reloaded = set_cover.SetCoverModel()
|
||||
reloaded.import_model_from_proto(proto)
|
||||
|
||||
self.assertEqual(model.num_subsets, reloaded.num_subsets)
|
||||
self.assertEqual(model.num_elements, reloaded.num_elements)
|
||||
self.assertEqual(model.subset_costs, reloaded.subset_costs)
|
||||
self.assertEqual(model.columns, reloaded.columns)
|
||||
if model.row_view_is_valid and reloaded.row_view_is_valid:
|
||||
self.assertEqual(model.rows, reloaded.rows)
|
||||
|
||||
def test_save_reload_twice(self):
|
||||
model = create_knights_cover_model(3, 3)
|
||||
inv = set_cover.SetCoverInvariant(model)
|
||||
|
||||
greedy = set_cover.GreedySolutionGenerator(inv)
|
||||
self.assertTrue(greedy.next_solution())
|
||||
self.assertTrue(
|
||||
inv.check_consistency(set_cover.consistency_level.FREE_AND_UNCOVERED)
|
||||
)
|
||||
greedy_proto = inv.export_solution_as_proto()
|
||||
|
||||
steepest = set_cover.SteepestSearch(inv)
|
||||
self.assertTrue(steepest.next_solution(500))
|
||||
self.assertTrue(
|
||||
inv.check_consistency(set_cover.consistency_level.FREE_AND_UNCOVERED)
|
||||
)
|
||||
steepest_proto = inv.export_solution_as_proto()
|
||||
|
||||
inv.import_solution_from_proto(greedy_proto)
|
||||
self.assertTrue(steepest.next_solution(500))
|
||||
self.assertTrue(
|
||||
inv.check_consistency(set_cover.consistency_level.FREE_AND_UNCOVERED)
|
||||
)
|
||||
reloaded_proto = inv.export_solution_as_proto()
|
||||
self.assertEqual(str(steepest_proto), str(reloaded_proto))
|
||||
|
||||
def test_initial_values(self):
|
||||
model = create_initial_cover_model()
|
||||
self.assertTrue(model.compute_feasibility())
|
||||
|
||||
inv = set_cover.SetCoverInvariant(model)
|
||||
trivial = set_cover.TrivialSolutionGenerator(inv)
|
||||
self.assertTrue(trivial.next_solution())
|
||||
self.assertTrue(
|
||||
inv.check_consistency(set_cover.consistency_level.COST_AND_COVERAGE)
|
||||
)
|
||||
|
||||
greedy = set_cover.GreedySolutionGenerator(inv)
|
||||
self.assertTrue(greedy.next_solution())
|
||||
self.assertTrue(
|
||||
inv.check_consistency(set_cover.consistency_level.FREE_AND_UNCOVERED)
|
||||
)
|
||||
|
||||
self.assertEqual(inv.num_uncovered_elements(), 0)
|
||||
steepest = set_cover.SteepestSearch(inv)
|
||||
self.assertTrue(steepest.next_solution(500))
|
||||
self.assertTrue(
|
||||
inv.check_consistency(set_cover.consistency_level.COST_AND_COVERAGE)
|
||||
)
|
||||
|
||||
def test_infeasible(self):
|
||||
model = set_cover.SetCoverModel()
|
||||
model.add_empty_subset(1.0)
|
||||
model.add_element_to_last_subset(0)
|
||||
model.add_empty_subset(1.0)
|
||||
model.add_element_to_last_subset(3)
|
||||
self.assertFalse(model.compute_feasibility())
|
||||
|
||||
def test_knights_cover_creation(self):
|
||||
model = create_knights_cover_model(16, 16)
|
||||
self.assertTrue(model.compute_feasibility())
|
||||
|
||||
def test_knights_cover_greedy(self):
|
||||
model = create_knights_cover_model(16, 16)
|
||||
self.assertTrue(model.compute_feasibility())
|
||||
inv = set_cover.SetCoverInvariant(model)
|
||||
|
||||
greedy = set_cover.GreedySolutionGenerator(inv)
|
||||
self.assertTrue(greedy.next_solution())
|
||||
self.assertTrue(
|
||||
inv.check_consistency(set_cover.consistency_level.FREE_AND_UNCOVERED)
|
||||
)
|
||||
|
||||
steepest = set_cover.SteepestSearch(inv)
|
||||
self.assertTrue(steepest.next_solution(500))
|
||||
self.assertTrue(
|
||||
inv.check_consistency(set_cover.consistency_level.FREE_AND_UNCOVERED)
|
||||
)
|
||||
|
||||
def test_knights_cover_degree(self):
|
||||
model = create_knights_cover_model(16, 16)
|
||||
self.assertTrue(model.compute_feasibility())
|
||||
inv = set_cover.SetCoverInvariant(model)
|
||||
|
||||
degree = set_cover.ElementDegreeSolutionGenerator(inv)
|
||||
self.assertTrue(degree.next_solution())
|
||||
self.assertTrue(
|
||||
inv.check_consistency(set_cover.consistency_level.COST_AND_COVERAGE)
|
||||
)
|
||||
|
||||
steepest = set_cover.SteepestSearch(inv)
|
||||
self.assertTrue(steepest.next_solution(500))
|
||||
self.assertTrue(
|
||||
inv.check_consistency(set_cover.consistency_level.FREE_AND_UNCOVERED)
|
||||
)
|
||||
|
||||
def test_knights_cover_gls(self):
|
||||
model = create_knights_cover_model(16, 16)
|
||||
self.assertTrue(model.compute_feasibility())
|
||||
inv = set_cover.SetCoverInvariant(model)
|
||||
|
||||
greedy = set_cover.GreedySolutionGenerator(inv)
|
||||
self.assertTrue(greedy.next_solution())
|
||||
self.assertTrue(
|
||||
inv.check_consistency(set_cover.consistency_level.FREE_AND_UNCOVERED)
|
||||
)
|
||||
|
||||
gls = set_cover.GuidedLocalSearch(inv)
|
||||
self.assertTrue(gls.next_solution(500))
|
||||
self.assertTrue(
|
||||
inv.check_consistency(set_cover.consistency_level.FREE_AND_UNCOVERED)
|
||||
)
|
||||
|
||||
def test_knights_cover_random(self):
|
||||
model = create_knights_cover_model(16, 16)
|
||||
self.assertTrue(model.compute_feasibility())
|
||||
inv = set_cover.SetCoverInvariant(model)
|
||||
|
||||
random = set_cover.RandomSolutionGenerator(inv)
|
||||
self.assertTrue(random.next_solution())
|
||||
self.assertTrue(
|
||||
inv.check_consistency(set_cover.consistency_level.COST_AND_COVERAGE)
|
||||
)
|
||||
|
||||
steepest = set_cover.SteepestSearch(inv)
|
||||
self.assertTrue(steepest.next_solution(500))
|
||||
self.assertTrue(
|
||||
inv.check_consistency(set_cover.consistency_level.FREE_AND_UNCOVERED)
|
||||
)
|
||||
|
||||
def test_knights_cover_trivial(self):
|
||||
model = create_knights_cover_model(16, 16)
|
||||
self.assertTrue(model.compute_feasibility())
|
||||
inv = set_cover.SetCoverInvariant(model)
|
||||
|
||||
trivial = set_cover.TrivialSolutionGenerator(inv)
|
||||
self.assertTrue(trivial.next_solution())
|
||||
self.assertTrue(
|
||||
inv.check_consistency(set_cover.consistency_level.COST_AND_COVERAGE)
|
||||
)
|
||||
|
||||
steepest = set_cover.SteepestSearch(inv)
|
||||
self.assertTrue(steepest.next_solution(500))
|
||||
self.assertTrue(
|
||||
inv.check_consistency(set_cover.consistency_level.FREE_AND_UNCOVERED)
|
||||
)
|
||||
|
||||
# TODO(user): KnightsCoverGreedyAndTabu, KnightsCoverGreedyRandomClear,
|
||||
# KnightsCoverElementDegreeRandomClear, KnightsCoverRandomClearMip,
|
||||
# KnightsCoverMip
|
||||
|
||||
|
||||
def main(_):
|
||||
absltest.main()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
app.run(main)
|
||||
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 "$@"
|
||||
@@ -16,9 +16,9 @@
|
||||
#include <cstdlib>
|
||||
|
||||
#include "absl/log/log.h"
|
||||
#include "ortools/algorithms/set_cover_heuristics.h"
|
||||
#include "ortools/algorithms/set_cover_invariant.h"
|
||||
#include "ortools/algorithms/set_cover_model.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]
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ message SetCoverProto {
|
||||
optional double cost = 1;
|
||||
|
||||
// The list of elements in the subset.
|
||||
repeated int32 element = 2 [packed = true];
|
||||
repeated int64 element = 2 [packed = true];
|
||||
}
|
||||
|
||||
// The list of subsets in the model.
|
||||
@@ -66,10 +66,10 @@ message SetCoverSolutionResponse {
|
||||
|
||||
// The number of subsets that are selected in the solution. This is used
|
||||
// to decompress their indices below.
|
||||
optional int32 num_subsets = 2;
|
||||
optional int64 num_subsets = 2;
|
||||
|
||||
// The list of the subsets selected in the solution.
|
||||
repeated int32 subset = 3 [packed = true];
|
||||
repeated int64 subset = 3 [packed = true];
|
||||
|
||||
// The cost of the solution, as computed by the algorithm.
|
||||
optional double cost = 4;
|
||||
@@ -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>
|
||||
@@ -29,9 +29,10 @@
|
||||
#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/base_types.h"
|
||||
#include "ortools/set_cover/set_cover_invariant.h"
|
||||
#include "ortools/set_cover/set_cover_model.h"
|
||||
|
||||
namespace operations_research {
|
||||
|
||||
@@ -120,28 +121,47 @@ bool GreedySolutionGenerator::NextSolution(absl::Span<const SubsetIndex> focus,
|
||||
subset_priorities.push_back({priority, subset.value()});
|
||||
}
|
||||
}
|
||||
const SetCoverModel& model = *inv_->model();
|
||||
const BaseInt num_subsets = model.num_subsets();
|
||||
const SparseColumnView& colunms = model.columns();
|
||||
const SparseRowView& rows = model.rows();
|
||||
|
||||
// The priority queue maintains the maximum number of elements covered by unit
|
||||
// of cost. We chose 16 as the arity of the heap after some testing.
|
||||
// TODO(user): research more about the best value for Arity.
|
||||
AdjustableKAryHeap<float, SubsetIndex::ValueType, 16, true> pq(
|
||||
subset_priorities, inv_->model()->num_subsets());
|
||||
while (!pq.IsEmpty()) {
|
||||
subset_priorities, num_subsets);
|
||||
SubsetBoolVector subset_seen(num_subsets, false);
|
||||
std::vector<SubsetIndex> subsets_to_remove;
|
||||
subsets_to_remove.reserve(focus.size());
|
||||
while (!pq.IsEmpty() || inv_->num_uncovered_elements() > 0) {
|
||||
// LOG_EVERY_N_SEC(INFO, 5)
|
||||
// << "Queue size: " << pq.heap_size()
|
||||
// << ", #uncovered elements: " << inv_->num_uncovered_elements();
|
||||
const SubsetIndex best_subset(pq.TopIndex());
|
||||
pq.Pop();
|
||||
inv_->Select(best_subset, CL::kFreeAndUncovered);
|
||||
// NOMUTANTS -- reason, for C++
|
||||
if (inv_->num_uncovered_elements() == 0) break;
|
||||
for (IntersectingSubsetsIterator it(*inv_->model(), best_subset);
|
||||
!it.at_end(); ++it) {
|
||||
const SubsetIndex subset = *it;
|
||||
const BaseInt marginal_impact(inv_->num_free_elements()[subset]);
|
||||
if (marginal_impact > 0) {
|
||||
const float priority = marginal_impact / costs[subset];
|
||||
pq.Update({priority, subset.value()});
|
||||
} else {
|
||||
pq.Remove(subset.value());
|
||||
subset_seen[best_subset] = true;
|
||||
subsets_to_remove.push_back(best_subset);
|
||||
for (const ElementIndex element : colunms[best_subset]) {
|
||||
for (const SubsetIndex subset : rows[element]) {
|
||||
if (subset_seen[subset]) continue;
|
||||
subset_seen[subset] = true;
|
||||
const BaseInt marginal_impact(inv_->num_free_elements()[subset]);
|
||||
if (marginal_impact > 0) {
|
||||
const float priority = marginal_impact / costs[subset];
|
||||
pq.Update({priority, subset.value()});
|
||||
} else {
|
||||
pq.Remove(subset.value());
|
||||
}
|
||||
subsets_to_remove.push_back(subset);
|
||||
}
|
||||
}
|
||||
for (const SubsetIndex subset : subsets_to_remove) {
|
||||
subset_seen[subset] = false;
|
||||
}
|
||||
subsets_to_remove.clear();
|
||||
DVLOG(1) << "Cost = " << inv_->cost()
|
||||
<< " num_uncovered_elements = " << inv_->num_uncovered_elements();
|
||||
}
|
||||
@@ -686,7 +706,11 @@ bool GuidedTabuSearch::NextSolution(absl::Span<const SubsetIndex> focus,
|
||||
|
||||
UpdatePenalties(focus);
|
||||
tabu_list_.Add(best_subset);
|
||||
inv_->Flip(best_subset, CL::kFreeAndUncovered);
|
||||
if (inv_->is_selected()[best_subset]) {
|
||||
inv_->Deselect(best_subset, CL::kFreeAndUncovered);
|
||||
} else {
|
||||
inv_->Select(best_subset, CL::kFreeAndUncovered);
|
||||
}
|
||||
// TODO(user): make the cost computation incremental.
|
||||
augmented_cost =
|
||||
std::accumulate(augmented_costs_.begin(), augmented_costs_.end(), 0.0);
|
||||
@@ -695,9 +719,6 @@ bool GuidedTabuSearch::NextSolution(absl::Span<const SubsetIndex> focus,
|
||||
<< inv_->cost() << ", best cost = ," << best_cost
|
||||
<< ", penalized cost = ," << augmented_cost;
|
||||
if (inv_->cost() < best_cost) {
|
||||
LOG(INFO) << "Updated best cost, " << "Iteration, " << iteration
|
||||
<< ", current cost = ," << inv_->cost() << ", best cost = ,"
|
||||
<< best_cost << ", penalized cost = ," << augmented_cost;
|
||||
best_cost = inv_->cost();
|
||||
best_choices = inv_->is_selected();
|
||||
}
|
||||
@@ -754,17 +775,19 @@ bool GuidedLocalSearch::NextSolution(absl::Span<const SubsetIndex> focus,
|
||||
|
||||
for (int iteration = 0;
|
||||
!priority_heap_.IsEmpty() && iteration < num_iterations; ++iteration) {
|
||||
// Improve current solution respective to the current penalties.
|
||||
// Improve current solution respective to the current penalties by flipping
|
||||
// the best subset.
|
||||
const SubsetIndex best_subset(priority_heap_.TopIndex());
|
||||
if (inv_->is_selected()[best_subset]) {
|
||||
utility_heap_.Insert({0, best_subset.value()});
|
||||
inv_->Deselect(best_subset, CL::kRedundancy);
|
||||
} else {
|
||||
utility_heap_.Insert(
|
||||
{static_cast<float>(inv_->model()->subset_costs()[best_subset] /
|
||||
(1 + penalties_[best_subset])),
|
||||
best_subset.value()});
|
||||
inv_->Select(best_subset, CL::kRedundancy);
|
||||
}
|
||||
inv_->Flip(best_subset, CL::kRedundancy); // Flip the best subset.
|
||||
DCHECK(!utility_heap_.IsEmpty());
|
||||
|
||||
// Getting the subset with highest utility. utility_heap_ is not empty,
|
||||
@@ -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/base_types.h"
|
||||
#include "ortools/set_cover/set_cover_invariant.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>
|
||||
@@ -21,9 +21,10 @@
|
||||
#include "absl/log/check.h"
|
||||
#include "absl/log/log.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/base_types.h"
|
||||
#include "ortools/set_cover/set_cover_model.h"
|
||||
|
||||
namespace operations_research {
|
||||
|
||||
@@ -301,15 +302,6 @@ BaseInt SetCoverInvariant::ComputeNumFreeElements(SubsetIndex subset) const {
|
||||
return num_free_elements;
|
||||
}
|
||||
|
||||
void SetCoverInvariant::Flip(SubsetIndex subset,
|
||||
ConsistencyLevel target_consistency) {
|
||||
if (!is_selected_[subset]) {
|
||||
Select(subset, target_consistency);
|
||||
} else {
|
||||
Deselect(subset, target_consistency);
|
||||
}
|
||||
}
|
||||
|
||||
void SetCoverInvariant::Select(SubsetIndex subset,
|
||||
ConsistencyLevel target_consistency) {
|
||||
const bool update_redundancy_info = target_consistency >= CL::kRedundancy;
|
||||
@@ -11,16 +11,17 @@
|
||||
// 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/base_types.h"
|
||||
#include "ortools/set_cover/set_cover.pb.h"
|
||||
#include "ortools/set_cover/set_cover_model.h"
|
||||
|
||||
namespace operations_research {
|
||||
|
||||
@@ -179,17 +180,6 @@ class SetCoverInvariant {
|
||||
// Computes the number of free (uncovered) elements in the given subset.
|
||||
BaseInt ComputeNumFreeElements(SubsetIndex subset) const;
|
||||
|
||||
// Includes subset in the solution by setting is_selected_[subset] to true
|
||||
// without updating the invariant. Only updates the cost and the coverage.
|
||||
// TODO(user): Merge with Select. Introduce consistency levels and maybe split
|
||||
// the invariant into three.
|
||||
void SelectNoUpdate(SubsetIndex subset);
|
||||
|
||||
// Flips is_selected_[subset] to its negation, by calling Select or Deselect
|
||||
// depending on value. Updates the invariant incrementally to the given
|
||||
// consistency level.
|
||||
void Flip(SubsetIndex subset, ConsistencyLevel consistency);
|
||||
|
||||
// Includes subset in the solution by setting is_selected_[subset] to true
|
||||
// and incrementally updating the invariant to the given consistency level.
|
||||
void Select(SubsetIndex subset, ConsistencyLevel consistency);
|
||||
@@ -288,4 +278,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,10 @@
|
||||
#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/base_types.h"
|
||||
#include "ortools/set_cover/set_cover_invariant.h"
|
||||
#include "ortools/set_cover/set_cover_model.h"
|
||||
|
||||
namespace operations_research {
|
||||
|
||||
@@ -99,7 +100,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/base_types.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,7 +11,7 @@
|
||||
// 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>
|
||||
@@ -19,10 +19,11 @@
|
||||
#include "absl/log/check.h"
|
||||
#include "absl/log/log.h"
|
||||
#include "absl/types/span.h"
|
||||
#include "ortools/algorithms/set_cover_invariant.h"
|
||||
#include "ortools/algorithms/set_cover_model.h"
|
||||
#include "ortools/linear_solver/linear_solver.h"
|
||||
#include "ortools/lp_data/lp_types.h"
|
||||
#include "ortools/set_cover/base_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/base_types.h"
|
||||
#include "ortools/set_cover/set_cover_invariant.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>
|
||||
@@ -32,7 +32,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/set_cover/base_types.h"
|
||||
#include "ortools/set_cover/set_cover.pb.h"
|
||||
|
||||
namespace operations_research {
|
||||
|
||||
@@ -159,15 +160,10 @@ SetCoverModel SetCoverModel::GenerateRandomModelFrom(
|
||||
subset_already_contains_element[element] = false;
|
||||
}
|
||||
}
|
||||
LOG(INFO) << "Finished genreating the model with " << num_elements_covered
|
||||
<< " elements covered.";
|
||||
|
||||
// It can happen -- rarely in practice -- that some of the elements cannot be
|
||||
// covered. Let's add them to randomly chosen subsets.
|
||||
if (num_elements_covered != num_elements) {
|
||||
LOG(INFO) << "Generated model with " << num_elements - num_elements_covered
|
||||
<< " elements that cannot be covered. Adding them to random "
|
||||
"subsets.";
|
||||
SubsetBoolVector element_already_in_subset(num_subsets, false);
|
||||
for (ElementIndex element(0); element.value() < num_elements; ++element) {
|
||||
LOG_EVERY_N_SEC(INFO, 5) << absl::StrFormat(
|
||||
@@ -200,11 +196,7 @@ SetCoverModel SetCoverModel::GenerateRandomModelFrom(
|
||||
++num_elements_covered;
|
||||
}
|
||||
}
|
||||
LOG(INFO) << "Finished generating subsets for elements that were not "
|
||||
"covered in the original model.";
|
||||
}
|
||||
LOG(INFO) << "Finished generating the model. There are "
|
||||
<< num_elements - num_elements_covered << " uncovered elements.";
|
||||
|
||||
CHECK_EQ(num_elements_covered, num_elements);
|
||||
|
||||
@@ -237,6 +229,8 @@ void SetCoverModel::UpdateAllSubsetsList() {
|
||||
}
|
||||
|
||||
void SetCoverModel::AddEmptySubset(Cost cost) {
|
||||
// TODO(user): refine the logic for is_unicost_ and is_unicost_valid_.
|
||||
is_unicost_valid_ = false;
|
||||
elements_in_subsets_are_sorted_ = false;
|
||||
subset_costs_.push_back(cost);
|
||||
columns_.push_back(SparseColumn());
|
||||
@@ -262,6 +256,9 @@ void SetCoverModel::AddElementToLastSubset(ElementIndex element) {
|
||||
}
|
||||
|
||||
void SetCoverModel::SetSubsetCost(BaseInt subset, Cost cost) {
|
||||
// TODO(user): refine the logic for is_unicost_ and is_unicost_valid_.
|
||||
// NOMUTANTS -- this is a performance optimization.
|
||||
is_unicost_valid_ = false;
|
||||
elements_in_subsets_are_sorted_ = false;
|
||||
CHECK(std::isfinite(cost));
|
||||
DCHECK_GE(subset, 0);
|
||||
@@ -338,14 +335,14 @@ void SetCoverModel::CreateSparseRowView() {
|
||||
rows_.resize(num_elements_, SparseRow());
|
||||
ElementToIntVector row_sizes(num_elements_, 0);
|
||||
for (const SubsetIndex subset : SubsetRange()) {
|
||||
// Sort the columns. It's not super-critical to improve performance here
|
||||
// as this needs to be done only once.
|
||||
BaseInt* data = reinterpret_cast<BaseInt*>(columns_[subset].data());
|
||||
RadixSort(absl::MakeSpan(data, columns_[subset].size()));
|
||||
|
||||
ElementIndex preceding_element(-1);
|
||||
for (const ElementIndex element : columns_[subset]) {
|
||||
DCHECK_GT(element, preceding_element); // Fail if there is a repetition.
|
||||
CHECK_GT(element, preceding_element)
|
||||
<< "Repetition in column "
|
||||
<< subset; // Fail if there is a repetition.
|
||||
++row_sizes[element];
|
||||
preceding_element = element;
|
||||
}
|
||||
@@ -372,11 +369,14 @@ bool SetCoverModel::ComputeFeasibility() const {
|
||||
for (const Cost cost : subset_costs_) {
|
||||
CHECK_GT(cost, 0.0);
|
||||
}
|
||||
SubsetIndex column_index(0);
|
||||
for (const SparseColumn& column : columns_) {
|
||||
CHECK_GT(column.size(), 0);
|
||||
// DLOG_IF(INFO, column.empty()) << "Empty column " << column_index.value();
|
||||
for (const ElementIndex element : column) {
|
||||
++coverage[element];
|
||||
}
|
||||
// NOMUTANTS -- column_index is only used for logging in debug mode.
|
||||
++column_index;
|
||||
}
|
||||
for (const ElementIndex element : ElementRange()) {
|
||||
CHECK_GE(coverage[element], 0);
|
||||
@@ -409,7 +409,6 @@ SetCoverProto SetCoverModel::ExportModelAsProto() const {
|
||||
subset_proto->add_element(element.value());
|
||||
}
|
||||
}
|
||||
LOG(INFO) << "Finished exporting the model.";
|
||||
return message;
|
||||
}
|
||||
|
||||
@@ -497,15 +496,27 @@ class StatsAccumulator {
|
||||
} // namespace
|
||||
|
||||
template <typename T>
|
||||
SetCoverModel::Stats ComputeStats(std::vector<T> sizes) {
|
||||
SetCoverModel::Stats ComputeStats(std::vector<T> samples) {
|
||||
SetCoverModel::Stats stats;
|
||||
stats.min = *std::min_element(sizes.begin(), sizes.end());
|
||||
stats.max = *std::max_element(sizes.begin(), sizes.end());
|
||||
stats.mean = std::accumulate(sizes.begin(), sizes.end(), 0.0) / sizes.size();
|
||||
std::nth_element(sizes.begin(), sizes.begin() + sizes.size() / 2,
|
||||
sizes.end());
|
||||
stats.median = sizes[sizes.size() / 2];
|
||||
stats.stddev = StandardDeviation(sizes);
|
||||
stats.min = *std::min_element(samples.begin(), samples.end());
|
||||
stats.max = *std::max_element(samples.begin(), samples.end());
|
||||
stats.mean =
|
||||
std::accumulate(samples.begin(), samples.end(), 0.0) / samples.size();
|
||||
auto const q1 = samples.size() / 4;
|
||||
auto const q2 = samples.size() / 2;
|
||||
auto const q3 = q1 + q2;
|
||||
// The first call to nth_element is O(n). The 2nd and 3rd calls are O(n / 2).
|
||||
// Basically it's equivalent to running nth_element twice.
|
||||
// One should be tempted to use a faster sorting algorithm like radix sort,
|
||||
// it is not sure that we would gain a lot. There would be no gain in
|
||||
// complexity whatsoever anyway. On top of that, this code is called at most
|
||||
// one per problem, so it's not worth the effort.
|
||||
std::nth_element(samples.begin(), samples.begin() + q2, samples.end());
|
||||
std::nth_element(samples.begin(), samples.begin() + q1, samples.begin() + q2);
|
||||
std::nth_element(samples.begin() + q2, samples.begin() + q3, samples.end());
|
||||
stats.median = samples[samples.size() / 2];
|
||||
stats.iqr = samples[q3] - samples[q1];
|
||||
stats.stddev = StandardDeviation(samples);
|
||||
return stats;
|
||||
}
|
||||
|
||||
@@ -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,10 @@
|
||||
|
||||
#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/base_types.h"
|
||||
#include "ortools/set_cover/set_cover.pb.h"
|
||||
|
||||
// Representation class for the weighted set-covering problem.
|
||||
//
|
||||
@@ -50,55 +51,6 @@
|
||||
// cardinalities of all the subsets.
|
||||
|
||||
namespace operations_research {
|
||||
// Basic non-strict type for cost. The speed penalty for using double is ~2%.
|
||||
using Cost = double;
|
||||
|
||||
// Base non-strict integer type for counting elements and subsets.
|
||||
// Using ints makes it possible to represent problems with more than 2 billion
|
||||
// (2e9) elements and subsets. If need arises one day, BaseInt can be split
|
||||
// into SubsetBaseInt and ElementBaseInt.
|
||||
// Quick testing has shown a slowdown of about 20-25% when using int64_t.
|
||||
using BaseInt = int32_t;
|
||||
|
||||
// We make heavy use of strong typing to avoid obvious mistakes.
|
||||
// Subset index.
|
||||
DEFINE_STRONG_INT_TYPE(SubsetIndex, BaseInt);
|
||||
|
||||
// Element index.
|
||||
DEFINE_STRONG_INT_TYPE(ElementIndex, BaseInt);
|
||||
|
||||
// Position in a vector. The vector may either represent a column, i.e. a
|
||||
// subset with all its elements, or a row, i,e. the list of subsets which
|
||||
// contain a given element.
|
||||
DEFINE_STRONG_INT_TYPE(ColumnEntryIndex, BaseInt);
|
||||
DEFINE_STRONG_INT_TYPE(RowEntryIndex, BaseInt);
|
||||
|
||||
using SubsetRange = util_intops::StrongIntRange<SubsetIndex>;
|
||||
using ElementRange = util_intops::StrongIntRange<ElementIndex>;
|
||||
using ColumnEntryRange = util_intops::StrongIntRange<ColumnEntryIndex>;
|
||||
|
||||
using SubsetCostVector = util_intops::StrongVector<SubsetIndex, Cost>;
|
||||
using ElementCostVector = util_intops::StrongVector<ElementIndex, Cost>;
|
||||
|
||||
using SparseColumn = util_intops::StrongVector<ColumnEntryIndex, ElementIndex>;
|
||||
using SparseRow = util_intops::StrongVector<RowEntryIndex, SubsetIndex>;
|
||||
|
||||
using ElementToIntVector = util_intops::StrongVector<ElementIndex, BaseInt>;
|
||||
using SubsetToIntVector = util_intops::StrongVector<SubsetIndex, BaseInt>;
|
||||
|
||||
// Views of the sparse vectors. These need not be aligned as it's their contents
|
||||
// that need to be aligned.
|
||||
using SparseColumnView = util_intops::StrongVector<SubsetIndex, SparseColumn>;
|
||||
using SparseRowView = util_intops::StrongVector<ElementIndex, SparseRow>;
|
||||
|
||||
using SubsetBoolVector = util_intops::StrongVector<SubsetIndex, bool>;
|
||||
using ElementBoolVector = util_intops::StrongVector<ElementIndex, bool>;
|
||||
|
||||
// Useful for representing permutations,
|
||||
using ElementToElementVector =
|
||||
util_intops::StrongVector<ElementIndex, ElementIndex>;
|
||||
using SubsetToSubsetVector =
|
||||
util_intops::StrongVector<SubsetIndex, SubsetIndex>;
|
||||
|
||||
// Main class for describing a weighted set-covering problem.
|
||||
class SetCoverModel {
|
||||
@@ -111,6 +63,8 @@ class SetCoverModel {
|
||||
row_view_is_valid_(false),
|
||||
elements_in_subsets_are_sorted_(false),
|
||||
subset_costs_(),
|
||||
is_unicost_(true),
|
||||
is_unicost_valid_(false),
|
||||
columns_(),
|
||||
rows_(),
|
||||
all_subsets_() {}
|
||||
@@ -161,6 +115,23 @@ class SetCoverModel {
|
||||
// Vector of costs for each subset.
|
||||
const SubsetCostVector& subset_costs() const { return subset_costs_; }
|
||||
|
||||
// Returns true if all subset costs are equal to 1.0. This is a fast check
|
||||
// that is only valid if the subset costs are not modified.
|
||||
bool is_unicost() {
|
||||
if (is_unicost_valid_) {
|
||||
return is_unicost_;
|
||||
}
|
||||
is_unicost_ = true;
|
||||
for (const Cost cost : subset_costs_) {
|
||||
if (cost != 1.0) {
|
||||
is_unicost_ = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
is_unicost_valid_ = true;
|
||||
return is_unicost_;
|
||||
}
|
||||
|
||||
// Column view of the set covering problem.
|
||||
const SparseColumnView& columns() const { return columns_; }
|
||||
|
||||
@@ -244,10 +215,12 @@ class SetCoverModel {
|
||||
double median;
|
||||
double mean;
|
||||
double stddev;
|
||||
double iqr; // Interquartile range.
|
||||
|
||||
std::string DebugString() const {
|
||||
return absl::StrCat("min = ", min, ", max = ", max, ", mean = ", mean,
|
||||
", median = ", median, ", stddev = ", stddev, ", ");
|
||||
", median = ", median, ", stddev = ", stddev, ", ",
|
||||
"iqr = ", iqr);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -298,6 +271,12 @@ class SetCoverModel {
|
||||
// Costs for each subset.
|
||||
SubsetCostVector subset_costs_;
|
||||
|
||||
// True when all subset costs are equal to 1.0.
|
||||
bool is_unicost_;
|
||||
|
||||
// True when is_unicost_ is up-to-date.
|
||||
bool is_unicost_valid_;
|
||||
|
||||
// Vector of columns. Each column corresponds to a subset and contains the
|
||||
// elements of the given subset.
|
||||
// This takes NNZ (number of non-zeros) BaseInts, or |E| * |S| * fill_rate.
|
||||
@@ -340,7 +319,7 @@ class IntersectingSubsetsIterator {
|
||||
seed_subset_(seed_subset),
|
||||
model_(model),
|
||||
subset_seen_(model_.columns().size(), false) {
|
||||
CHECK(model_.row_view_is_valid());
|
||||
DCHECK(model_.row_view_is_valid());
|
||||
subset_seen_[seed_subset] = true; // Avoid iterating on `seed_subset`.
|
||||
++(*this); // Move to the first intersecting subset.
|
||||
}
|
||||
@@ -359,11 +338,12 @@ class IntersectingSubsetsIterator {
|
||||
DCHECK(!at_end());
|
||||
const SparseRowView& rows = model_.rows();
|
||||
const SparseColumn& column = model_.columns()[seed_subset_];
|
||||
for (; element_entry_ < ColumnEntryIndex(column.size()); ++element_entry_) {
|
||||
const ColumnEntryIndex column_size = ColumnEntryIndex(column.size());
|
||||
for (; element_entry_ < column_size; ++element_entry_) {
|
||||
const ElementIndex current_element = column[element_entry_];
|
||||
const SparseRow& current_row = rows[current_element];
|
||||
for (; subset_entry_ < RowEntryIndex(current_row.size());
|
||||
++subset_entry_) {
|
||||
const RowEntryIndex current_row_size = RowEntryIndex(current_row.size());
|
||||
for (; subset_entry_ < current_row_size; ++subset_entry_) {
|
||||
intersecting_subset_ = current_row[subset_entry_];
|
||||
if (!subset_seen_[intersecting_subset_]) {
|
||||
subset_seen_[intersecting_subset_] = true;
|
||||
@@ -398,4 +378,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>
|
||||
@@ -29,11 +30,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/options.h"
|
||||
#include "ortools/set_cover/base_types.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 +108,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 +151,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 +307,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 +328,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 +351,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 +369,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_
|
||||
@@ -21,12 +21,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/timer.h"
|
||||
#include "ortools/set_cover/base_types.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, "",
|
||||
@@ -20,13 +20,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/parse_text_proto.h"
|
||||
#include "ortools/set_cover/base_types.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