#include <map>
#include <iostream>
#include "util/debug.h"
#include "math/lp/lar_solver.h"
#include "math/lp/lp_bound_propagator.h"
namespace lp {
struct farkas_explanation_collector {
lar_solver& m_solver;
vector<std::pair<mpq, constraint_index>> m_premises;
farkas_explanation_collector(lar_solver& s) : m_solver(s) {}
lar_solver& lp() { return m_solver; }
const lar_solver& lp() const { return m_solver; }
bool bound_is_interesting(unsigned, lconstraint_kind, const mpq&) const { return true; }
void consume(const mpq& a, constraint_index ci) { m_premises.push_back({a, ci}); }
bool add_eq(lpvar, lpvar, const explanation&, bool) { return false; }
bool is_equal(lpvar u, lpvar v) const { return u == v; }
};
typedef vector<std::pair<mpq, constraint_index>> premises_t;
static mpq farkas_coeff_of(premises_t const& premises, constraint_index ci) {
for (auto const& [a, c] : premises)
if (c == ci)
return a;
ENSURE(false);
return mpq(0);
}
static void check_farkas_certificate(lar_solver const& ls, implied_bound const& ib, premises_t const& premises) {
std::map<lpvar, mpq> lhs;
mpq rhs(0);
ENSURE(!premises.empty());
for (auto const& [a, ci] : premises) {
ENSURE(a.is_pos());
auto const& c = ls.constraints()[ci];
ENSURE(c.kind() == LE || c.kind() == GE);
mpq sign = c.kind() == LE ? a : -a;
for (auto const& [coeff, j] : c.coeffs())
lhs[j] += sign * coeff;
rhs += sign * c.rhs();
}
for (auto it = lhs.begin(); it != lhs.end();) {
if (it->second.is_zero())
it = lhs.erase(it);
else
++it;
}
ENSURE(lhs.size() == 1);
ENSURE(lhs.begin()->first == ib.m_j);
mpq expected_coeff = ib.m_is_lower_bound ? mpq(-1) : mpq(1);
ENSURE(lhs.begin()->second == expected_coeff);
ENSURE(rhs == expected_coeff * ib.m_bound);
}
static implied_bound const& find_implied_bound(std_vector<implied_bound> const& ibounds, lpvar j, bool is_lower) {
for (auto const& ib : ibounds)
if (ib.m_j == j && ib.m_is_lower_bound == is_lower)
return ib;
ENSURE(false);
return ibounds[0];
}
static void test_row_farkas_coefficients() {
lar_solver ls;
lpvar x = ls.add_var(0, false);
lpvar y = ls.add_var(1, false);
lpvar z = ls.add_var(2, false);
lpvar w = ls.add_var(3, false);
vector<std::pair<mpq, lpvar>> coeffs;
coeffs.push_back({mpq(2), x});
coeffs.push_back({mpq(3), y});
coeffs.push_back({mpq(-1), z});
lpvar t1 = ls.add_term(coeffs, 4);
constraint_index t1_ge = ls.add_var_bound(t1, GE, mpq(0));
constraint_index t1_le = ls.add_var_bound(t1, LE, mpq(0));
coeffs.clear();
coeffs.push_back({mpq(1), x});
coeffs.push_back({mpq(3), y});
coeffs.push_back({mpq(-2), w});
lpvar t2 = ls.add_term(coeffs, 5);
constraint_index t2_ge = ls.add_var_bound(t2, GE, mpq(0));
constraint_index t2_le = ls.add_var_bound(t2, LE, mpq(0));
constraint_index x_le = ls.add_var_bound(x, LE, mpq(1));
constraint_index y_le = ls.add_var_bound(y, LE, mpq(2));
ENSURE(ls.find_feasible_solution() != lp_status::INFEASIBLE);
farkas_explanation_collector imp(ls);
std_vector<implied_bound> ibounds;
lp_bound_propagator<farkas_explanation_collector> bp(imp, ibounds);
bp.init();
ls.add_column_rows_to_touched_rows(x);
ls.add_column_rows_to_touched_rows(y);
ls.propagate_bounds_for_touched_rows(bp);
{
implied_bound const& ib = find_implied_bound(bp.ibounds(), z, false);
ENSURE(ib.m_bound == mpq(8));
ENSURE(!ib.m_strict);
imp.m_premises.clear();
ls.explain_implied_bound(ib, bp);
ENSURE(imp.m_premises.size() == 3);
ENSURE(farkas_coeff_of(imp.m_premises, x_le) == mpq(2));
ENSURE(farkas_coeff_of(imp.m_premises, y_le) == mpq(3));
bool uses_t1 = false;
for (auto const& [a, ci] : imp.m_premises) {
if (ci == t1_ge || ci == t1_le) {
uses_t1 = true;
ENSURE(a == mpq(1));
}
}
ENSURE(uses_t1);
check_farkas_certificate(ls, ib, imp.m_premises);
}
{
implied_bound const& ib = find_implied_bound(bp.ibounds(), w, false);
ENSURE(ib.m_bound == mpq(7, 2));
ENSURE(!ib.m_strict);
imp.m_premises.clear();
ls.explain_implied_bound(ib, bp);
ENSURE(imp.m_premises.size() == 3);
ENSURE(farkas_coeff_of(imp.m_premises, x_le) == mpq(1, 2));
ENSURE(farkas_coeff_of(imp.m_premises, y_le) == mpq(3, 2));
bool uses_t2 = false;
for (auto const& [a, ci] : imp.m_premises) {
if (ci == t2_ge || ci == t2_le) {
uses_t2 = true;
ENSURE(a == mpq(1, 2));
}
}
ENSURE(uses_t2);
check_farkas_certificate(ls, ib, imp.m_premises);
}
for (auto const& ib : bp.ibounds()) {
imp.m_premises.clear();
ls.explain_implied_bound(ib, bp);
check_farkas_certificate(ls, ib, imp.m_premises);
}
std::cout << "bound propagation: " << bp.ibounds().size() << " implied bounds certified\n";
}
}
void tst_lp_bound_propagation() {
lp::test_row_farkas_coefficients();
}