diff --git a/ortools/set_cover/python/set_cover.cc b/ortools/set_cover/python/set_cover.cc index 82d0b4e2f4..ce5d83032f 100644 --- a/ortools/set_cover/python/set_cover.cc +++ b/ortools/set_cover/python/set_cover.cc @@ -291,6 +291,9 @@ PYBIND11_MODULE(set_cover, m) { *invariant.model() = model; }) .def("cost", &SetCoverInvariant::cost) + .def("lower_bound", &SetCoverInvariant::LowerBound) + .def("cost_or_lower_bound", &SetCoverInvariant::CostOrLowerBound) + .def("is_cost_consistent", &SetCoverInvariant::is_cost_consistent) .def("num_uncovered_elements", &SetCoverInvariant::num_uncovered_elements) .def("is_selected", [](SetCoverInvariant& invariant) -> std::vector { diff --git a/ortools/set_cover/python/set_cover_test.py b/ortools/set_cover/python/set_cover_test.py index 220e2cee51..708777913d 100644 --- a/ortools/set_cover/python/set_cover_test.py +++ b/ortools/set_cover/python/set_cover_test.py @@ -222,6 +222,33 @@ def test_knights_cover_trivial(self): inv.check_consistency(set_cover.consistency_level.FREE_AND_UNCOVERED) ) + def test_dual_ascent_lower_bound(self): + model = create_knights_cover_model(8, 8) + self.assertTrue(model.compute_feasibility()) + + primal = set_cover.SetCoverInvariant(model) + self.assertTrue(set_cover.GreedySolutionOptimizer(primal).optimize()) + upper_bound = primal.cost() + + dual = set_cover.SetCoverInvariant(model) + self.assertTrue(set_cover.DualAscentOptimizer(dual).optimize()) + lower_bound = dual.lower_bound() + + # The dual ascent bound is a valid lower bound on the optimum, so it + # cannot exceed the cost of any feasible cover. + self.assertGreaterEqual(lower_bound, 0.0) + self.assertLessEqual(lower_bound, upper_bound) + + def test_cost_or_lower_bound(self): + model = create_knights_cover_model(4, 4) + inv = set_cover.SetCoverInvariant(model) + + self.assertTrue(set_cover.GreedySolutionOptimizer(inv).optimize()) + if inv.is_cost_consistent(): + self.assertEqual(inv.cost_or_lower_bound(), inv.cost()) + else: + self.assertEqual(inv.cost_or_lower_bound(), inv.lower_bound()) + # TODO(user): KnightsCoverGreedyAndTabu, KnightsCoverGreedyRandomClear, # KnightsCoverElementDegreeRandomClear, KnightsCoverRandomClearMip, # KnightsCoverMip