3
0
Fork 0
mirror of https://github.com/Z3Prover/z3 synced 2026-03-31 08:39:01 +00:00

Nl2lin - integrate a linear under approximation of a CAD cell by Valentin Promies. (#8982)

* outline of signature for assignment based conflict generation

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

* outline of interface contract

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

* remove confusing construction

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

* add material in nra-solver to interface

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

* add marshaling from nlsat lemmas into core solver

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

* tidy

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

* add call to check-assignment

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

* Nl2lin (#7795)

* add linearized projection in nlsat

* implement nlsat check for given assignment

* add some comments

* fixup loop

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

* updates

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

* fixes

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

* debug nl2lin

Signed-off-by: Lev Nachmanson <levnach@hotmail.com>

* Nl2lin (#7827)

* fix linear projection

* fix linear projection

* use an explicit cell description in check_assignment

* clean up (#7844)

* Simplify no effect checks in nla_core.cpp

Move up linear nlsat call to replace bounded nlsat.

* t

Signed-off-by: Lev Nachmanson <levnach@hotmail.com>

* t

Signed-off-by: Lev Nachmanson <levnach@hotmail.com>

* detangle mess

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

* remove the too early return

Signed-off-by: Lev Nachmanson <levnach@hotmail.com>

* do not set use_nra_model to true

Signed-off-by: Lev Nachmanson <levnach@hotmail.com>

* remove a comment

Signed-off-by: Lev Nachmanson <levnach@hotmail.com>

* add a hook to add new multiplication definitions in nla_core

* add internalization routine that uses macro-expanded polynomial representation

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

* add internalization routine that uses macro-expanded polynomial representation

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

* fixup backtranslation to not use roots

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

* call setup_assignment_solver instead of setup_solver

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

* debug the setup, still not working

Signed-off-by: Lev Nachmanson <levnach@hotmail.com>

* updated clang format

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

* simplify

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

* create polynomials with integer coefficients, use the hook to create new monomials

Signed-off-by: Lev Nachmanson <levnach@hotmail.com>

* integrating changes from master related to work with polynomials

Signed-off-by: Lev Nachmanson <levnach@hotmail.com>

* add forgotten files

Signed-off-by: Lev Nachmanson <levnach@hotmail.com>

* Update nlsat_explain.cpp

Remove a duplicate call

* fix

* move linear cell construction to levelwise

* fix

* fix

* Port throttle and soundness fixes from master

- Fix soundness: pop incomplete lemma from m_lemmas on add_lemma failure
- Gracefully handle root atoms in add_lemma
- Throttle check_assignment with failure counter (decrement on success)
- Add arith.nl.nra_check_assignment parameter

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Add arith.nl.nra_check_assignment_max_fail parameter

Replace hardcoded failure threshold with configurable parameter (default 10).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Add cha_abort_on_fail parameter to control failure counter decrement

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* abort nla check_assignment after a set number of allowed failures

Signed-off-by: Lev Nachmanson <levnach@hotmail.com>

* Add missing AST query methods to Java API (#8977)

* add Expr.isGround() to Java API

Expose Z3_is_ground as a public method on Expr. Returns true when the
expression contains no free variables.

* add Expr.isLambda() to Java API

Expose Z3_is_lambda as a public method on Expr. Returns true when the
expression is a lambda quantifier.

* add AST.getDepth() to Java API

Expose Z3_get_depth as a public method on AST. Returns the maximum
number of nodes on any path from root to leaf.

* add ArraySort.getArity() to Java API

Expose Z3_get_array_arity as a public method on ArraySort. Returns
the number of dimensions of a multi-dimensional array sort.

* add DatatypeSort.isRecursive() to Java API

Expose Z3_is_recursive_datatype_sort as a public method on
DatatypeSort. Returns true when the datatype refers to itself.

* add FPExpr.isNumeral() to Java API

Expose Z3_fpa_is_numeral as a public method on FPExpr. Returns true
when the expression is a concrete floating-point value.

* add isGroundExample test to JavaExample

Test Expr.isGround() on constants, variables, and compound
expressions.

* add astDepthExample test to JavaExample

Test AST.getDepth() on leaf nodes and nested expressions to verify
the depth computation.

* add arrayArityExample test to JavaExample

Test ArraySort.getArity() on single-domain and multi-domain array
sorts.

* add recursiveDatatypeExample test to JavaExample

Test DatatypeSort.isRecursive() on a recursive list datatype and a
non-recursive pair datatype.

* add fpNumeralExample test to JavaExample

Test FPExpr.isNumeral() on a floating point constant and a symbolic
variable.

* add isLambdaExample test to JavaExample

Test Expr.isLambda() on a lambda expression and a plain variable.

* change the default number of failures in check_assignment to 7

Signed-off-by: Lev Nachmanson <levnach@hotmail.com>

* Fix high and medium priority API coherence issues (Go, Java, C++, TypeScript) (#8983)

* Initial plan

* Add missing API functions to Go, Java, C++, and TypeScript bindings

Co-authored-by: NikolajBjorner <3085284+NikolajBjorner@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: NikolajBjorner <3085284+NikolajBjorner@users.noreply.github.com>

* qf-s-benchmark: debug build + seq tracing + seq-fast/nseq-slow trace analysis (#8988)

* Initial plan

* Update qf-s-benchmark: debug build, seq tracing, trace analysis

Co-authored-by: NikolajBjorner <3085284+NikolajBjorner@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: NikolajBjorner <3085284+NikolajBjorner@users.noreply.github.com>

* disable linear approximation by default to check the merge

Signed-off-by: Lev Nachmanson <levnach@hotmail.com>

* set check_assignment to true

Signed-off-by: Lev Nachmanson <levnach@hotmail.com>

* fix restore_x by recalulating new column values

Signed-off-by: Lev Nachmanson <levnach@hotmail.com>

* fix restore_x by recalulating new column values

Signed-off-by: Lev Nachmanson <levnach@hotmail.com>

* fix a memory leak

Signed-off-by: Lev Nachmanson <levnach@hotmail.com>

---------

Signed-off-by: Nikolaj Bjorner <nbjorner@microsoft.com>
Signed-off-by: Lev Nachmanson <levnach@hotmail.com>
Co-authored-by: Nikolaj Bjorner <nbjorner@microsoft.com>
Co-authored-by: ValentinPromies <44966217+ValentinPromies@users.noreply.github.com>
Co-authored-by: Valentin Promies <valentin.promies@rwth-aachen.de>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Angelica Moreira <48168649+angelica-moreira@users.noreply.github.com>
Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com>
Co-authored-by: NikolajBjorner <3085284+NikolajBjorner@users.noreply.github.com>
This commit is contained in:
Lev Nachmanson 2026-03-15 06:13:04 -10:00 committed by GitHub
parent cb13fa2325
commit 6fb68ac010
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
17 changed files with 497 additions and 22 deletions

View file

@ -446,8 +446,11 @@ public:
cs.restore_x();
if (backup_sz < current_sz) {
// New columns were added after backup.
// move_non_basic_columns_to_bounds snaps non-basic
// columns to their bounds and finds a feasible solution.
// Recalculate basic variable values from non-basic ones
// to restore the Ax=0 tableau invariant, then snap
// non-basic columns to their bounds and find a feasible solution.
for (unsigned i = 0; i < A_r().row_count(); i++)
set_column_value(r_basis()[i], get_basic_var_value_from_row(i));
move_non_basic_columns_to_bounds();
}
else {

View file

@ -1,4 +1,3 @@
/*++
Copyright (c) 2025 Microsoft Corporation
@ -85,4 +84,4 @@ namespace nla {
}
}
}
}
}

View file

@ -1,4 +1,3 @@
/*++
Copyright (c) 2025 Microsoft Corporation
@ -14,6 +13,9 @@
#pragma once
#include "util/uint_set.h"
#include "util/vector.h"
namespace nla {
class core;
@ -40,4 +42,4 @@ namespace nla {
indexed_uint_set const &vars() { return m_var_set; }
};
}
}

View file

@ -1331,7 +1331,14 @@ lbool core::check(unsigned level) {
return l_false;
}
if (no_effect() && params().arith_nl_nra_check_assignment() && m_check_assignment_fail_cnt < params().arith_nl_nra_check_assignment_max_fail()) {
scoped_limits sl(m_reslim);
sl.push_child(&m_nra_lim);
ret = m_nra.check_assignment();
if (ret != l_true)
++m_check_assignment_fail_cnt;
}
if (no_effect() && should_run_bounded_nlsat())
ret = bounded_nlsat();
@ -1582,4 +1589,4 @@ void core::refine_pseudo_linear(monic const& m) {
}
SASSERT(nlvar != null_lpvar);
lemma |= ineq(lp::lar_term(m.var(), rational(-prod), nlvar), llc::EQ, rational(0));
}
}

View file

@ -63,6 +63,7 @@ class core {
unsigned m_nlsat_delay = 0;
unsigned m_nlsat_delay_bound = 0;
unsigned m_check_assignment_fail_cnt = 0;
bool should_run_bounded_nlsat();
lbool bounded_nlsat();
@ -94,6 +95,8 @@ class core {
emonics m_emons;
svector<lpvar> m_add_buffer;
mutable indexed_uint_set m_active_var_set;
// hook installed by theory_lra for creating a multiplication definition
std::function<lpvar(unsigned, lpvar const*)> m_add_mul_def_hook;
reslimit m_nra_lim;
@ -215,6 +218,8 @@ public:
void add_idivision(lpvar q, lpvar x, lpvar y) { m_divisions.add_idivision(q, x, y); }
void add_rdivision(lpvar q, lpvar x, lpvar y) { m_divisions.add_rdivision(q, x, y); }
void add_bounded_division(lpvar q, lpvar x, lpvar y) { m_divisions.add_bounded_division(q, x, y); }
void set_add_mul_def_hook(std::function<lpvar(unsigned, lpvar const*)> const& f) { m_add_mul_def_hook = f; }
lpvar add_mul_def(unsigned sz, lpvar const* vs) { SASSERT(m_add_mul_def_hook); lpvar v = m_add_mul_def_hook(sz, vs); add_monic(v, sz, vs); return v; }
void set_relevant(std::function<bool(lpvar)>& is_relevant) { m_relevant = is_relevant; }
bool is_relevant(lpvar v) const { return !m_relevant || m_relevant(v); }
@ -478,4 +483,3 @@ inline std::ostream& operator<<(std::ostream& out, pp_factorization const& f) {
inline std::ostream& operator<<(std::ostream& out, pp_var const& v) { return v.c.print_var(v.v, out); }
} // end of namespace nla

View file

@ -12,6 +12,7 @@
#include "math/lp/nla_intervals.h"
#include "math/lp/nex.h"
#include "math/lp/cross_nested.h"
#include "util/params.h"
#include "util/uint_set.h"
#include "math/grobner/pdd_solver.h"

View file

@ -432,4 +432,4 @@ std::ostream& core::display_constraint_smt(std::ostream& out, unsigned id, lp::l
out << (evaluation ? "true" : "false");
out << "\n";
return out;
}
}

View file

@ -11,6 +11,7 @@
#include "math/lp/nra_solver.h"
#include "math/lp/nla_coi.h"
#include "nlsat/nlsat_solver.h"
#include "nlsat/nlsat_assignment.h"
#include "math/polynomial/polynomial.h"
#include "math/polynomial/algebraic_numbers.h"
#include "util/map.h"
@ -35,6 +36,13 @@ struct solver::imp {
scoped_ptr<scoped_anum_vector> m_values; // values provided by LRA solver
scoped_ptr<scoped_anum> m_tmp1, m_tmp2;
nla::coi m_coi;
svector<lp::constraint_index> m_literal2constraint;
struct eq {
bool operator()(unsigned_vector const &a, unsigned_vector const &b) const {
return a == b;
}
};
map<unsigned_vector, unsigned, svector_hash<unsigned_hash>, eq> m_vars2mon;
nla::core& m_nla_core;
imp(lp::lar_solver& s, reslimit& lim, params_ref const& p, nla::core& nla_core):
@ -92,6 +100,44 @@ struct solver::imp {
denominators.push_back(den);
}
// Create polynomial definition for variable v used in setup_assignment_solver.
// Side-effects: updates m_vars2mon when v is a monic variable.
void mk_definition_assignment(unsigned v, polynomial_ref_vector &definitions) {
auto &pm = m_nlsat->pm();
polynomial::polynomial_ref p(pm);
if (m_nla_core.emons().is_monic_var(v)) {
auto const &m = m_nla_core.emons()[v];
auto vars = m.vars();
std::sort(vars.begin(), vars.end());
m_vars2mon.insert(vars, v);
for (auto v2 : vars) {
auto pv = definitions.get(v2);
if (!p)
p = pv;
else
p = pm.mul(p, pv);
}
}
else if (lra.column_has_term(v)) {
rational den(1);
for (auto const& [w, coeff] : lra.get_term(v))
den = lcm(den, denominator(coeff));
for (auto const& [w, coeff] : lra.get_term(v)) {
auto pw = definitions.get(w);
polynomial::polynomial_ref term(pm);
term = pm.mul(den * coeff, pw);
if (!p)
p = term;
else
p = pm.add(p, term);
}
}
else {
p = pm.mk_polynomial(lp2nl(v));
}
definitions.push_back(p);
}
void setup_solver_poly() {
m_coi.init();
auto &pm = m_nlsat->pm();
@ -273,7 +319,195 @@ struct solver::imp {
break;
}
return r;
}
}
void setup_assignment_solver() {
SASSERT(need_check());
reset();
m_literal2constraint.reset();
m_vars2mon.reset();
m_coi.init();
auto &pm = m_nlsat->pm();
polynomial_ref_vector definitions(pm);
for (unsigned v = 0; v < lra.number_of_vars(); ++v) {
auto j = m_nlsat->mk_var(lra.var_is_int(v));
VERIFY(j == v);
m_lp2nl.insert(v, j);
scoped_anum a(am());
am().set(a, m_nla_core.val(v).to_mpq());
m_values->push_back(a);
mk_definition_assignment(v, definitions);
}
for (auto ci : m_coi.constraints()) {
auto &c = lra.constraints()[ci];
auto &pm = m_nlsat->pm();
auto k = c.kind();
auto rhs = c.rhs();
auto lhs = c.coeffs();
rational den = denominator(rhs);
for (auto [coeff, v] : lhs)
den = lcm(den, denominator(coeff));
polynomial::polynomial_ref p(pm);
p = pm.mk_const(-den * rhs);
for (auto [coeff, v] : lhs) {
polynomial_ref poly(pm);
poly = pm.mul(den * coeff, definitions.get(v));
p = p + poly;
}
auto lit = add_constraint(p, ci, k);
m_literal2constraint.setx(lit.index(), ci, lp::null_ci);
}
}
void process_polynomial_check_assignment(polynomial::polynomial const* p, rational& bound, const u_map<lp::lpvar>& nl2lp, lp::lar_term& t) {
polynomial::manager& pm = m_nlsat->pm();
for (unsigned i = 0; i < pm.size(p); ++i) {
polynomial::monomial* m = pm.get_monomial(p, i);
auto& coeff = pm.coeff(p, i);
unsigned num_vars = pm.size(m);
// add mon * coeff to t;
switch (num_vars) {
case 0:
bound -= coeff;
break;
case 1: {
auto v = nl2lp[pm.get_var(m, 0)];
t.add_monomial(coeff, v);
break;
}
default: {
svector<lp::lpvar> vars;
for (unsigned j = 0; j < num_vars; ++j)
vars.push_back(nl2lp[pm.get_var(m, j)]);
std::sort(vars.begin(), vars.end());
lp::lpvar v;
if (m_vars2mon.contains(vars))
v = m_vars2mon[vars];
else
v = m_nla_core.add_mul_def(vars.size(), vars.data());
t.add_monomial(coeff, v);
break;
}
}
}
}
u_map<lp::lpvar> reverse_lp2nl() {
u_map<lp::lpvar> nl2lp;
for (auto [j, x] : m_lp2nl)
nl2lp.insert(x, j);
return nl2lp;
}
lbool check_assignment() {
setup_assignment_solver();
lbool r = l_undef;
statistics &st = m_nla_core.lp_settings().stats().m_st;
nlsat::literal_vector clause;
try {
nlsat::assignment rvalues(m_nlsat->am());
for (auto [j, x] : m_lp2nl) {
scoped_anum a(am());
am().set(a, m_nla_core.val(j).to_mpq());
rvalues.set(x, a);
}
r = m_nlsat->check(rvalues, clause);
}
catch (z3_exception &) {
if (m_limit.is_canceled()) {
r = l_undef;
}
else {
m_nlsat->collect_statistics(st);
throw;
}
}
m_nlsat->collect_statistics(st);
switch (r) {
case l_true:
m_nla_core.set_use_nra_model(true);
lra.init_model();
for (lp::constraint_index ci : lra.constraints().indices())
if (!check_constraint(ci))
return l_undef;
for (auto const& m : m_nla_core.emons())
if (!check_monic(m))
return l_undef;
m_nla_core.set_use_nra_model(true);
break;
case l_false:
r = add_lemma(clause);
break;
default:
break;
}
return r;
}
lbool add_lemma(nlsat::literal_vector const &clause) {
u_map<lp::lpvar> nl2lp = reverse_lp2nl();
polynomial::manager &pm = m_nlsat->pm();
lbool result = l_false;
{
nla::lemma_builder lemma(m_nla_core, __FUNCTION__);
for (nlsat::literal l : clause) {
if (m_literal2constraint.get((~l).index(), lp::null_ci) != lp::null_ci) {
auto ci = m_literal2constraint[(~l).index()];
lp::explanation ex;
ex.push_back(ci);
lemma &= ex;
continue;
}
nlsat::atom *a = m_nlsat->bool_var2atom(l.var());
if (a->is_root_atom()) {
result = l_undef;
break;
}
SASSERT(a->is_ineq_atom());
auto &ia = *to_ineq_atom(a);
if (ia.size() != 1) {
result = l_undef; // factored polynomials not handled here
break;
}
polynomial::polynomial const *p = ia.p(0);
rational bound(0);
lp::lar_term t;
process_polynomial_check_assignment(p, bound, nl2lp, t);
nla::ineq inq(lp::lconstraint_kind::EQ, t, bound); // initial value overwritten in cases below
switch (a->get_kind()) {
case nlsat::atom::EQ:
inq = nla::ineq(l.sign() ? lp::lconstraint_kind::NE : lp::lconstraint_kind::EQ, t, bound);
break;
case nlsat::atom::LT:
inq = nla::ineq(l.sign() ? lp::lconstraint_kind::GE : lp::lconstraint_kind::LT, t, bound);
break;
case nlsat::atom::GT:
inq = nla::ineq(l.sign() ? lp::lconstraint_kind::LE : lp::lconstraint_kind::GT, t, bound);
break;
default:
UNREACHABLE();
result = l_undef;
break;
}
if (result == l_undef)
break;
if (m_nla_core.ineq_holds(inq)) {
result = l_undef;
break;
}
lemma |= inq;
}
if (result == l_false)
this->m_nla_core.m_check_feasible = true;
} // lemma_builder destructor runs here
if (result == l_undef)
m_nla_core.m_lemmas.pop_back(); // discard incomplete lemma
return result;
}
void add_monic_eq_bound(mon_eq const& m) {
@ -643,10 +877,9 @@ struct solver::imp {
unsigned w;
scoped_anum a(am());
for (unsigned v = m_values->size(); v < sz; ++v) {
if (m_nla_core.emons().is_monic_var(v)) {
if (m_nla_core.emons().is_monic_var(v)) {
am().set(a, 1);
auto &m = m_nla_core.emon(v);
for (auto x : m.vars())
am().mul(a, (*m_values)[x], a);
m_values->push_back(a);
@ -654,7 +887,7 @@ struct solver::imp {
else if (lra.column_has_term(v)) {
scoped_anum b(am());
am().set(a, 0);
for (auto const &[w, coeff] : lra.get_term(v)) {
for (auto const &[w, coeff] : lra.get_term(v)) {
am().set(b, coeff.to_mpq());
am().mul(b, (*m_values)[w], b);
am().add(a, b, a);
@ -737,6 +970,10 @@ lbool solver::check(dd::solver::equation_vector const& eqs) {
return m_imp->check(eqs);
}
lbool solver::check_assignment() {
return m_imp->check_assignment();
}
bool solver::need_check() {
return m_imp->need_check();
}

View file

@ -47,6 +47,11 @@ namespace nra {
*/
lbool check(dd::solver::equation_vector const& eqs);
/**
\brief Check feasibility modulo current value assignment.
*/
lbool check_assignment();
/*
\brief determine whether nra check is needed.
*/