3
0
Fork 0
mirror of https://github.com/Z3Prover/z3 synced 2026-04-15 08:44:10 +00:00
z3/src/math/lp/horner.h
Arie Gurfinkel 1eb83ca9d4 Introduce new monomials in Horner when shared factors have bounded linear combinations
When solve-eqs eliminates a variable x (= a - b) that appears as a factor
in a nonlinear product x*y, the product splits into a*y - b*y. The NLA
solver then reasons about a*y and b*y independently, losing the tight
bounds that x had. This can cause severe performance degradation (e.g.,
timeout on a QF_UFNIA verification condition that solves in 3s without
solve-eqs).

The Horner module's cross-nested factoring already recovers the factored
form y*(a-b), and interval_from_term (fixed in the previous commit) finds
the LP column for (a-b) with its tight bounds. However, only Horner's
zero-exclusion check used this — the rest of the NLA solver (order lemmas,
tangent planes, bounds propagation) continued reasoning about the split
monomials independently.

This commit adds a new mechanism: when Horner discovers that a linear
sub-expression maps to a bounded LP column, it introduces a new monomial
pairing that column with the shared factor. For example, if y*(a-b) is
discovered and (a-b) maps to LP column j with bounds [L,U], we create a
new monomial m := y*j via add_mul_def and assert the equality
m = a*y - b*y via literals. This allows all NLA modules to generate
lemmas using j's tight bounds.

The feature is gated by smt.arith.nl.horner_max_new_monomials (default 2,
0 to disable). On the motivating benchmark, this changes
simplify+propagate-values+solve-eqs+smt from timeout (30s) to UNSAT in
~15s with no regressions on other configurations.

Files changed:
- horner.cpp: introduce_monomials_from_term_columns() and find_binary_monic()
- horner.h: m_introduced_monomials dedup set
- nla_intervals.cpp/h: m_term_columns to record interval_from_term discoveries
- smt_params_helper.pyg: arith.nl.horner_max_new_monomials parameter

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 16:25:49 -04:00

55 lines
1.4 KiB
C++

/*++
Copyright (c) 2017 Microsoft Corporation
Module Name:
<name>
Abstract:
<abstract>
Author:
Nikolaj Bjorner (nbjorner)
Lev Nachmanson (levnach)
Revision History:
--*/
#pragma once
#include "math/lp/nla_common.h"
#include "math/lp/nla_intervals.h"
#include "math/lp/nex.h"
#include "math/lp/cross_nested.h"
#include "util/uint_set.h"
namespace nla {
class core;
class horner : common {
nex_creator::sum_factory m_row_sum;
unsigned m_row_index;
svector<std::pair<lpvar, lpvar>> m_introduced_monomials; // track what we've already created
public:
typedef intervals::interval interv;
horner(core *core);
bool horner_lemmas();
template <typename T> // T has an iterator of (coeff(), var())
bool lemmas_on_row(const T&);
template <typename T> bool row_is_interesting(const T&) const;
intervals::interval interval_of_sum_with_deps(const nex_sum*);
intervals::interval interval_of_sum_no_term_with_deps(const nex_sum*);
void set_var_interval_with_deps(lpvar j, intervals::interval&) const;
bool lemmas_on_expr(cross_nested&, nex_sum*);
template <typename T> // T has an iterator of (coeff(), var())
bool row_has_monomial_to_refine(const T&) const;
bool interval_from_term_with_deps(const nex* e, intervals::interval&) const;
void introduce_monomials_from_term_columns();
}; // end of horner
}