Skip to content

Commit

Permalink
Merge pull request #87 from c4v4/deploypypi
Browse files Browse the repository at this point in the history
Getting it running on Windows
  • Loading branch information
c4v4 authored Mar 5, 2025
2 parents 4059b3f + 555ba5a commit 6cb63d0
Show file tree
Hide file tree
Showing 17 changed files with 89 additions and 133 deletions.
50 changes: 31 additions & 19 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -13,52 +13,64 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
FetchContent_Declare(fmt GIT_REPOSITORY https://github.com/fmtlib/fmt)
FetchContent_MakeAvailable(fmt)

set(WARNING_FLAGS "-Wall -Wextra -Wpedantic -Wuninitialized -Wshadow -Wnull-dereference -Winit-self -Wunused-macros -Wwrite-strings -Wextra-semi")

set(SANITIZERS_FLAGS "-fno-omit-frame-pointer -fsanitize=address -fsanitize-address-use-after-scope -fsanitize=undefined")
set(OPT_FLAGS "-O3 -flto=auto")
if (MSVC)
#set(WARNING_FLAGS "/W4 /WX") # Treating all warnings as errors
set(WARNING_FLAGS "/W4") # Highest warning level but not treating warnings as errors
set(SANITIZERS_FLAGS "")
set(OPT_FLAGS "/O2")
else()
set(WARNING_FLAGS "-Wall -Wextra -Wpedantic -Wuninitialized -Wshadow -Wnull-dereference -Winit-self -Wunused-macros -Wwrite-strings -Wextra-semi")
set(SANITIZERS_FLAGS "-fno-omit-frame-pointer -fsanitize=address -fsanitize-address-use-after-scope -fsanitize=undefined")
set(OPT_FLAGS "-O3 -flto=auto")
endif()

set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${WARNING_FLAGS}")
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} ${SANITIZERS_FLAGS}")
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} ${OPT_FLAGS}")

set(SOURCE src/main.cpp)
set(SOURCE src/main.cpp)
include_directories(src)
add_executable(accft ${SOURCE})
set(LIBRARIES fmt::fmt pthread dl m)

if (WIN32)
set(LIBRARIES fmt::fmt)
else()
set(LIBRARIES fmt::fmt pthread dl m)
endif()

target_link_libraries(accft PUBLIC ${LIBRARIES})

if (SKBUILD)
set(PYTHON_BINDINGS ON)
set(PYTHON_BINDINGS ON)
endif()

if (PYTHON_BINDINGS)
message(STATUS "PYTHON_BINDINGS: ${PYTHON_BINDINGS}")
message(STATUS "PYTHON_BINDINGS: ${PYTHON_BINDINGS}")

# PyBind11
FetchContent_Declare(pybind11 GIT_REPOSITORY https://github.com/pybind/pybind11.git GIT_TAG v2.13.6)
FetchContent_MakeAvailable(pybind11) # pybind11, essential
set(CMAKE_POSITION_INDEPENDENT_CODE ON) # The code needs to be compiled as PIC
# to build the shared lib for python.
set(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE)
# Ensure fmt is compiled with -fPIC
set_target_properties(fmt PROPERTIES POSITION_INDEPENDENT_CODE ON)
# PyBind11
FetchContent_Declare(pybind11 GIT_REPOSITORY https://github.com/pybind/pybind11.git GIT_TAG v2.13.6)
FetchContent_MakeAvailable(pybind11) # pybind11, essential
set(CMAKE_POSITION_INDEPENDENT_CODE ON) # The code needs to be compiled as PIC
# to build the shared lib for python.
set(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE)
# Ensure fmt is compiled with -fPIC
set_target_properties(fmt PROPERTIES POSITION_INDEPENDENT_CODE ON)

pybind11_add_module(_bindings ./src/pycft/_bindings.cpp)
target_link_libraries(_bindings PUBLIC fmt::fmt ${LIBRARIES})
# enable compilation warnings
target_compile_options(
_bindings PRIVATE "$<$<CXX_COMPILER_ID:GNU,Clang,AppleClang>:-Wall>")
target_compile_definitions(_bindings PRIVATE PYBIND11_DETAILED_ERROR_MESSAGES)
install(TARGETS _bindings DESTINATION ./src/pycft/)
install(TARGETS _bindings DESTINATION src/pycft/)
endif()

########################################
############## Unit tests ##############
########################################
option(UNIT_TESTS "Build unit tests." OFF)
message(STATUS "UNIT_TESTS: ${UNIT_TESTS}")
if (UNIT_TESTS)
if (UNIT_TESTS)
enable_testing()
add_subdirectory(test)
endif()
endif()
10 changes: 8 additions & 2 deletions README.py.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,22 @@ SPDX-License-Identifier: MIT

# Python Bindings for the AC-CFT Set Cover Heuristic

Implementation of the Caprara, Fischetti, and Toth algorithm for the [Set Covering problem](https://en.wikipedia.org/wiki/Set_cover_problem).
The original code is written in C++ and can be found [here](https://github.com/c4v4/cft).
This Python-packages wraps the C++ code using [pybind11](https://github.com/pybind/pybind11) and provides a simple interface to solve set cover instances.

*Caprara, A., Fischetti, M., & Toth, P. (1999). A Heuristic Method for the Set Covering Problem. Operations Research, 47(5), 730–743. [doi:10.1287/opre.47.5.730](https://doi.org/10.1287/opre.47.5.730)*

## Install

We will publish the package on PyPI soon. For now, you can install the package by cloning the repository and running the following command in the root directory:
The package can be installed via pip:

```bash
pip install --verbose .
pip install pycft
```

It should be precompiled for Windows, Linux, and MacOS. If not precompiled version is available, it should be able to compile itself if a C++-compiler is available on the system.

## Usage

To use the `SetCoverSolver`, first create an instance of the solver. You can then add sets with their respective costs and solve the set cover problem. The solver will find the optimal selection of sets that covers all elements at the minimum cost.
Expand Down
6 changes: 3 additions & 3 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ def readme():

setup( # https://scikit-build.readthedocs.io/en/latest/usage.html#setup-options
name="pycft",
version="0.0.1",
author="Francesco Cavaliere and Dominik Krupke",
license="LICENSE",
version="1.0.0",
author="Luca Accorsi, Francesco Cavaliere, and Dominik Krupke",
license="MIT",
description="Python-Bindings for the C++-based Set Cover Algorithm CFT.",
long_description=readme(),
long_description_content_type="text/markdown",
Expand Down
4 changes: 2 additions & 2 deletions src/algorithms/Refinement.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ namespace local { namespace {
) {
ridx_t const nrows = rsize(inst.rows);

fix_fraction = min(1.0_F, fix_fraction * env.alpha);
fix_fraction = std::min(1.0_F, fix_fraction * env.alpha);
if (best_sol.cost < prev_cost)
fix_fraction = env.min_fixing;
prev_cost = best_sol.cost;
Expand All @@ -67,7 +67,7 @@ namespace local { namespace {
gap_contrib += best_lagr_mult[i] * (cov - 1.0_F) / cov;
reduced_cost -= best_lagr_mult[i];
}
gap_contrib += max(reduced_cost, 0.0_F);
gap_contrib += std::max(reduced_cost, 0.0_F);
gap_contributions.push_back({j, gap_contrib});
}
cft::sort(gap_contributions, [](CidxAndCost c) { return c.cost; });
Expand Down
8 changes: 4 additions & 4 deletions src/algorithms/ThreePhase.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ class ThreePhase {
for (ridx_t i = 0_R; i < rsize(inst.rows); ++i)
for (cidx_t j : inst.rows[i]) {
real_t candidate = inst.costs[j] / as_real(inst.cols[j].size());
lagr_mult[i] = cft::min(lagr_mult[i], candidate);
lagr_mult[i] = std::min(lagr_mult[i], candidate);
}
}

Expand All @@ -153,16 +153,16 @@ class ThreePhase {
static void _build_tentative_core_instance(Instance const& inst, // in
InstAndMap& core_inst // out
) {
static constexpr cidx_t min_row_coverage = 5_C;
static constexpr size_t min_row_coverage = 5;
ridx_t const nrows = rsize(inst.rows);

clear_inst(core_inst.inst);
core_inst.col_map.clear();

// Select the first n columns of each row (there might be duplicates)
core_inst.col_map.reserve(checked_cast<size_t>(as_cidx(nrows) * min_row_coverage));
core_inst.col_map.reserve(checked_cast<size_t>(nrows) * min_row_coverage);
for (auto const& row : inst.rows)
for (size_t n = 0; n < min(row.size(), min_row_coverage); ++n) {
for (size_t n = 0; n < std::min(row.size(), min_row_coverage); ++n) {
cidx_t j = row[n]; // column covering row i
core_inst.col_map.push_back(j);
}
Expand Down
2 changes: 1 addition & 1 deletion src/core/CliArgs.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ inline Environment parse_cli_args(int argc, char const** argv) {
auto args = cft::make_span(argv, checked_cast<size_t>(argc));
auto env = Environment{};

auto asize = size(args);
auto asize = args.size();
for (size_t a = 1; a < asize; ++a) {
auto arg = StringView(args[a]);
if (CFT_FLAG_MATCH(arg, HELP))
Expand Down
4 changes: 2 additions & 2 deletions src/core/cft.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -91,14 +91,14 @@ constexpr real_t operator""_F(long double f) {
// container as cidx_t. It also allows to avoid narrowing conversion warnings.
template <typename Cont>
inline cidx_t csize(Cont const& cont) {
return as_cidx(cft::size(cont));
return as_cidx(cont.size());
}

// Since ridx_t could be any integer type, rsize provide a (debug) checked way to get the size of a
// container as ridx_t. It also allows to avoid narrowing conversion warnings.
template <typename Cont>
inline ridx_t rsize(Cont const& cont) {
return as_ridx(cft::size(cont));
return as_ridx(cont.size());
}

// Simple pair of column index and cost of some sort
Expand Down
2 changes: 1 addition & 1 deletion src/core/parsing.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -289,7 +289,7 @@ inline FileData parse_inst_and_initsol(Environment const& env) {

if (env.use_unit_costs) {
fdata.inst.costs.assign(csize(fdata.inst.costs), 1.0_F);
fdata.init_sol.cost = as_real(size(fdata.init_sol.idxs));
fdata.init_sol.cost = as_real(fdata.init_sol.idxs.size());
}

print<1>(env, "CFT> Instance size: {} x {}.\n", rsize(fdata.inst.rows), csize(fdata.inst.cols));
Expand Down
2 changes: 1 addition & 1 deletion src/fixing/ColFixing.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ class ColFixing {
_select_non_overlapping_cols(inst, lagr_mult, row_coverage, cols_to_fix, reduced_costs);
cidx_t no_overlap_ncols = csize(cols_to_fix);

cidx_t fix_at_least = csize(cols_to_fix) + max(1_C, as_cidx(orig_nrows / 200_R));
cidx_t fix_at_least = csize(cols_to_fix) + std::max(1_C, as_cidx(orig_nrows / 200_R));
greedy(inst, lagr_mult, reduced_costs, cols_to_fix, limits<real_t>::max(), fix_at_least);

fix_columns_and_compute_maps(cols_to_fix, inst, fixing, old2new);
Expand Down
3 changes: 2 additions & 1 deletion src/greedy/Greedy.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,8 @@ class Greedy {

// Get the column-fraction with best scores
if (good_scores.empty()) {
auto good_size = min(as_cidx(nrows_to_cover), csize(inst.cols) - csize(sol));
cidx_t good_size = csize(inst.cols) - csize(sol);
good_size = std::min(good_size, as_cidx(nrows_to_cover));
good_scores = select_good_scores(score_info, good_size);
worst_good_score = good_scores.back().score;
}
Expand Down
14 changes: 7 additions & 7 deletions src/subgradient/Subgradient.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,19 +40,19 @@ class Subgradient {
real_t& step_size, // inout
std::vector<real_t>& best_lagr_mult // inout
) {
size_t const nrows = size(orig_inst.rows);
size_t const nrows = orig_inst.rows.size();
real_t const max_real_lb = cutoff - env.epsilon;

assert(!orig_inst.cols.empty() && "Empty instance");
assert(!core.inst.cols.empty() && "Empty core instance");
assert(nrows == size(core.inst.rows) && "Incompatible instances");
assert(nrows == core.inst.rows.size() && "Incompatible instances");

auto timer = Chrono<>();
auto next_step_size = local::StepSizeManager(20, step_size);
auto should_exit = local::ExitConditionManager(300);
auto should_price = local::PricingManager(10ULL, min(1000ULL, nrows / 3ULL));
real_t best_core_lb = limits<real_t>::min();
auto best_real_lb = limits<real_t>::min();
auto should_price = local::PricingManager(10ULL, std::min<size_t>(1000ULL, nrows / 3ULL));
real_t best_core_lb = limits<real_t>::min();
auto best_real_lb = limits<real_t>::min();
_reset_lower_bounds(lb_sol, best_core_lb);
lagr_mult = best_lagr_mult;

Expand Down Expand Up @@ -95,7 +95,7 @@ class Subgradient {
best_core_lb,
step_size);

best_real_lb = max(best_real_lb, real_lb);
best_real_lb = std::max(best_real_lb, real_lb);
_reset_lower_bounds(lb_sol, best_core_lb);

if (env.timer.elapsed<sec>() > env.time_limit)
Expand Down Expand Up @@ -244,4 +244,4 @@ class Subgradient {
} // namespace cft


#endif /* CFT_SRC_SUBGRADIENT_SUBGRADIENT_HPP */
#endif /* CFT_SRC_SUBGRADIENT_SUBGRADIENT_HPP */
10 changes: 5 additions & 5 deletions src/subgradient/utils.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ namespace cft { namespace local { namespace {

// Computes the next step size.
real_t operator()(size_t iter, real_t lower_bound) {
min_lower_bound = min(min_lower_bound, lower_bound);
max_lower_bound = max(max_lower_bound, lower_bound);
min_lower_bound = std::min(min_lower_bound, lower_bound);
max_lower_bound = std::max(max_lower_bound, lower_bound);
if (iter == next_update_iter) {
next_update_iter += period;
real_t diff = (max_lower_bound - min_lower_bound) / abs(max_lower_bound);
Expand Down Expand Up @@ -101,11 +101,11 @@ namespace cft { namespace local { namespace {
void update(real_t core_lb, real_t real_lb, real_t ub) {
real_t const delta = (core_lb - real_lb) / ub;
if (delta <= 1e-6_F)
period = min(max_period_increment, 10 * period);
period = std::min(max_period_increment, 10 * period);
else if (delta <= 0.02_F)
period = min(max_period_increment, 5 * period);
period = std::min(max_period_increment, 5 * period);
else if (delta <= 0.2_F)
period = min(max_period_increment, 2 * period);
period = std::min(max_period_increment, 2 * period);
else
period = 10;

Expand Down
2 changes: 1 addition & 1 deletion src/utils/StringView.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ struct StringView {
}

int compare(StringView other) const {
size_t min_size = cft::min(size(), other.size());
size_t min_size = std::min(size(), other.size());
for (size_t n = 0; n < min_size; ++n) {
if ((*this)[n] < other[n])
return -1;
Expand Down
42 changes: 4 additions & 38 deletions src/utils/utility.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -75,48 +75,14 @@ T abs(T val) {
return val < T{} ? -val : val;
}

/// Multi-arg max. NOTE: to avoid ambiguity, return type is always the first argument type
template <typename T1, typename T2>
constexpr T1 max(T1 v1, T2 v2) {
return v1 > checked_cast<T1>(v2) ? v1 : checked_cast<T1>(v2);
}

template <typename T1, typename T2, typename... Ts>
T1 max(T1 v1, T2 v2, Ts... tail) {
T1 mtail = max<T1>(v2, tail...);
return (v1 >= mtail ? v1 : mtail);
}

/// Multi-arg min. NOTE: to avoid ambiguity, return type is always the first argument type
template <typename T1, typename T2>
constexpr T1 min(T1 v1, T2 v2) {
return v1 < checked_cast<T1>(v2) ? v1 : checked_cast<T1>(v2);
}

template <typename T1, typename T2, typename... Ts>
T1 min(T1 v1, T2 v2, Ts... tail) {
T1 mtail = min<T1>(v2, tail...);
return v1 <= mtail ? v1 : mtail;
}

///////////// RANGES STUFF /////////////

template <typename C>
size_t size(C const& container) {
return container.size();
}

template <typename C, size_t N>
constexpr size_t size(C const (& /*unused*/)[N]) {
return N;
}

template <typename C>
using container_iterator_t = decltype(std::begin(std::declval<C&>()));
template <typename C>
using container_value_type_t = no_cvr<decltype(*std::declval<container_iterator_t<C>>())>;
template <typename C>
using container_size_type_t = decltype(cft::size(std::declval<C>()));
using container_size_type_t = decltype(std::declval<C>().size());

// Condition test operations
template <typename T, typename O>
Expand All @@ -138,9 +104,9 @@ bool all(T const& container, O op) {
// Return minimum value of a non-empty range
template <typename C, typename K = IdentityFtor>
container_value_type_t<C> range_min(C const& container, K key = {}) {
assert(cft::size(container) > 0ULL);
assert(container.size() > 0ULL);
auto min_elem = container[0];
for (size_t i = 1ULL; i < cft::size(container); ++i)
for (size_t i = 1ULL; i < container.size(); ++i)
if (key(container[i]) < key(min_elem))
min_elem = container[i];
return min_elem;
Expand All @@ -150,7 +116,7 @@ container_value_type_t<C> range_min(C const& container, K key = {}) {
template <typename C, typename Op>
void remove_if(C& container, Op op) {
size_t w = 0;
for (size_t i = 0ULL; i < cft::size(container); ++i)
for (size_t i = 0ULL; i < container.size(); ++i)
if (!op(container[i]))
container[w++] = container[i];
container.resize(w);
Expand Down
Loading

0 comments on commit 6cb63d0

Please sign in to comment.