backport algorithms/ from main

This commit is contained in:
Corentin Le Molgat
2024-11-12 15:00:36 +01:00
parent d72ac65be6
commit bc4c73a786
16 changed files with 798 additions and 362 deletions

View File

@@ -37,6 +37,7 @@ using ::operations_research::ElementIndex;
using ::operations_research::GreedySolutionGenerator;
using ::operations_research::GuidedLocalSearch;
using ::operations_research::GuidedTabuSearch;
using ::operations_research::LazyElementDegreeSolutionGenerator;
using ::operations_research::Preprocessor;
using ::operations_research::RandomSolutionGenerator;
using ::operations_research::ReadBeasleySetCoverProblem;
@@ -127,44 +128,46 @@ PYBIND11_MODULE(set_cover, m) {
[](SetCoverModel& model) -> const std::vector<double>& {
return model.subset_costs().get();
})
.def("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;
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("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;
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("row_view_is_valid", &SetCoverModel::row_view_is_valid)
.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()),
@@ -242,11 +245,17 @@ PYBIND11_MODULE(set_cover, m) {
})
.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("recompute_invariant", &SetCoverInvariant::RecomputeInvariant)
.def("model", &SetCoverInvariant::model)
.def_property(
"model",
@@ -295,9 +304,10 @@ PYBIND11_MODULE(set_cover, m) {
.def("clear_trace", &SetCoverInvariant::ClearTrace)
.def("clear_removability_information",
&SetCoverInvariant::ClearRemovabilityInformation)
.def("new_removable_subsets", &SetCoverInvariant::new_removable_subsets)
.def("new_non_removable_subsets",
&SetCoverInvariant::new_non_removable_subsets)
.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,
@@ -312,43 +322,28 @@ PYBIND11_MODULE(set_cover, m) {
return invariant.ComputeIsRedundant(SubsetIndex(subset));
},
arg("subset"))
.def("make_fully_updated", &SetCoverInvariant::MakeFullyUpdated)
.def("recompute", &SetCoverInvariant::Recompute)
.def(
"flip",
[](SetCoverInvariant& invariant, BaseInt subset) {
invariant.Flip(SubsetIndex(subset));
[](SetCoverInvariant& invariant, BaseInt subset,
SetCoverInvariant::ConsistencyLevel consistency) {
invariant.Flip(SubsetIndex(subset), consistency);
},
arg("subset"))
.def(
"flip_and_fully_update",
[](SetCoverInvariant& invariant, BaseInt subset) {
invariant.FlipAndFullyUpdate(SubsetIndex(subset));
},
arg("subset"))
arg("subset"), arg("consistency"))
.def(
"select",
[](SetCoverInvariant& invariant, BaseInt subset) {
invariant.Select(SubsetIndex(subset));
[](SetCoverInvariant& invariant, BaseInt subset,
SetCoverInvariant::ConsistencyLevel consistency) {
invariant.Select(SubsetIndex(subset), consistency);
},
arg("subset"))
.def(
"select_and_fully_update",
[](SetCoverInvariant& invariant, BaseInt subset) {
invariant.SelectAndFullyUpdate(SubsetIndex(subset));
},
arg("subset"))
arg("subset"), arg("consistency"))
.def(
"deselect",
[](SetCoverInvariant& invariant, BaseInt subset) {
invariant.Deselect(SubsetIndex(subset));
[](SetCoverInvariant& invariant, BaseInt subset,
SetCoverInvariant::ConsistencyLevel consistency) {
invariant.Deselect(SubsetIndex(subset), consistency);
},
arg("subset"))
.def(
"deselect_and_fully_update",
[](SetCoverInvariant& invariant, BaseInt subset) {
invariant.DeselectAndFullyUpdate(SubsetIndex(subset));
},
arg("subset"))
arg("subset"), arg("consistency"))
.def("export_solution_as_proto",
&SetCoverInvariant::ExportSolutionAsProto)
.def("import_solution_from_proto",
@@ -434,6 +429,27 @@ PYBIND11_MODULE(set_cover, m) {
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,
const std::vector<BaseInt>& focus) -> bool {
return heuristic.NextSolution(VectorIntToVectorSubsetIndex(focus));
})
.def("next_solution",
[](LazyElementDegreeSolutionGenerator& heuristic,
const std::vector<BaseInt>& focus,
const std::vector<double>& costs) -> bool {
return heuristic.NextSolution(
VectorIntToVectorSubsetIndex(focus),
VectorDoubleToSubsetCostVector(costs));
});
py::class_<SteepestSearch>(m, "SteepestSearch")
.def(py::init<SetCoverInvariant*>())
.def("next_solution",

View File

@@ -62,9 +62,10 @@ class SetCoverTest(absltest.TestCase):
self.assertEqual(model.num_subsets, reloaded.num_subsets)
self.assertEqual(model.num_elements, reloaded.num_elements)
# TODO(user): these methods are not yet wrapped.
# self.assertEqual(model.subset_costs, reloaded.subset_costs)
# self.assertEqual(model.columns, reloaded.columns)
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)
@@ -72,17 +73,23 @@ class SetCoverTest(absltest.TestCase):
greedy = set_cover.GreedySolutionGenerator(inv)
self.assertTrue(greedy.next_solution())
self.assertTrue(inv.check_consistency())
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())
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())
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))
@@ -93,16 +100,22 @@ class SetCoverTest(absltest.TestCase):
inv = set_cover.SetCoverInvariant(model)
trivial = set_cover.TrivialSolutionGenerator(inv)
self.assertTrue(trivial.next_solution())
self.assertTrue(inv.check_consistency())
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())
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())
self.assertTrue(
inv.check_consistency(set_cover.consistency_level.COST_AND_COVERAGE)
)
def test_preprocessor(self):
model = create_initial_cover_model()
@@ -111,11 +124,15 @@ class SetCoverTest(absltest.TestCase):
inv = set_cover.SetCoverInvariant(model)
preprocessor = set_cover.Preprocessor(inv)
self.assertTrue(preprocessor.next_solution())
self.assertTrue(inv.check_consistency())
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())
self.assertTrue(
inv.check_consistency(set_cover.consistency_level.FREE_AND_UNCOVERED)
)
def test_infeasible(self):
model = set_cover.SetCoverModel()
@@ -136,11 +153,15 @@ class SetCoverTest(absltest.TestCase):
greedy = set_cover.GreedySolutionGenerator(inv)
self.assertTrue(greedy.next_solution())
self.assertTrue(inv.check_consistency())
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())
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)
@@ -149,11 +170,15 @@ class SetCoverTest(absltest.TestCase):
degree = set_cover.ElementDegreeSolutionGenerator(inv)
self.assertTrue(degree.next_solution())
self.assertTrue(inv.check_consistency())
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())
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)
@@ -162,11 +187,15 @@ class SetCoverTest(absltest.TestCase):
greedy = set_cover.GreedySolutionGenerator(inv)
self.assertTrue(greedy.next_solution())
self.assertTrue(inv.check_consistency())
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())
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)
@@ -175,11 +204,15 @@ class SetCoverTest(absltest.TestCase):
random = set_cover.RandomSolutionGenerator(inv)
self.assertTrue(random.next_solution())
self.assertTrue(inv.check_consistency())
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())
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)
@@ -188,11 +221,15 @@ class SetCoverTest(absltest.TestCase):
trivial = set_cover.TrivialSolutionGenerator(inv)
self.assertTrue(trivial.next_solution())
self.assertTrue(inv.check_consistency())
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())
self.assertTrue(
inv.check_consistency(set_cover.consistency_level.FREE_AND_UNCOVERED)
)
# TODO(user): KnightsCoverGreedyAndTabu, KnightsCoverGreedyRandomClear,
# KnightsCoverElementDegreeRandomClear, KnightsCoverRandomClearMip,