3
0
Fork 0
mirror of https://github.com/Z3Prover/z3 synced 2026-05-16 15:15:35 +00:00

Fixes for lar_term== operator (#9284)

* Fix broken term_comparer in m_normalized_terms_to_columns lookup

The `m_normalized_terms_to_columns` map in `lar_solver` uses a
`term_comparer` that delegates to `lar_term::operator==`, which
intentionally returns `false` (with comment "take care not to create
identical terms"). This makes `fetch_normalized_term_column` unable to
find any term, rendering the Horner module's `interval_from_term`
bounds-recovery path dead code.

History: `lar_term::operator==` returning `false` has been present since
the original "merge LRA" commit (911b24784, 2018). The
`m_normalized_terms_to_columns` lookup was added later (dfe0e856,
c95f66e0, Aug 2019) as "toward fetching existing terms intervals from
lar_solver". The initial code had `lp_assert(find == end)` on
registration (always true with broken ==) and `lp_assert(find != end)`
on deregister (always false). The very next commit (207c1c50, one day
later) removed both asserts, replacing them with soft checks. The
`term_comparer` struct delegating to `operator==` was introduced during
a later PIMPL refactor (b375faa77).

Fix: Replace the `term_comparer` implementation with a structural
comparison that checks size and then verifies each coefficient-variable
pair via `coeffs().find_core()`. This is localized to the
`m_normalized_terms_to_columns` map and does not change
`lar_term::operator==`, preserving its intentional semantics elsewhere.

Validated: on a QF_UFNIA benchmark, `interval_from_term` lookups go
from 0/573 successful to 34/573 successful. Unit test added for the
`fetch_normalized_term_column` round-trip.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Disable operator== for lar_term

The operator== for lar_term was never intended to be used.
This changes physically disables it to identify what happens to depend
on the operator.

* Work around missing lar_term==

Previous commit disabled lar_term==. This is the only use of the
operator that seems meaningful. Changed it to compare by references
instead.

Compiles, but not sure this is the best solution.

* replace with e

Signed-off-by: Nikolaj Bjorner <nbjorner@microsoft.com>

* Delete unused ineq::operator==

The operator is unused, so there is no need to figure what is
the best fix for it.

* Remove lp tests that use ineq::operator==

---------

Signed-off-by: Nikolaj Bjorner <nbjorner@microsoft.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Nikolaj Bjorner <nbjorner@microsoft.com>
This commit is contained in:
Arie 2026-04-12 17:31:18 -04:00 committed by GitHub
parent 68e528eaf7
commit 665d4f36ff
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 94 additions and 21 deletions

View file

@ -238,18 +238,13 @@ namespace lp {
r.c() -= b.c();
return r;
}
#if Z3DEBUG
friend bool operator==(const term_o& a, const term_o& b) {
friend bool eq(const term_o& a, const term_o& b) {
term_o t = a.clone();
t += mpq(-1) * b;
return t.c() == mpq(0) && t.size() == 0;
}
friend bool operator!=(const term_o& a, const term_o& b) {
return ! (a == b);
}
#endif
term_o& operator+=(const term_o& t) {
for (const auto& p : t) {
add_monomial(p.coeff(), p.j());
@ -1541,7 +1536,7 @@ namespace lp {
term_o t1 = open_ml(t0);
t1.add_monomial(mpq(1), j);
term_o rs = fix_vars(t1);
if (ls != rs) {
if (!eq(ls, rs)) {
TRACE(dio, tout << "ls:"; print_term_o(ls, tout) << "\n";
tout << "rs:"; print_term_o(rs, tout) << "\n";);
return false;
@ -2351,7 +2346,7 @@ namespace lp {
return false;
}
bool ret = ls == fix_vars(open_ml(m_l_matrix.m_rows[ei]));
bool ret = eq(ls, fix_vars(open_ml(m_l_matrix.m_rows[ei])));
if (!ret) {
CTRACE(dio, !ret,
{

View file

@ -36,7 +36,12 @@ namespace lp {
struct term_comparer {
bool operator()(const lar_term& a, const lar_term& b) const {
return a == b;
if (a.size() != b.size()) return false;
for (const auto& p : a) {
auto const* e = b.coeffs().find_core(p.j());
if (!e || e->get_data().m_value != p.coeff()) return false;
}
return true;
}
};

View file

@ -129,8 +129,8 @@ public:
add_monomial(a, v1);
add_monomial(b, v2);
}
bool operator==(const lar_term & a) const { return false; } // take care not to create identical terms
bool operator!=(const lar_term & a) const { return ! (*this == a);}
bool operator==(const lar_term & a) const = delete; // take care not to create identical terms
bool operator!=(const lar_term & a) const = delete;
// some terms get used in add constraint
// it is the same as the offset in the m_constraints

View file

@ -41,9 +41,8 @@ namespace nla {
ineq(const lp::lar_term& term, lp::lconstraint_kind cmp, const rational& rs) : m_cmp(cmp), m_term(term), m_rs(rs) {}
ineq(lpvar v, lp::lconstraint_kind cmp, int i): m_cmp(cmp), m_term(v), m_rs(rational(i)) {}
ineq(lpvar v, lp::lconstraint_kind cmp, rational const& r): m_cmp(cmp), m_term(v), m_rs(r) {}
bool operator==(const ineq& a) const {
return m_cmp == a.m_cmp && m_term == a.m_term && m_rs == a.m_rs;
}
bool operator==(const ineq& a) const = delete;
bool operator!=(const ineq& a) const = delete;
const lp::lar_term& term() const { return m_term; };
lp::lconstraint_kind cmp() const { return m_cmp; };
const rational& rs() const { return m_rs; };