From caed3a353fa72c9e361f7b12071de2a5672d6dad Mon Sep 17 00:00:00 2001 From: Alexander Condello Date: Tue, 18 Oct 2022 12:28:35 -0700 Subject: [PATCH] Add handling discrete constraints to presolve --- dimod/include/dimod/constraint.h | 7 +++ dimod/include/dimod/expression.h | 10 +++++ dimod/include/dimod/presolve.h | 40 +++++++++++++++++ testscpp/tests/test_presolve.cpp | 74 ++++++++++++++++++++++++++++++++ 4 files changed, 131 insertions(+) diff --git a/dimod/include/dimod/constraint.h b/dimod/include/dimod/constraint.h index 7358d822e..98a07325f 100644 --- a/dimod/include/dimod/constraint.h +++ b/dimod/include/dimod/constraint.h @@ -91,6 +91,13 @@ bool Constraint::is_onehot() const { // must be linear and must have at least two variables if (!base_type::is_linear() || base_type::num_variables() < 2) return false; + // must be equality + if (sense_ != Sense::EQ) return false; + + // we could check the rhs vs offset, but let's treat it canonically as + // needing no offset + if (base_type::offset()) return false; + // all of out variables must be binary for (const auto& v : base_type::variables()) { if (base_type::vartype(v) != Vartype::BINARY) return false; diff --git a/dimod/include/dimod/expression.h b/dimod/include/dimod/expression.h index 0ed6f0ae2..2db4d7639 100644 --- a/dimod/include/dimod/expression.h +++ b/dimod/include/dimod/expression.h @@ -274,6 +274,8 @@ class Expression : public abc::QuadraticModelBase { /// Set the quadratic bias for the given variables. void set_quadratic(index_type u, index_type v, bias_type bias); + bool shares_variables(const Expression& other) const; + void substitute_variable(index_type v, bias_type multiplier, bias_type offset); /// Return the upper bound on variable ``v``. @@ -584,6 +586,14 @@ void Expression::set_quadratic(index_type u, index_type v base_type::set_quadratic(enforce_variable(u), enforce_variable(v), bias); } +template +bool Expression::shares_variables(const Expression& other) const { + for (auto& v : variables_) { + if (other.has_variable(v)) return true; // overlap! + } + return false; +} + template void Expression::substitute_variable(index_type v, bias_type multiplier, bias_type offset) { diff --git a/dimod/include/dimod/presolve.h b/dimod/include/dimod/presolve.h index c49c14339..6f877741d 100644 --- a/dimod/include/dimod/presolve.h +++ b/dimod/include/dimod/presolve.h @@ -295,6 +295,46 @@ void PreSolver::apply() { ++v; } } + + // Cleanup + + // *-- remove any invalid discrete markers + std::vector discrete; + for (size_type c = 0; c < model_.num_constraints(); ++c) { + auto& constraint = model_.constraint_ref(c); + + if (!constraint.marked_discrete()) continue; + + // we can check if it's well formed + if (constraint.is_onehot()) { + discrete.push_back(c); + } else { + constraint.mark_discrete(false); // if it's not one-hot, it's not discrete + } + } + // check if they overlap + size_type i = 0; + while (i < discrete.size()) { + // check if ci overlaps with any other constraints + auto& constraint = model_.constraint_ref(discrete[i]); + + bool overlap = false; + for (size_type j = i + 1; j < discrete.size(); ++j) { + if (model_.constraint_ref(discrete[j]).shares_variables(constraint)) { + // we have overlap! + overlap = true; + constraint.mark_discrete(false); + break; + } + } + + if (overlap) { + discrete.erase(discrete.begin() + i); + continue; + } + + ++i; + } } template diff --git a/testscpp/tests/test_presolve.cpp b/testscpp/tests/test_presolve.cpp index e08a4adae..8584cbb60 100644 --- a/testscpp/tests/test_presolve.cpp +++ b/testscpp/tests/test_presolve.cpp @@ -162,6 +162,80 @@ SCENARIO("constrained quadratic models can be presolved") { } } } + + GIVEN("a CQM with 5 binary variables") { + auto cqm = ConstrainedQuadraticModel(); + cqm.add_variables(Vartype::BINARY, 5); + + WHEN("we add a discrete constraint that has an offset but not a rhs") { + auto& c1 = cqm.constraint_ref(cqm.add_constraint()); + c1.set_linear(0, 1); + c1.set_linear(1, 1); + c1.set_offset(-1); + c1.mark_discrete(); + + AND_WHEN("we presolve is applied") { + auto presolver = presolve::PreSolver(std::move(cqm)); + presolver.load_default_presolvers(); + presolver.apply(); + + THEN("the constraint is still marked as discrete") { + REQUIRE(presolver.model().num_constraints() == 1); + CHECK(presolver.model().constraint_ref(0).marked_discrete()); + } + } + } + WHEN("we add a discrete constraint that has an offset and a rhs") { + auto& c1 = cqm.constraint_ref(cqm.add_constraint()); + c1.set_linear(0, 1); + c1.set_linear(1, 1); + c1.set_rhs(1); + c1.set_offset(-1); + c1.mark_discrete(); + + AND_WHEN("we presolve is applied") { + auto presolver = presolve::PreSolver(std::move(cqm)); + presolver.load_default_presolvers(); + presolver.apply(); + + THEN("the constraint is still marked as discrete") { + REQUIRE(presolver.model().num_constraints() == 1); + CHECK(!presolver.model().constraint_ref(0).marked_discrete()); + } + } + } + + WHEN("we add two overlapping discrete constraints and one non-overlapping") { + auto& c1 = cqm.constraint_ref(cqm.add_constraint()); + c1.set_linear(0, 1); + c1.set_linear(1, 1); + c1.set_rhs(1); + c1.mark_discrete(); + auto& c2 = cqm.constraint_ref(cqm.add_constraint()); + c2.set_linear(2, 1); + c2.set_linear(1, 1); + c2.set_rhs(1); + c2.mark_discrete(); + auto& c3 = cqm.constraint_ref(cqm.add_constraint()); + c3.set_linear(3, 1); + c3.set_linear(4, 1); + c3.set_rhs(1); + c3.mark_discrete(); + + AND_WHEN("we presolve is applied") { + auto presolver = presolve::PreSolver(std::move(cqm)); + presolver.load_default_presolvers(); + presolver.apply(); + + THEN("two will still be marked as discrete") { + REQUIRE(presolver.model().num_constraints() == 3); + CHECK(presolver.model().constraint_ref(0).marked_discrete() != + presolver.model().constraint_ref(1).marked_discrete()); + CHECK(presolver.model().constraint_ref(2).marked_discrete()); + } + } + } + } } } // namespace dimod