3
0
Fork 0
mirror of https://github.com/Z3Prover/z3 synced 2025-05-10 09:15:47 +00:00
z3/src/math/polysat/variable_elimination.cpp
2022-11-28 19:13:38 +01:00

147 lines
5.2 KiB
C++

/*++
Copyright (c) 2021 Microsoft Corporation
Module Name:
Polysat variable elimination
Author:
Nikolaj Bjorner (nbjorner) 2021-03-19
Jakob Rath 2021-04-06
--*/
#include "math/polysat/variable_elimination.h"
#include "math/polysat/conflict.h"
#include "math/polysat/clause_builder.h"
#include "math/polysat/solver.h"
#include <algorithm>
namespace polysat {
void free_variable_elimination::find_lemma(conflict& core) {
LOG_H1("Free Variable Elimination");
LOG("core: " << core);
LOG("Free variables: " << s.m_free_pvars);
for (pvar v : core.vars_occurring_in_constraints())
if (!s.is_assigned(v)) // TODO: too restrictive. should also consider variables that will be unassigned only after backjumping (can update this after assignment handling in search state is refactored.)
find_lemma(v, core);
}
void free_variable_elimination::find_lemma(pvar v, conflict& core) {
LOG_H2("Free Variable Elimination for v" << v);
// find constraint that allows computing v from other variables
// (currently, consider only equations that contain v with degree 1)
for (signed_constraint c : core) {
if (!c.is_eq())
continue;
if (c.eq().degree(v) != 1)
continue;
find_lemma(v, c, core);
}
}
void free_variable_elimination::find_lemma(pvar v, signed_constraint c, conflict& core) {
LOG_H3("Free Variable Elimination for v" << v << " using equation " << c);
pdd const& p = c.eq();
SASSERT_EQ(p.degree(v), 1);
auto& m = p.manager();
pdd lc = m.zero();
pdd rest = m.zero();
p.factor(v, 1, lc, rest);
if (rest.is_val())
return;
// lc * v + rest == p == 0
// v == -1 * rest * lc^-1
SASSERT(!lc.free_vars().contains(v));
SASSERT(!rest.free_vars().contains(v));
LOG("lc: " << lc);
LOG("rest: " << rest);
substitution sub(m);
pdd const lcs = eval(lc, core, sub);
LOG("lcs: " << lcs);
pdd lci = m.zero();
if (!inv(lcs, lci))
return;
pdd const rs = sub.apply_to(rest);
pdd const vs = -rs * lci; // this is the polynomial that computes v
LOG("vs: " << vs);
SASSERT(!vs.free_vars().contains(v));
// Find another constraint where we want to substitute v
for (signed_constraint c_target : core) {
if (c == c_target)
continue;
if (c_target.vars().size() <= 1)
continue;
if (!c_target.contains_var(v))
continue;
// TODO: helper method constraint::subst(pvar v, pdd const& p)
// (or rather, add it on constraint_manager since we need to allocate/dedup the new constraint)
// For now, just restrict to ule_constraint.
if (!c_target->is_ule())
continue;
// TODO: maybe apply assignment a here as well
pdd const lhs = c_target->to_ule().lhs().subst_pdd(v, vs);
pdd const rhs = c_target->to_ule().rhs().subst_pdd(v, vs);
signed_constraint c_new = s.ule(lhs, rhs);
if (c_target.is_negative())
c_new.negate();
LOG("c_target: " << lit_pp(s, c_target));
LOG("c_new: " << lit_pp(s, c_new));
// New constraint is already true (maybe we already derived it previously?)
// TODO: It might make sense to keep different derivations of the same constraint.
// E.g., if the new clause could derive c_new at a lower decision level.
if (c_new.bvalue(s) == l_true)
continue;
clause_builder cb(s);
for (auto [w, wv] : sub)
cb.insert(~s.eq(s.var(w), wv));
cb.insert(~c);
cb.insert(~c_target);
cb.insert(c_new);
core.add_lemma("variable elimination", cb.build());
}
}
// Evaluate p under assignments in the core.
pdd free_variable_elimination::eval(pdd const& p, conflict& core, substitution& out_sub) {
// TODO: this should probably be a helper method on conflict.
// TODO: recognize constraints of the form "v1 == 27" to be used in the assignment?
// (but maybe useful evaluations are always part of core.vars() anyway?)
substitution& sub = out_sub;
SASSERT(sub.empty());
for (auto v : p.free_vars())
if (core.contains_pvar(v))
sub.add(v, s.get_value(v));
pdd q = sub.apply_to(p);
// TODO: like in the old conflict::minimize_vars, we can now try to remove unnecessary variables from a.
return q;
}
// Compute the multiplicative inverse of p.
bool free_variable_elimination::inv(pdd const& p, pdd& out_p_inv) {
// TODO: in the non-val case, we could introduce an additional variable to represent the inverse
// (and a constraint p * p_inv == 1)
if (!p.is_val())
return false;
rational iv;
if (!p.val().mult_inverse(p.power_of_2(), iv))
return false;
out_p_inv = p.manager().mk_val(iv);
return true;
}
}