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>
* Introduce X-macro-based trace tag definition
- Created trace_tags.def to centralize TRACE tag definitions
- Each tag includes a symbolic name and description
- Set up enum class TraceTag for type-safe usage in TRACE macros
* Add script to generate Markdown documentation from trace_tags.def
- Python script parses trace_tags.def and outputs trace_tags.md
* Refactor TRACE_NEW to prepend TraceTag and pass enum to is_trace_enabled
* trace: improve trace tag handling system with hierarchical tagging
- Introduce hierarchical tag-class structure: enabling a tag class activates all child tags
- Unify TRACE, STRACE, SCTRACE, and CTRACE under enum TraceTag
- Implement initial version of trace_tag.def using X(tag, tag_class, description)
(class names and descriptions to be refined in a future update)
* trace: replace all string-based TRACE tags with enum TraceTag
- Migrated all TRACE, STRACE, SCTRACE, and CTRACE macros to use enum TraceTag values instead of raw string literals
* trace : add cstring header
* trace : Add Markdown documentation generation from trace_tags.def via mk_api_doc.py
* trace : rename macro parameter 'class' to 'tag_class' and remove Unicode comment in trace_tags.h.
* trace : Add TODO comment for future implementation of tag_class activation
* trace : Disable code related to tag_class until implementation is ready (#7663).
* replace u_set by indexed_uint_set
* replace u_set by indexed_uint_set
* create insert-fresh and insert for indexed_uint_set to make use cases with non-fresh inserts easier
Signed-off-by: Nikolaj Bjorner <nbjorner@microsoft.com>
* update nightly to pull arm
Signed-off-by: Nikolaj Bjorner <nbjorner@microsoft.com>
* update nightly to pull arm
Signed-off-by: Nikolaj Bjorner <nbjorner@microsoft.com>
* fixing the build of lp_tst
* update nightly to pull arm
Signed-off-by: Nikolaj Bjorner <nbjorner@microsoft.com>
* replace u_set by indexed_uint_set
* replace u_set by indexed_uint_set
* fixing the build of lp_tst
* remove unnecessery call to contains() before
insert to indexed_uint_set
* formatting, no check for contains()
in indexed_uint_set, always init m_touched_rows to nullptr
---------
Signed-off-by: Nikolaj Bjorner <nbjorner@microsoft.com>
Co-authored-by: Nikolaj Bjorner <nbjorner@microsoft.com>
this update integrates inferences to smt.arith.solver=6 related to grobner basis computation and handling of div/mod axioms to reconcile performance with smt.arith.solver=2.
The default of smt.arth.nl.grobner_subs_fixed is changed to 1 to make comparison with solver=2 more direct.
The selection of cluster equalities for solver=6 was reconciled with how it is done for solver=2.