From 016539cd61dfd777884517dc154798905145385f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 19 Sep 2025 21:27:53 +0000 Subject: [PATCH] Implement max PB constraints optimization feature Co-authored-by: NikolajBjorner <3085284+NikolajBjorner@users.noreply.github.com> --- src/opt/opt_context.cpp | 141 ++++++++++++++++++++++++++++++++++++++- src/opt/opt_context.h | 3 + test_pb_max.smt2 | 10 +++ test_pb_optimization | Bin 0 -> 66896 bytes test_pb_optimization.cpp | 104 +++++++++++++++++++++++++++++ 5 files changed, 257 insertions(+), 1 deletion(-) create mode 100644 test_pb_max.smt2 create mode 100755 test_pb_optimization create mode 100644 test_pb_optimization.cpp diff --git a/src/opt/opt_context.cpp b/src/opt/opt_context.cpp index 217fdaa26..6c6145ed4 100644 --- a/src/opt/opt_context.cpp +++ b/src/opt/opt_context.cpp @@ -345,6 +345,21 @@ namespace opt { m_optsmt.setup(*m_opt_solver.get()); update_lower(); + // Try to detect and optimize max PB constraints pattern before deciding optimization strategy + if (m_objectives.size() > 1) { + expr_ref_vector maxsat_terms(m); + vector maxsat_weights; + rational maxsat_offset; + bool maxsat_neg; + symbol maxsat_id; + + if (detect_max_pb_constraints(maxsat_terms, maxsat_weights, maxsat_offset, maxsat_neg, maxsat_id)) { + TRACE(opt, tout << "Max PB pattern detected, switching to single maxsat objective\n";); + // The pattern was detected and objectives were modified by detect_max_pb_constraints + // Now we have a single objective, so we can process it as such + } + } + switch (m_objectives.size()) { case 0: break; @@ -1077,12 +1092,135 @@ namespace opt { neg = is_max; std::ostringstream out; out << mk_bounded_pp(orig_term, m, 2) << ':' << index; - id = symbol(out.str()); return true; } return false; } + bool context::detect_max_pb_constraints(expr_ref_vector& terms, + vector& weights, rational& offset, + bool& neg, symbol& id) { + // This function detects when we have multiple maximize objectives that are PB constraints + // and transforms them to use auxiliary variables. + // + // Pattern: maximize { PB1, ..., PBn } where each PB_i is a weighted sum of Boolean variables + // Transform to: PB_i >= k => p_i_k for k = 1, ..., max_potential_sum(PB_i) + // maximize sum(p_i_k for all i,k) + + TRACE(opt, tout << "detect_max_pb_constraints called with " << m_objectives.size() << " objectives\n";); + + unsigned num_maximize_objs = 0; + vector max_indices; + vector pb_terms; + vector> pb_weights; + vector pb_offsets; + + // First pass: collect all maximize objectives that are PB constraints + for (unsigned idx = 0; idx < m_objectives.size(); ++idx) { + objective const& obj = m_objectives[idx]; + TRACE(opt, tout << "Objective " << idx << " type: " << obj.m_type << "\n";); + if (obj.m_type == O_MAXIMIZE && obj.m_term) { + expr_ref_vector pb_terms_i(m); + vector pb_weights_i; + rational pb_offset_i; + + if (get_pb_sum(obj.m_term, pb_terms_i, pb_weights_i, pb_offset_i)) { + max_indices.push_back(idx); + pb_terms.push_back(pb_terms_i); + pb_weights.push_back(pb_weights_i); + pb_offsets.push_back(pb_offset_i); + num_maximize_objs++; + TRACE(opt, tout << "Objective " << idx << " is a PB constraint with " << pb_terms_i.size() << " terms\n";); + } + } + } + + TRACE(opt, tout << "Found " << num_maximize_objs << " PB maximize objectives\n";); + + // Only apply transformation if we have multiple PB maximize objectives + if (num_maximize_objs < 2) { + return false; + } + + TRACE(opt, tout << "Detected " << num_maximize_objs << " PB maximize objectives, applying transformation\n";); + + // Clear output vectors + terms.reset(); + weights.reset(); + offset = rational::zero(); + neg = true; // We negate because we convert maximize to maxsat (minimizing penalty) + + arith_util arith(m); + + // For each PB constraint, create auxiliary variables and constraints + for (unsigned i = 0; i < num_maximize_objs; ++i) { + expr_ref_vector const& pb_terms_i = pb_terms[i]; + vector const& pb_weights_i = pb_weights[i]; + rational const& pb_offset_i = pb_offsets[i]; + + // Calculate maximum potential sum for this PB constraint + rational max_sum = pb_offset_i; + for (unsigned j = 0; j < pb_weights_i.size(); ++j) { + if (pb_weights_i[j].is_pos()) { + max_sum += pb_weights_i[j]; + } + } + + TRACE(opt, tout << "PB constraint " << i << " has max sum " << max_sum << "\n";); + + // Create auxiliary variables p_i_k for k = 1 to max_sum + for (rational k = rational::one(); k <= max_sum; k += rational::one()) { + std::ostringstream aux_name; + aux_name << "pb_aux_" << i << "_" << k.to_string(); + expr_ref aux_var(m.mk_const(symbol(aux_name.str()), m.mk_bool_sort()), m); + + // Create PB constraint: sum(pb_weights_i[j] * pb_terms_i[j]) for j + expr_ref pb_sum(arith.mk_numeral(pb_offset_i, true), m); + for (unsigned j = 0; j < pb_terms_i.size(); ++j) { + expr_ref weighted_term(arith.mk_mul(arith.mk_numeral(pb_weights_i[j], true), + m.mk_ite(pb_terms_i[j], arith.mk_numeral(rational::one(), true), arith.mk_numeral(rational::zero(), true))), m); + pb_sum = arith.mk_add(pb_sum, weighted_term); + } + + // Add constraint: pb_sum >= k => aux_var + expr_ref constraint(m.mk_implies(arith.mk_ge(pb_sum, arith.mk_numeral(k, true)), aux_var), m); + add_hard_constraint(constraint); + + // Add auxiliary variable to maximize + terms.push_back(aux_var); + weights.push_back(rational::one()); + offset += rational::one(); + } + } + + // Now modify m_objectives to replace the multiple maximize objectives with a single maxsat objective + // Find the first maximize objective to replace + unsigned target_index = max_indices[0]; + objective& obj = m_objectives[target_index]; + obj.m_id = symbol("max_pb_unified"); + obj.m_type = O_MAXSMT; + obj.m_term = nullptr; + obj.m_terms.reset(); + obj.m_terms.append(terms); + obj.m_weights.reset(); + obj.m_weights.append(weights); + obj.m_adjust_value.set_offset(offset); + obj.m_adjust_value.set_negate(neg); + + // Remove other maximize objectives that were converted (in reverse order to maintain indices) + for (int i = max_indices.size() - 1; i >= 1; --i) { + unsigned idx_to_remove = max_indices[i]; + m_objectives.erase(m_objectives.begin() + idx_to_remove); + } + + // Create a unique ID for this transformation + id = symbol("max_pb_unified"); + + TRACE(opt, tout << "Transformed to " << terms.size() << " auxiliary variables, " << m_objectives.size() << " objectives remaining\n";); + + return true; + } + expr* context::mk_objective_fn(unsigned index, objective_t ty, unsigned sz, expr*const* args) { ptr_vector domain; for (unsigned i = 0; i < sz; ++i) { @@ -1119,6 +1257,7 @@ namespace opt { void context::from_fmls(expr_ref_vector const& fmls) { TRACE(opt, tout << fmls << "\n";); m_hard_constraints.reset(); + for (expr * fml : fmls) { app_ref tr(m); expr_ref orig_term(m); diff --git a/src/opt/opt_context.h b/src/opt/opt_context.h index 991fe16e6..6313aeb00 100644 --- a/src/opt/opt_context.h +++ b/src/opt/opt_context.h @@ -313,6 +313,9 @@ namespace opt { bool is_maxsat(expr* fml, expr_ref_vector& terms, vector& weights, rational& offset, bool& neg, symbol& id, expr_ref& orig_term, unsigned& index); + bool detect_max_pb_constraints(expr_ref_vector& terms, + vector& weights, rational& offset, + bool& neg, symbol& id); void purify(app_ref& term); app* purify(generic_model_converter_ref& fm, expr* e); bool is_mul_const(expr* e); diff --git a/test_pb_max.smt2 b/test_pb_max.smt2 new file mode 100644 index 000000000..a4eeca282 --- /dev/null +++ b/test_pb_max.smt2 @@ -0,0 +1,10 @@ +(declare-fun x1 () Bool) +(declare-fun x2 () Bool) +(declare-fun y1 () Bool) +(declare-fun y2 () Bool) + +; Create two PB maximize objectives - must be integer expressions +(maximize (+ (ite x1 2 0) (ite x2 1 0))) ; PB1: max = 3 +(maximize (+ (ite y1 1 0) (ite y2 2 0))) ; PB2: max = 3 + +(check-sat) \ No newline at end of file diff --git a/test_pb_optimization b/test_pb_optimization new file mode 100755 index 0000000000000000000000000000000000000000..2945f325d9873200d56eb7e514d0d188919a1c07 GIT binary patch literal 66896 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 Jj0^^A{U4~D + +using namespace z3; + +void test_pb_max_pattern() { + std::cout << "Testing PB max pattern detection\n"; + + context c; + + // Create boolean variables + expr x1 = c.bool_const("x1"); + expr x2 = c.bool_const("x2"); + expr x3 = c.bool_const("x3"); + expr y1 = c.bool_const("y1"); + expr y2 = c.bool_const("y2"); + expr z1 = c.bool_const("z1"); + expr z2 = c.bool_const("z2"); + expr z3 = c.bool_const("z3"); + + // Create PB constraints like weighted sums + expr pb1 = 2*ite(x1, c.int_val(1), c.int_val(0)) + 3*ite(x2, c.int_val(1), c.int_val(0)) + ite(x3, c.int_val(1), c.int_val(0)); // potential max = 6 + expr pb2 = ite(y1, c.int_val(1), c.int_val(0)) + 2*ite(y2, c.int_val(1), c.int_val(0)); // potential max = 3 + expr pb3 = ite(z1, c.int_val(1), c.int_val(0)) + ite(z2, c.int_val(1), c.int_val(0)) + ite(z3, c.int_val(1), c.int_val(0)); // potential max = 3 + + optimize opt(c); + + // The pattern the issue might be referring to: + // Instead of maximizing pb1 + pb2 + pb3 directly, + // we should detect the pattern and introduce auxiliary variables + + // Current approach: maximize pb1 + pb2 + pb3 + opt.maximize(pb1 + pb2 + pb3); + + std::cout << "check: " << opt.check() << std::endl; + + if (opt.check() == sat) { + model m = opt.get_model(); + std::cout << "Model: " << m << std::endl; + + // Get objective values + std::cout << "Objectives: " << opt.objectives().size() << std::endl; + } +} + +void test_pb_max_with_constraints() { + std::cout << "\nTesting what the issue might want - auxiliary variables\n"; + + context c; + + // Create boolean variables + expr x1 = c.bool_const("x1"); + expr x2 = c.bool_const("x2"); + expr y1 = c.bool_const("y1"); + expr y2 = c.bool_const("y2"); + + // PB expressions + expr pb1 = 2*ite(x1, c.int_val(1), c.int_val(0)) + ite(x2, c.int_val(1), c.int_val(0)); // max = 3 + expr pb2 = ite(y1, c.int_val(1), c.int_val(0)) + 2*ite(y2, c.int_val(1), c.int_val(0)); // max = 3 + + optimize opt(c); + + // What the issue might want: auxiliary variables p_k for each threshold k + // For pb1: p1_1, p1_2, p1_3 where pb1 >= k => p1_k + // For pb2: p2_1, p2_2, p2_3 where pb2 >= k => p2_k + // Then maximize: p1_1 + p1_2 + p1_3 + p2_1 + p2_2 + p2_3 + + expr p1_1 = c.bool_const("p1_1"); + expr p1_2 = c.bool_const("p1_2"); + expr p1_3 = c.bool_const("p1_3"); + expr p2_1 = c.bool_const("p2_1"); + expr p2_2 = c.bool_const("p2_2"); + expr p2_3 = c.bool_const("p2_3"); + + // Add constraints: pb_i >= k => p_k + opt.add(implies(pb1 >= 1, p1_1)); + opt.add(implies(pb1 >= 2, p1_2)); + opt.add(implies(pb1 >= 3, p1_3)); + opt.add(implies(pb2 >= 1, p2_1)); + opt.add(implies(pb2 >= 2, p2_2)); + opt.add(implies(pb2 >= 3, p2_3)); + + // Maximize auxiliary variables + opt.maximize(ite(p1_1, c.int_val(1), c.int_val(0)) + ite(p1_2, c.int_val(1), c.int_val(0)) + ite(p1_3, c.int_val(1), c.int_val(0)) + ite(p2_1, c.int_val(1), c.int_val(0)) + ite(p2_2, c.int_val(1), c.int_val(0)) + ite(p2_3, c.int_val(1), c.int_val(0))); + + std::cout << "check: " << opt.check() << std::endl; + + if (opt.check() == sat) { + model m = opt.get_model(); + std::cout << "Model: " << m << std::endl; + + // Evaluate original PB expressions + std::cout << "pb1 = " << m.eval(pb1) << std::endl; + std::cout << "pb2 = " << m.eval(pb2) << std::endl; + + std::cout << "Objectives: " << opt.objectives().size() << std::endl; + } +} + +int main() { + test_pb_max_pattern(); + test_pb_max_with_constraints(); + return 0; +} \ No newline at end of file