From: Gereon Kremer Date: Tue, 1 Mar 2022 00:37:13 +0000 (+0100) Subject: Rename cad to coverings (#8187) X-Git-Tag: cvc5-1.0.0~358 X-Git-Url: https://git.libre-soc.org/?a=commitdiff_plain;h=8f1ecaaef1ce13533a7dd8b19a3373a64f9edab4;p=cvc5.git Rename cad to coverings (#8187) The nonlinear subsolver that implements cylindrical algebraic coverings is still called cad in many places, which really was a misnomer from the beginning. This PR renames it everywhere. --- diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index ac8b64293..e29b234ba 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -410,24 +410,24 @@ libcvc5_add_sources( theory/arith/linear_equality.h theory/arith/matrix.cpp theory/arith/matrix.h - theory/arith/nl/cad_solver.cpp - theory/arith/nl/cad_solver.h - theory/arith/nl/cad/cdcac.cpp - theory/arith/nl/cad/cdcac.h - theory/arith/nl/cad/cdcac_utils.cpp - theory/arith/nl/cad/cdcac_utils.h - theory/arith/nl/cad/constraints.cpp - theory/arith/nl/cad/constraints.h - theory/arith/nl/cad/lazard_evaluation.cpp - theory/arith/nl/cad/lazard_evaluation.h - theory/arith/nl/cad/projections.cpp - theory/arith/nl/cad/projections.h - theory/arith/nl/cad/proof_checker.cpp - theory/arith/nl/cad/proof_checker.h - theory/arith/nl/cad/proof_generator.cpp - theory/arith/nl/cad/proof_generator.h - theory/arith/nl/cad/variable_ordering.cpp - theory/arith/nl/cad/variable_ordering.h + theory/arith/nl/coverings_solver.cpp + theory/arith/nl/coverings_solver.h + theory/arith/nl/coverings/cdcac.cpp + theory/arith/nl/coverings/cdcac.h + theory/arith/nl/coverings/cdcac_utils.cpp + theory/arith/nl/coverings/cdcac_utils.h + theory/arith/nl/coverings/constraints.cpp + theory/arith/nl/coverings/constraints.h + theory/arith/nl/coverings/lazard_evaluation.cpp + theory/arith/nl/coverings/lazard_evaluation.h + theory/arith/nl/coverings/projections.cpp + theory/arith/nl/coverings/projections.h + theory/arith/nl/coverings/proof_checker.cpp + theory/arith/nl/coverings/proof_checker.h + theory/arith/nl/coverings/proof_generator.cpp + theory/arith/nl/coverings/proof_generator.h + theory/arith/nl/coverings/variable_ordering.cpp + theory/arith/nl/coverings/variable_ordering.h theory/arith/nl/equality_substitution.cpp theory/arith/nl/equality_substitution.h theory/arith/nl/ext/constraint.cpp diff --git a/src/options/arith_options.toml b/src/options/arith_options.toml index 5ec842f1a..d76f6c973 100644 --- a/src/options/arith_options.toml +++ b/src/options/arith_options.toml @@ -492,34 +492,34 @@ name = "Arithmetic Theory" help = "whether to use simple rounding, similar to a unit-cube test, for integers" [[option]] - name = "nlCad" + name = "nlCov" category = "regular" - long = "nl-cad" + long = "nl-cov" type = "bool" default = "false" help = "whether to use the cylindrical algebraic coverings solver for non-linear arithmetic" [[option]] - name = "nlCadVarElim" + name = "nlCovVarElim" category = "regular" - long = "nl-cad-var-elim" + long = "nl-cov-var-elim" type = "bool" default = "false" help = "whether to eliminate variables using equalities before going into the cylindrical algebraic coverings solver" [[option]] - name = "nlCadPrune" + name = "nlCovPrune" category = "regular" - long = "nl-cad-prune" + long = "nl-cov-prune" type = "bool" default = "false" help = "whether to prune intervals more agressively" [[option]] - name = "nlCadLinearModel" + name = "nlCovLinearModel" category = "regular" - long = "nl-cad-linear-model=MODE" - type = "NlCadLinearModelMode" + long = "nl-cov-linear-model=MODE" + type = "nlCovLinearModelMode" default = "NONE" help = "whether to use the linear model as initial guess for the cylindrical algebraic coverings solver" help_mode = "Modes for the usage of the linear model in non-linear arithmetic." @@ -534,13 +534,13 @@ name = "Arithmetic Theory" help = "Use linear model to seed nonlinear model whenever possible" [[option]] - name = "nlCadProjection" + name = "nlCovProjection" category = "expert" - long = "nl-cad-proj=MODE" - type = "NlCadProjectionMode" + long = "nl-cov-proj=MODE" + type = "nlCovProjectionMode" default = "MCCALLUM" - help = "choose the CAD projection operator" - help_mode = "Modes for the CAD projection operator in non-linear arithmetic." + help = "choose the Coverings projection operator" + help_mode = "Modes for the Coverings projection operator in non-linear arithmetic." [[option.mode.MCCALLUM]] name = "mccallum" help = "McCallum's projection operator." @@ -552,13 +552,13 @@ name = "Arithmetic Theory" help = "A modification of Lazard's projection operator." [[option]] - name = "nlCadLifting" + name = "nlCovLifting" category = "expert" - long = "nl-cad-lift=MODE" - type = "NlCadLiftingMode" + long = "nl-cov-lift=MODE" + type = "nlCovLiftingMode" default = "REGULAR" - help = "choose the CAD lifting mode" - help_mode = "Modes for the CAD lifting in non-linear arithmetic." + help = "choose the Coverings lifting mode" + help_mode = "Modes for the Coverings lifting in non-linear arithmetic." [[option.mode.REGULAR]] name = "regular" help = "Regular lifting." diff --git a/src/proof/lazy_tree_proof_generator.h b/src/proof/lazy_tree_proof_generator.h index 1863c739f..4f603fc2d 100644 --- a/src/proof/lazy_tree_proof_generator.h +++ b/src/proof/lazy_tree_proof_generator.h @@ -71,13 +71,13 @@ struct TreeProofNode * * Consider the example x*x<1 and x>2 and the intended proof * (SCOPE - * (ARITH_NL_CAD_SPLIT + * (ARITH_NL_COVERING_RECURSIVE * (SCOPE - * (ARITH_NL_CAD_DIRECT (x<=2 and x>2) ==> false) + * (ARITH_NL_COVERING_DIRECT (x<=2 and x>2) ==> false) * :args [x<=2] * ) * (SCOPE - * (ARITH_NL_CAD_DIRECT (x>=1 and x*x<1) ==> false) + * (ARITH_NL_COVERING_DIRECT (x>=1 and x*x<1) ==> false) * :args [x>=1] * ) * ) @@ -104,7 +104,7 @@ struct TreeProofNode * openChild(); * setCurrent(SCOPE, {}, {}, false); * openChild(); - * setCurrent(CAD_DIRECT, {x>2}, {}, false); + * setCurrent(ARITH_NL_COVERING_DIRECT, {x>2}, {}, false); * closeChild(); * getCurrent().args = {x<=2}; * closeChild(); @@ -112,12 +112,12 @@ struct TreeProofNode * openChild(); * setCurrent(SCOPE, {}, {}, false); * openChild(); - * setCurrent(CAD_DIRECT, {x*x<1}, {}, false); + * setCurrent(ARITH_NL_COVERING_DIRECT, {x*x<1}, {}, false); * closeChild(); * getCurrent().args = {x>=1}; * closeChild(); * // Finish split - * setCurrent(CAD_SPLIT, {}, {}, false); + * setCurrent(ARITH_NL_COVERING_RECURSIVE, {}, {}, false); * closeChild(); * closeChild(); * diff --git a/src/proof/proof_rule.cpp b/src/proof/proof_rule.cpp index 9bd714161..fd143350c 100644 --- a/src/proof/proof_rule.cpp +++ b/src/proof/proof_rule.cpp @@ -200,8 +200,8 @@ const char* toString(PfRule id) return "ARITH_TRANS_SINE_APPROX_BELOW_NEG"; case PfRule::ARITH_TRANS_SINE_APPROX_BELOW_POS: return "ARITH_TRANS_SINE_APPROX_BELOW_POS"; - case PfRule::ARITH_NL_CAD_DIRECT: return "ARITH_NL_CAD_DIRECT"; - case PfRule::ARITH_NL_CAD_RECURSIVE: return "ARITH_NL_CAD_RECURSIVE"; + case PfRule::ARITH_NL_COVERING_DIRECT: return "ARITH_NL_COVERING_DIRECT"; + case PfRule::ARITH_NL_COVERING_RECURSIVE: return "ARITH_NL_COVERING_RECURSIVE"; //================================================= External rules case PfRule::LFSC_RULE: return "LFSC_RULE"; case PfRule::ALETHE_RULE: return "ALETHE_RULE"; diff --git a/src/proof/proof_rule.h b/src/proof/proof_rule.h index eaa92c02c..5028cba1b 100644 --- a/src/proof/proof_rule.h +++ b/src/proof/proof_rule.h @@ -1379,7 +1379,7 @@ enum class PfRule : uint32_t // secant of p from l to u. ARITH_TRANS_SINE_APPROX_BELOW_POS, - // ================ CAD Lemmas + // ================ Coverings Lemmas // We use IRP for IndexedRootPredicate. // // A formula "Interval" describes that a variable (xn is none is given) is @@ -1396,7 +1396,7 @@ enum class PfRule : uint32_t // A formula "Covering" is a set of Intervals, implying that xn can be in // neither of these intervals. To be a covering (of the real line), the union // of these intervals should be the real numbers. - // ======== CAD direct conflict + // ======== Coverings direct conflict // Children (Cell, A) // --------------------- // Conclusion: (false) @@ -1404,15 +1404,15 @@ enum class PfRule : uint32_t // over a Cell (in variables x1...xn). It derives that A evaluates to false // over the Cell. In the actual algorithm, it means that xn can not be in the // topmost interval of the Cell. - ARITH_NL_CAD_DIRECT, - // ======== CAD recursive interval + ARITH_NL_COVERING_DIRECT, + // ======== Coverings recursive interval // Children (Cell, Covering) // --------------------- // Conclusion: (false) // A recursive interval is generated from a Covering (for xn) over a Cell // (in variables x1...xn-1). It generates the conclusion that no xn exists // that extends the Cell and satisfies all assumptions. - ARITH_NL_CAD_RECURSIVE, + ARITH_NL_COVERING_RECURSIVE, //================================================ Place holder for Lfsc rules // ======== Lfsc rule diff --git a/src/smt/set_defaults.cpp b/src/smt/set_defaults.cpp index c3008f2dd..bc5ab5f08 100644 --- a/src/smt/set_defaults.cpp +++ b/src/smt/set_defaults.cpp @@ -798,10 +798,10 @@ void SetDefaults::setDefaultsPost(const LogicInfo& logic, Options& opts) const #ifdef CVC5_USE_POLY if (logic == LogicInfo("QF_UFNRA")) { - if (!opts.arith.nlCad && !opts.arith.nlCadWasSetByUser) + if (!opts.arith.nlCov && !opts.arith.nlCovWasSetByUser) { - opts.arith.nlCad = true; - opts.arith.nlCadVarElim = true; + opts.arith.nlCov = true; + opts.arith.nlCovVarElim = true; if (!opts.arith.nlExtWasSetByUser) { opts.arith.nlExt = options::NlExtMode::LIGHT; @@ -815,10 +815,10 @@ void SetDefaults::setDefaultsPost(const LogicInfo& logic, Options& opts) const else if (logic.isQuantified() && logic.isTheoryEnabled(theory::THEORY_ARITH) && logic.areRealsUsed() && !logic.areIntegersUsed()) { - if (!opts.arith.nlCad && !opts.arith.nlCadWasSetByUser) + if (!opts.arith.nlCov && !opts.arith.nlCovWasSetByUser) { - opts.arith.nlCad = true; - opts.arith.nlCadVarElim = true; + opts.arith.nlCov = true; + opts.arith.nlCovVarElim = true; if (!opts.arith.nlExtWasSetByUser) { opts.arith.nlExt = options::NlExtMode::LIGHT; @@ -826,18 +826,18 @@ void SetDefaults::setDefaultsPost(const LogicInfo& logic, Options& opts) const } } #else - if (opts.arith.nlCad) + if (opts.arith.nlCov) { - if (opts.arith.nlCadWasSetByUser) + if (opts.arith.nlCovWasSetByUser) { throw OptionException( - "Cannot use --nl-cad without configuring with --poly."); + "Cannot use --nl-cov without configuring with --poly."); } else { - verbose(1) << "Cannot use --nl-cad without configuring with --poly." + verbose(1) << "Cannot use --nl-cov without configuring with --poly." << std::endl; - opts.arith.nlCad = false; + opts.arith.nlCov = false; opts.arith.nlExt = options::NlExtMode::FULL; } } diff --git a/src/theory/arith/nl/cad/cdcac.cpp b/src/theory/arith/nl/cad/cdcac.cpp deleted file mode 100644 index 989144729..000000000 --- a/src/theory/arith/nl/cad/cdcac.cpp +++ /dev/null @@ -1,768 +0,0 @@ -/****************************************************************************** - * Top contributors (to current version): - * Gereon Kremer, Aina Niemetz - * - * This file is part of the cvc5 project. - * - * Copyright (c) 2009-2021 by the authors listed in the file AUTHORS - * in the top-level source directory and their institutional affiliations. - * All rights reserved. See the file COPYING in the top-level source - * directory for licensing information. - * **************************************************************************** - * - * Implements the CDCAC approach as described in - * https://arxiv.org/pdf/2003.05633.pdf. - */ - -#include "theory/arith/nl/cad/cdcac.h" - -#ifdef CVC5_POLY_IMP - -#include "options/arith_options.h" -#include "theory/arith/nl/cad/lazard_evaluation.h" -#include "theory/arith/nl/cad/projections.h" -#include "theory/arith/nl/cad/variable_ordering.h" -#include "theory/arith/nl/nl_model.h" -#include "theory/rewriter.h" - -using namespace cvc5::kind; - -namespace std { -/** Generic streaming operator for std::vector. */ -template -std::ostream& operator<<(std::ostream& os, const std::vector& v) -{ - cvc5::container_to_stream(os, v); - return os; -} -} // namespace std - -namespace cvc5 { -namespace theory { -namespace arith { -namespace nl { -namespace cad { - -CDCAC::CDCAC(Env& env, const std::vector& ordering) - : EnvObj(env), d_variableOrdering(ordering) -{ - if (d_env.isTheoryProofProducing()) - { - d_proof.reset( - new CADProofGenerator(userContext(), d_env.getProofNodeManager())); - } -} - -void CDCAC::reset() -{ - d_constraints.reset(); - d_assignment.clear(); - d_nextIntervalId = 1; -} - -void CDCAC::computeVariableOrdering() -{ - // Actually compute the variable ordering - d_variableOrdering = d_varOrder(d_constraints.getConstraints(), - VariableOrderingStrategy::BROWN); - Trace("cdcac") << "Variable ordering is now " << d_variableOrdering - << std::endl; - - // Write variable ordering back to libpoly. - lp_variable_order_t* vo = poly::Context::get_context().get_variable_order(); - lp_variable_order_clear(vo); - for (const auto& v : d_variableOrdering) - { - lp_variable_order_push(vo, v.get_internal()); - } -} - -void CDCAC::retrieveInitialAssignment(NlModel& model, const Node& ran_variable) -{ - if (options().arith.nlCadLinearModel == options::NlCadLinearModelMode::NONE) return; - d_initialAssignment.clear(); - Trace("cdcac") << "Retrieving initial assignment:" << std::endl; - for (const auto& var : d_variableOrdering) - { - Node v = getConstraints().varMapper()(var); - Node val = model.computeConcreteModelValue(v); - poly::Value value = node_to_value(val, ran_variable); - Trace("cdcac") << "\t" << var << " = " << value << std::endl; - d_initialAssignment.emplace_back(value); - } -} -Constraints& CDCAC::getConstraints() { return d_constraints; } -const Constraints& CDCAC::getConstraints() const { return d_constraints; } - -const poly::Assignment& CDCAC::getModel() const { return d_assignment; } - -const std::vector& CDCAC::getVariableOrdering() const -{ - return d_variableOrdering; -} - -std::vector CDCAC::getUnsatIntervals(std::size_t cur_variable) -{ - std::vector res; - LazardEvaluation le; - prepareRootIsolation(le, cur_variable); - for (const auto& c : d_constraints.getConstraints()) - { - const poly::Polynomial& p = std::get<0>(c); - poly::SignCondition sc = std::get<1>(c); - const Node& n = std::get<2>(c); - - if (main_variable(p) != d_variableOrdering[cur_variable]) - { - // Constraint is in another variable, ignore it. - continue; - } - - Trace("cdcac") << "Infeasible intervals for " << p << " " << sc - << " 0 over " << d_assignment << std::endl; - std::vector intervals; - if (options().arith.nlCadLifting - == options::NlCadLiftingMode::LAZARD) - { - intervals = le.infeasibleRegions(p, sc); - if (Trace.isOn("cdcac")) - { - auto reference = poly::infeasible_regions(p, d_assignment, sc); - Trace("cdcac") << "Lazard: " << intervals << std::endl; - Trace("cdcac") << "Regular: " << reference << std::endl; - } - } - else - { - intervals = poly::infeasible_regions(p, d_assignment, sc); - } - for (const auto& i : intervals) - { - Trace("cdcac") << "-> " << i << std::endl; - PolyVector l, u, m, d; - m.add(p); - m.pushDownPolys(d, d_variableOrdering[cur_variable]); - if (!is_minus_infinity(get_lower(i))) l = m; - if (!is_plus_infinity(get_upper(i))) u = m; - res.emplace_back(CACInterval{d_nextIntervalId++, i, l, u, m, d, {n}}); - if (isProofEnabled()) - { - d_proof->addDirect( - d_constraints.varMapper()(d_variableOrdering[cur_variable]), - d_constraints.varMapper(), - p, - d_assignment, - sc, - i, - n, - res.back().d_id); - } - } - } - pruneRedundantIntervals(res); - return res; -} - -bool CDCAC::sampleOutsideWithInitial(const std::vector& infeasible, - poly::Value& sample, - std::size_t cur_variable) -{ - if (options().arith.nlCadLinearModel != options::NlCadLinearModelMode::NONE - && cur_variable < d_initialAssignment.size()) - { - const poly::Value& suggested = d_initialAssignment[cur_variable]; - for (const auto& i : infeasible) - { - if (poly::contains(i.d_interval, suggested)) - { - if (options().arith.nlCadLinearModel == options::NlCadLinearModelMode::INITIAL) - { - d_initialAssignment.clear(); - } - return sampleOutside(infeasible, sample); - } - } - Trace("cdcac") << "Using suggested initial value" << std::endl; - sample = suggested; - return true; - } - return sampleOutside(infeasible, sample); -} - -namespace { - -/** - * This method follows the projection operator as detailed in algorithm 6 of - * 10.1016/j.jlamp.2020.100633, which mostly follows the projection operator due - * to McCallum. It uses all coefficients until one is either constant or does - * not vanish over the current assignment. - */ -PolyVector requiredCoefficientsOriginal(const poly::Polynomial& p, - const poly::Assignment& assignment) -{ - PolyVector res; - for (long deg = degree(p); deg >= 0; --deg) - { - auto coeff = coefficient(p, deg); - Assert(poly::is_constant(coeff) - == lp_polynomial_is_constant(coeff.get_internal())); - if (poly::is_constant(coeff)) break; - res.add(coeff); - if (evaluate_constraint(coeff, assignment, poly::SignCondition::NE)) - { - break; - } - } - return res; -} - -/** - * This method follows the original projection operator due to Lazard from - * section 3 of 10.1007/978-1-4612-2628-4_29. It uses the leading and trailing - * coefficient, and makes some limited efforts to avoid them in certain cases: - * We avoid the leading coefficient if it is constant. We avoid the trailing - * coefficient if the leading coefficient does not vanish over the current - * assignment and the trailing coefficient is not constant. - */ -PolyVector requiredCoefficientsLazard(const poly::Polynomial& p, - const poly::Assignment& assignment) -{ - PolyVector res; - auto lc = poly::leading_coefficient(p); - if (poly::is_constant(lc)) return res; - res.add(lc); - if (evaluate_constraint(lc, assignment, poly::SignCondition::NE)) return res; - auto tc = poly::coefficient(p, 0); - if (poly::is_constant(tc)) return res; - res.add(tc); - return res; -} - -/** - * This method follows the enhancements from 10.1007/978-3-030-60026-6_8 for the - * projection operator due to Lazard, more specifically Section 3.3 and - * Definition 4. In essence, we can skip the trailing coefficient if we can show - * that p is not nullified by any n-1 dimensional point. The statement in the - * paper is slightly more general, but there is no simple way to have such a - * procedure T here. We simply try to show that T(f) = {} by using the extended - * rewriter to simplify (and (= f_i 0)) (f_i being the coefficients of f) to - * false. - */ -PolyVector requiredCoefficientsLazardModified( - const poly::Polynomial& p, - const poly::Assignment& assignment, - VariableMapper& vm, - Rewriter* rewriter) -{ - PolyVector res; - auto lc = poly::leading_coefficient(p); - // if leading coefficient is constant - if (poly::is_constant(lc)) return res; - // add leading coefficient - res.add(lc); - auto tc = poly::coefficient(p, 0); - // if trailing coefficient is constant - if (poly::is_constant(tc)) return res; - // if leading coefficient does not vanish over the current assignment - if (evaluate_constraint(lc, assignment, poly::SignCondition::NE)) return res; - - // construct phi := (and (= p_i 0)) with p_i the coefficients of p - std::vector conditions; - auto zero = NodeManager::currentNM()->mkConst(CONST_RATIONAL, Rational(0)); - for (const auto& coeff : poly::coefficients(p)) - { - conditions.emplace_back(NodeManager::currentNM()->mkNode( - Kind::EQUAL, nl::as_cvc_polynomial(coeff, vm), zero)); - } - // if phi is false (i.e. p can not vanish) - Node rewritten = - rewriter->extendedRewrite(NodeManager::currentNM()->mkAnd(conditions)); - if (rewritten.isConst()) - { - Assert(rewritten.getKind() == Kind::CONST_BOOLEAN); - Assert(!rewritten.getConst()); - return res; - } - // otherwise add trailing coefficient as well - res.add(tc); - return res; -} - -} // namespace - -PolyVector CDCAC::requiredCoefficients(const poly::Polynomial& p) -{ - if (Trace.isOn("cdcac::projection")) - { - Trace("cdcac::projection") - << "Poly: " << p << " over " << d_assignment << std::endl; - Trace("cdcac::projection") - << "Lazard: " << requiredCoefficientsLazard(p, d_assignment) - << std::endl; - Trace("cdcac::projection") - << "LMod: " - << requiredCoefficientsLazardModified( - p, d_assignment, d_constraints.varMapper(), d_env.getRewriter()) - << std::endl; - Trace("cdcac::projection") - << "Original: " << requiredCoefficientsOriginal(p, d_assignment) - << std::endl; - } - switch (options().arith.nlCadProjection) - { - case options::NlCadProjectionMode::MCCALLUM: - return requiredCoefficientsOriginal(p, d_assignment); - case options::NlCadProjectionMode::LAZARD: - return requiredCoefficientsLazard(p, d_assignment); - case options::NlCadProjectionMode::LAZARDMOD: - return requiredCoefficientsLazardModified( - p, d_assignment, d_constraints.varMapper(), d_env.getRewriter()); - default: - Assert(false); - return requiredCoefficientsOriginal(p, d_assignment); - } -} - -PolyVector CDCAC::constructCharacterization(std::vector& intervals) -{ - Assert(!intervals.empty()) << "A covering can not be empty"; - Trace("cdcac") << "Constructing characterization now" << std::endl; - PolyVector res; - - for (std::size_t i = 0, n = intervals.size(); i < n - 1; ++i) - { - cad::makeFinestSquareFreeBasis(intervals[i], intervals[i + 1]); - } - - for (const auto& i : intervals) - { - Trace("cdcac") << "Considering " << i.d_interval << std::endl; - Trace("cdcac") << "-> " << i.d_lowerPolys << " / " << i.d_upperPolys - << " and " << i.d_mainPolys << " / " << i.d_downPolys - << std::endl; - Trace("cdcac") << "-> " << i.d_origins << std::endl; - for (const auto& p : i.d_downPolys) - { - // Add all polynomial from lower levels. - res.add(p); - } - for (const auto& p : i.d_mainPolys) - { - Trace("cdcac::projection") - << "Discriminant of " << p << " -> " << discriminant(p) << std::endl; - // Add all discriminants - res.add(discriminant(p)); - - for (const auto& q : requiredCoefficients(p)) - { - // Add all required coefficients - Trace("cdcac::projection") - << "Coeff of " << p << " -> " << q << std::endl; - res.add(q); - } - for (const auto& q : i.d_lowerPolys) - { - if (p == q) continue; - // Check whether p(s \times a) = 0 for some a <= l - if (!hasRootBelow(q, get_lower(i.d_interval))) continue; - Trace("cdcac::projection") << "Resultant of " << p << " and " << q - << " -> " << resultant(p, q) << std::endl; - res.add(resultant(p, q)); - } - for (const auto& q : i.d_upperPolys) - { - if (p == q) continue; - // Check whether p(s \times a) = 0 for some a >= u - if (!hasRootAbove(q, get_upper(i.d_interval))) continue; - Trace("cdcac::projection") << "Resultant of " << p << " and " << q - << " -> " << resultant(p, q) << std::endl; - res.add(resultant(p, q)); - } - } - } - - for (std::size_t i = 0, n = intervals.size(); i < n - 1; ++i) - { - // Add resultants of consecutive intervals. - for (const auto& p : intervals[i].d_upperPolys) - { - for (const auto& q : intervals[i + 1].d_lowerPolys) - { - Trace("cdcac::projection") << "Resultant of " << p << " and " << q - << " -> " << resultant(p, q) << std::endl; - res.add(resultant(p, q)); - } - } - } - - res.reduce(); - res.makeFinestSquareFreeBasis(); - - return res; -} - -CACInterval CDCAC::intervalFromCharacterization( - const PolyVector& characterization, - std::size_t cur_variable, - const poly::Value& sample) -{ - PolyVector l; - PolyVector u; - PolyVector m; - PolyVector d; - - for (const auto& p : characterization) - { - // Add polynomials to main - m.add(p); - } - // Push lower-dimensional polys to down - m.pushDownPolys(d, d_variableOrdering[cur_variable]); - - // Collect -oo, all roots, oo - - LazardEvaluation le; - prepareRootIsolation(le, cur_variable); - std::vector roots; - roots.emplace_back(poly::Value::minus_infty()); - for (const auto& p : m) - { - Trace("cdcac") << "Isolating real roots of " << p << " over " - << d_assignment << std::endl; - - auto tmp = isolateRealRoots(le, p); - roots.insert(roots.end(), tmp.begin(), tmp.end()); - } - roots.emplace_back(poly::Value::plus_infty()); - std::sort(roots.begin(), roots.end()); - - // Now find the interval bounds - poly::Value lower; - poly::Value upper; - for (std::size_t i = 0, n = roots.size(); i < n; ++i) - { - if (sample < roots[i]) - { - lower = roots[i - 1]; - upper = roots[i]; - break; - } - if (roots[i] == sample) - { - lower = sample; - upper = sample; - break; - } - } - Assert(!is_none(lower) && !is_none(upper)); - - if (!is_minus_infinity(lower)) - { - // Identify polynomials that have a root at the lower bound - d_assignment.set(d_variableOrdering[cur_variable], lower); - for (const auto& p : m) - { - Trace("cdcac") << "Evaluating " << p << " = 0 over " << d_assignment - << std::endl; - if (evaluate_constraint(p, d_assignment, poly::SignCondition::EQ)) - { - l.add(p, true); - } - } - d_assignment.unset(d_variableOrdering[cur_variable]); - } - if (!is_plus_infinity(upper)) - { - // Identify polynomials that have a root at the upper bound - d_assignment.set(d_variableOrdering[cur_variable], upper); - for (const auto& p : m) - { - Trace("cdcac") << "Evaluating " << p << " = 0 over " << d_assignment - << std::endl; - if (evaluate_constraint(p, d_assignment, poly::SignCondition::EQ)) - { - u.add(p, true); - } - } - d_assignment.unset(d_variableOrdering[cur_variable]); - } - - if (lower == upper) - { - // construct a point interval - return CACInterval{d_nextIntervalId++, - poly::Interval(lower, false, upper, false), - l, - u, - m, - d, - {}}; - } - else - { - // construct an open interval - Assert(lower < upper); - return CACInterval{d_nextIntervalId++, - poly::Interval(lower, true, upper, true), - l, - u, - m, - d, - {}}; - } -} - -std::vector CDCAC::getUnsatCoverImpl(std::size_t curVariable, - bool returnFirstInterval) -{ - Trace("cdcac") << "Looking for unsat cover for " - << d_variableOrdering[curVariable] << std::endl; - std::vector intervals = getUnsatIntervals(curVariable); - - if (Trace.isOn("cdcac")) - { - Trace("cdcac") << "Unsat intervals for " << d_variableOrdering[curVariable] - << ":" << std::endl; - for (const auto& i : intervals) - { - Trace("cdcac") << "-> " << i.d_interval << " from " << i.d_origins - << std::endl; - } - } - poly::Value sample; - - while (sampleOutsideWithInitial(intervals, sample, curVariable)) - { - if (!checkIntegrality(curVariable, sample)) - { - // the variable is integral, but the sample is not. - Trace("cdcac") << "Used " << sample << " for integer variable " - << d_variableOrdering[curVariable] << std::endl; - auto newInterval = buildIntegralityInterval(curVariable, sample); - Trace("cdcac") << "Adding integrality interval " << newInterval.d_interval - << std::endl; - intervals.emplace_back(newInterval); - pruneRedundantIntervals(intervals); - continue; - } - d_assignment.set(d_variableOrdering[curVariable], sample); - Trace("cdcac") << "Sample: " << d_assignment << std::endl; - if (curVariable == d_variableOrdering.size() - 1) - { - // We have a full assignment. SAT! - Trace("cdcac") << "Found full assignment: " << d_assignment << std::endl; - return {}; - } - if (isProofEnabled()) - { - d_proof->startScope(); - d_proof->startRecursive(); - } - // Recurse to next variable - auto cov = getUnsatCoverImpl(curVariable + 1); - if (cov.empty()) - { - // Found SAT! - Trace("cdcac") << "SAT!" << std::endl; - return {}; - } - Trace("cdcac") << "Refuting Sample: " << d_assignment << std::endl; - auto characterization = constructCharacterization(cov); - Trace("cdcac") << "Characterization: " << characterization << std::endl; - - d_assignment.unset(d_variableOrdering[curVariable]); - - Trace("cdcac") << "Building interval..." << std::endl; - auto newInterval = - intervalFromCharacterization(characterization, curVariable, sample); - Trace("cdcac") << "New interval: " << newInterval.d_interval << std::endl; - newInterval.d_origins = collectConstraints(cov); - intervals.emplace_back(newInterval); - if (isProofEnabled()) - { - d_proof->endRecursive(newInterval.d_id); - auto cell = d_proof->constructCell( - d_constraints.varMapper()(d_variableOrdering[curVariable]), - newInterval, - d_assignment, - sample, - d_constraints.varMapper()); - d_proof->endScope(cell); - } - - if (returnFirstInterval) - { - return intervals; - } - - Trace("cdcac") << "Added " << intervals.back().d_interval << std::endl; - Trace("cdcac") << "\tlower: " << intervals.back().d_lowerPolys - << std::endl; - Trace("cdcac") << "\tupper: " << intervals.back().d_upperPolys - << std::endl; - Trace("cdcac") << "\tmain: " << intervals.back().d_mainPolys - << std::endl; - Trace("cdcac") << "\tdown: " << intervals.back().d_downPolys - << std::endl; - Trace("cdcac") << "\torigins: " << intervals.back().d_origins << std::endl; - - // Remove redundant intervals - pruneRedundantIntervals(intervals); - } - - if (Trace.isOn("cdcac")) - { - Trace("cdcac") << "Returning intervals for " - << d_variableOrdering[curVariable] << ":" << std::endl; - for (const auto& i : intervals) - { - Trace("cdcac") << "-> " << i.d_interval << std::endl; - } - } - return intervals; -} - -std::vector CDCAC::getUnsatCover(bool returnFirstInterval) -{ - if (isProofEnabled()) - { - d_proof->startRecursive(); - } - auto res = getUnsatCoverImpl(0, returnFirstInterval); - if (isProofEnabled()) - { - d_proof->endRecursive(0); - } - return res; -} - -void CDCAC::startNewProof() -{ - if (isProofEnabled()) - { - d_proof->startNewProof(); - } -} - -ProofGenerator* CDCAC::closeProof(const std::vector& assertions) -{ - if (isProofEnabled()) - { - d_proof->endScope(assertions); - return d_proof->getProofGenerator(); - } - return nullptr; -} - -bool CDCAC::checkIntegrality(std::size_t cur_variable, const poly::Value& value) -{ - Node var = d_constraints.varMapper()(d_variableOrdering[cur_variable]); - if (var.getType() != NodeManager::currentNM()->integerType()) - { - // variable is not integral - return true; - } - return poly::represents_integer(value); -} - -CACInterval CDCAC::buildIntegralityInterval(std::size_t cur_variable, - const poly::Value& value) -{ - poly::Variable var = d_variableOrdering[cur_variable]; - poly::Integer below = poly::floor(value); - poly::Integer above = poly::ceil(value); - // construct var \in (below, above) - return CACInterval{d_nextIntervalId++, - poly::Interval(below, above), - {var - below}, - {var - above}, - {var - below, var - above}, - {}, - {}}; -} - -bool CDCAC::hasRootAbove(const poly::Polynomial& p, - const poly::Value& val) const -{ - auto roots = poly::isolate_real_roots(p, d_assignment); - return std::any_of(roots.begin(), roots.end(), [&val](const poly::Value& r) { - return r >= val; - }); -} - -bool CDCAC::hasRootBelow(const poly::Polynomial& p, - const poly::Value& val) const -{ - auto roots = poly::isolate_real_roots(p, d_assignment); - return std::any_of(roots.begin(), roots.end(), [&val](const poly::Value& r) { - return r <= val; - }); -} - -void CDCAC::pruneRedundantIntervals(std::vector& intervals) -{ - cleanIntervals(intervals); - if (options().arith.nlCadPrune) - { - if (Trace.isOn("cdcac")) - { - auto copy = intervals; - removeRedundantIntervals(intervals); - if (copy.size() != intervals.size()) - { - Trace("cdcac") << "Before pruning:"; - for (const auto& i : copy) Trace("cdcac") << " " << i.d_interval; - Trace("cdcac") << std::endl; - Trace("cdcac") << "After pruning: "; - for (const auto& i : intervals) Trace("cdcac") << " " << i.d_interval; - Trace("cdcac") << std::endl; - } - } - else - { - removeRedundantIntervals(intervals); - } - } - if (isProofEnabled()) - { - d_proof->pruneChildren([&intervals](std::size_t id) { - if (id == 0) return false; - return std::find_if(intervals.begin(), - intervals.end(), - [id](const CACInterval& i) { return i.d_id == id; }) - == intervals.end(); - }); - } -} - -void CDCAC::prepareRootIsolation(LazardEvaluation& le, - size_t cur_variable) const -{ - if (options().arith.nlCadLifting == options::NlCadLiftingMode::LAZARD) - { - for (size_t vid = 0; vid < cur_variable; ++vid) - { - const auto& val = d_assignment.get(d_variableOrdering[vid]); - le.add(d_variableOrdering[vid], val); - } - le.addFreeVariable(d_variableOrdering[cur_variable]); - } -} - -std::vector CDCAC::isolateRealRoots( - LazardEvaluation& le, const poly::Polynomial& p) const -{ - if (options().arith.nlCadLifting == options::NlCadLiftingMode::LAZARD) - { - return le.isolateRealRoots(p); - } - return poly::isolate_real_roots(p, d_assignment); -} - -} // namespace cad -} // namespace nl -} // namespace arith -} // namespace theory -} // namespace cvc5 - -#endif diff --git a/src/theory/arith/nl/cad/cdcac.h b/src/theory/arith/nl/cad/cdcac.h deleted file mode 100644 index 8317c0813..000000000 --- a/src/theory/arith/nl/cad/cdcac.h +++ /dev/null @@ -1,246 +0,0 @@ -/****************************************************************************** - * Top contributors (to current version): - * Gereon Kremer - * - * This file is part of the cvc5 project. - * - * Copyright (c) 2009-2021 by the authors listed in the file AUTHORS - * in the top-level source directory and their institutional affiliations. - * All rights reserved. See the file COPYING in the top-level source - * directory for licensing information. - * **************************************************************************** - * - * Implements the CDCAC approach as described in - * https://arxiv.org/pdf/2003.05633.pdf. - */ - -#include "cvc5_private.h" - -#ifndef CVC5__THEORY__ARITH__NL__CAD__CDCAC_H -#define CVC5__THEORY__ARITH__NL__CAD__CDCAC_H - -#ifdef CVC5_POLY_IMP - -#include - -#include - -#include "smt/env.h" -#include "smt/env_obj.h" -#include "theory/arith/nl/cad/cdcac_utils.h" -#include "theory/arith/nl/cad/constraints.h" -#include "theory/arith/nl/cad/lazard_evaluation.h" -#include "theory/arith/nl/cad/proof_generator.h" -#include "theory/arith/nl/cad/variable_ordering.h" - -namespace cvc5 { -namespace theory { -namespace arith { -namespace nl { - -class NlModel; - -namespace cad { - -/** - * This class implements Cylindrical Algebraic Coverings as presented in - * https://arxiv.org/pdf/2003.05633.pdf - */ -class CDCAC : protected EnvObj -{ - public: - /** Initialize this method with the given variable ordering. */ - CDCAC(Env& env, const std::vector& ordering = {}); - - /** Reset this instance. */ - void reset(); - - /** Collect variables from the constraints and compute a variable ordering. */ - void computeVariableOrdering(); - - /** - * Extract an initial assignment from the given model. - * This initial assignment is used to guide sampling if possible. - * The ran_variable should be the variable used to encode real algebraic - * numbers in the model and is simply passed on to node_to_value. - */ - void retrieveInitialAssignment(NlModel& model, const Node& ran_variable); - - /** - * Returns the constraints as a non-const reference. Can be used to add new - * constraints. - */ - Constraints& getConstraints(); - /** Returns the constraints as a const reference. */ - const Constraints& getConstraints() const; - - /** - * Returns the current assignment. This is a satisfying model if - * get_unsat_cover() returned an empty vector. - */ - const poly::Assignment& getModel() const; - - /** Returns the current variable ordering. */ - const std::vector& getVariableOrdering() const; - - /** - * Collect all unsatisfiable intervals for the given variable. - * Combines unsatisfiable regions from d_constraints evaluated over - * d_assignment. Implements Algorithm 2. - */ - std::vector getUnsatIntervals(std::size_t cur_variable); - - /** - * Sample outside of the set of intervals. - * Uses a given initial value from mInitialAssignment if possible. - * Returns whether a sample was found (true) or the infeasible intervals cover - * the whole real line (false). - */ - bool sampleOutsideWithInitial(const std::vector& infeasible, - poly::Value& sample, - std::size_t cur_variable); - - /** - * Collects the coefficients required for projection from the given - * polynomial. Implements Algorithm 6, depending on the command line - * arguments. Either directly implements Algorithm 6, or improved variants - * based on Lazard's projection. - */ - PolyVector requiredCoefficients(const poly::Polynomial& p); - - /** - * Constructs a characterization of the given covering. - * A characterization contains polynomials whose roots bound the region around - * the current assignment. Implements Algorithm 4. - */ - PolyVector constructCharacterization(std::vector& intervals); - - /** - * Constructs an infeasible interval from a characterization. - * Implements Algorithm 5. - */ - CACInterval intervalFromCharacterization(const PolyVector& characterization, - std::size_t cur_variable, - const poly::Value& sample); - - /** - * Internal implementation of getUnsatCover(). - * @param curVariable The id of the variable (within d_variableOrdering) to - * be considered. This argument is used to manage the recursion internally and - * should always be zero if called externally. - * @param returnFirstInterval If true, the function returns after the first - * interval obtained from a recursive call. The result is not (necessarily) an - * unsat cover, but merely a list of infeasible intervals. - */ - std::vector getUnsatCoverImpl(std::size_t curVariable = 0, - bool returnFirstInterval = false); - - /** - * Main method that checks for the satisfiability of the constraints. - * Recursively explores possible assignments and excludes regions based on the - * coverings. Returns either a covering for the lowest dimension or an empty - * vector. If the covering is empty, the result is SAT and an assignment can - * be obtained from d_assignment. If the covering is not empty, the result is - * UNSAT and an infeasible subset can be extracted from the returned covering. - * Implements Algorithm 2. - * This method itself only takes care of the outermost proof scope and calls - * out to getUnsatCoverImpl() with curVariable set to zero. - * @param returnFirstInterval If true, the function returns after the first - * interval obtained from a recursive call. The result is not (necessarily) an - * unsat cover, but merely a list of infeasible intervals. - */ - std::vector getUnsatCover(bool returnFirstInterval = false); - - void startNewProof(); - /** - * Finish the generated proof (if proofs are enabled) with a scope over the - * given assertions. - */ - ProofGenerator* closeProof(const std::vector& assertions); - - /** Get the proof generator */ - CADProofGenerator* getProof() { return d_proof.get(); } - - private: - /** Check whether proofs are enabled */ - bool isProofEnabled() const { return d_proof != nullptr; } - - /** - * Check whether the current sample satisfies the integrality condition of the - * current variable. Returns true if the variable is not integral or the - * sample is integral. - */ - bool checkIntegrality(std::size_t cur_variable, const poly::Value& value); - /** - * Constructs an interval that excludes the non-integral region around the - * current sample. Assumes !check_integrality(cur_variable, value). - */ - CACInterval buildIntegralityInterval(std::size_t cur_variable, - const poly::Value& value); - - /** - * Check whether the polynomial has a real root above the given value (when - * evaluated over the current assignment). - */ - bool hasRootAbove(const poly::Polynomial& p, const poly::Value& val) const; - /** - * Check whether the polynomial has a real root below the given value (when - * evaluated over the current assignment). - */ - bool hasRootBelow(const poly::Polynomial& p, const poly::Value& val) const; - - /** - * Sort intervals according to section 4.4.1. and removes fully redundant - * intervals as in 4.5. 1. by calling out to cleanIntervals. - * Additionally makes sure to prune proofs for removed intervals. - */ - void pruneRedundantIntervals(std::vector& intervals); - - /** - * Prepare the lazard evaluation object with the current assignment, if the - * lazard lifting is enabled. Otherwise, this function does nothing. - */ - void prepareRootIsolation(LazardEvaluation& le, size_t cur_variable) const; - - /** - * Isolates the real roots of the polynomial `p`. If the lazard lifting is - * enabled, this function uses `le.isolateRealRoots()`, otherwise uses the - * regular `poly::isolate_real_roots()`. - */ - std::vector isolateRealRoots(LazardEvaluation& le, - const poly::Polynomial& p) const; - - /** - * The current assignment. When the method terminates with SAT, it contains a - * model for the input constraints. - */ - poly::Assignment d_assignment; - - /** The set of input constraints to be checked for consistency. */ - Constraints d_constraints; - - /** The computed variable ordering used for this method. */ - std::vector d_variableOrdering; - - /** The object computing the variable ordering. */ - VariableOrdering d_varOrder; - - /** The linear assignment used as an initial guess. */ - std::vector d_initialAssignment; - - /** The proof generator */ - std::unique_ptr d_proof; - - /** The next interval id */ - size_t d_nextIntervalId = 1; -}; - -} // namespace cad -} // namespace nl -} // namespace arith -} // namespace theory -} // namespace cvc5 - -#endif - -#endif diff --git a/src/theory/arith/nl/cad/cdcac_utils.cpp b/src/theory/arith/nl/cad/cdcac_utils.cpp deleted file mode 100644 index f5723a634..000000000 --- a/src/theory/arith/nl/cad/cdcac_utils.cpp +++ /dev/null @@ -1,466 +0,0 @@ -/****************************************************************************** - * Top contributors (to current version): - * Gereon Kremer - * - * This file is part of the cvc5 project. - * - * Copyright (c) 2009-2021 by the authors listed in the file AUTHORS - * in the top-level source directory and their institutional affiliations. - * All rights reserved. See the file COPYING in the top-level source - * directory for licensing information. - * **************************************************************************** - * - * Implements utilities for cdcac. - */ - -#include "theory/arith/nl/cad/cdcac_utils.h" - -#ifdef CVC5_POLY_IMP - -#include - -#include "theory/arith/nl/cad/projections.h" - -namespace cvc5 { -namespace theory { -namespace arith { -namespace nl { -namespace cad { - -using namespace poly; - -bool operator==(const CACInterval& lhs, const CACInterval& rhs) -{ - return lhs.d_interval == rhs.d_interval; -} -bool operator<(const CACInterval& lhs, const CACInterval& rhs) -{ - return lhs.d_interval < rhs.d_interval; -} - -namespace { -/** - * Induces an ordering on poly intervals that is suitable for redundancy - * removal as implemented in clean_intervals. - */ -bool compareForCleanup(const Interval& lhs, const Interval& rhs) -{ - const lp_value_t* ll = &(lhs.get_internal()->a); - const lp_value_t* lu = - lhs.get_internal()->is_point ? ll : &(lhs.get_internal()->b); - const lp_value_t* rl = &(rhs.get_internal()->a); - const lp_value_t* ru = - rhs.get_internal()->is_point ? rl : &(rhs.get_internal()->b); - - int lc = lp_value_cmp(ll, rl); - // Lower bound is smaller - if (lc < 0) return true; - // Lower bound is larger - if (lc > 0) return false; - // Lower bound type is smaller - if (!lhs.get_internal()->a_open && rhs.get_internal()->a_open) return true; - // Lower bound type is larger - if (lhs.get_internal()->a_open && !rhs.get_internal()->a_open) return false; - - // Attention: Here it differs from the regular interval ordering! - int uc = lp_value_cmp(lu, ru); - // Upper bound is smaller - if (uc < 0) return false; - // Upper bound is larger - if (uc > 0) return true; - // Upper bound type is smaller - if (lhs.get_internal()->b_open && !rhs.get_internal()->b_open) return false; - // Upper bound type is larger - if (!lhs.get_internal()->b_open && rhs.get_internal()->b_open) return true; - - // Identical - return false; -} - -/** - * Check whether lhs covers rhs. - */ -bool intervalCovers(const Interval& lhs, const Interval& rhs) -{ - const lp_value_t* ll = &(lhs.get_internal()->a); - const lp_value_t* lu = - lhs.get_internal()->is_point ? ll : &(lhs.get_internal()->b); - const lp_value_t* rl = &(rhs.get_internal()->a); - const lp_value_t* ru = - rhs.get_internal()->is_point ? rl : &(rhs.get_internal()->b); - - int lc = lp_value_cmp(ll, rl); - int uc = lp_value_cmp(lu, ru); - - // Lower bound is smaller and upper bound is larger - if (lc < 0 && uc > 0) return true; - // Lower bound is larger or upper bound is smaller - if (lc > 0 || uc < 0) return false; - - // Now both bounds are identical. - Assert(lc <= 0 && uc >= 0); - Assert(lc == 0 || uc == 0); - - // Lower bound is the same and the bound type is stricter - if (lc == 0 && lhs.get_internal()->a_open && !rhs.get_internal()->a_open) - return false; - // Upper bound is the same and the bound type is stricter - if (uc == 0 && lhs.get_internal()->b_open && !rhs.get_internal()->b_open) - return false; - - // Both bounds are weaker - return true; -} - -/** - * Check whether two intervals connect, assuming lhs < rhs. - * They connect, if their union has no gap. - */ -bool intervalConnect(const Interval& lhs, const Interval& rhs) -{ - Assert(lhs < rhs) << "Can only check for a connection if lhs < rhs."; - - const lp_value_t* lu = poly::get_upper(lhs).get_internal(); - const lp_value_t* rl = poly::get_lower(rhs).get_internal(); - - int c = lp_value_cmp(lu, rl); - if (c < 0) - { - Trace("libpoly::interval_connect") - << lhs << " and " << rhs << " are separated." << std::endl; - return false; - } - if (c > 0) - { - Trace("libpoly::interval_connect") - << lhs << " and " << rhs << " overlap." << std::endl; - return true; - } - Assert(c == 0); - if (poly::get_upper_open(lhs) && poly::get_lower_open(rhs)) - { - Trace("libpoly::interval_connect") - << lhs << " and " << rhs - << " touch and the intermediate point is not covered." << std::endl; - return false; - } - Trace("libpoly::interval_connect") - << lhs << " and " << rhs - << " touch and the intermediate point is covered." << std::endl; - return true; -} - -/** - * Check whether the union of a and b covers rhs. - * First check whether a and b connect, and then defer the containment check to - * intervalCovers. - */ -std::optional intervalsCover(const Interval& a, - const Interval& b, - const Interval& rhs) -{ - if (!intervalConnect(a, b)) return {}; - - Interval c(poly::get_lower(a), - poly::get_lower_open(a), - poly::get_upper(b), - poly::get_upper_open(b)); - - return intervalCovers(c, rhs); -} -} // namespace - -void cleanIntervals(std::vector& intervals) -{ - // Simplifies removal of redundancies later on. - if (intervals.size() < 2) return; - - if (Trace.isOn("cdcac")) - { - Trace("cdcac") << "Before pruning:" << std::endl; - for (const auto& i : intervals) - { - Trace("cdcac") << "\t[" << i.d_id << "] " << i.d_interval << std::endl; - } - } - - // Sort intervals. - std::sort(intervals.begin(), - intervals.end(), - [](const CACInterval& lhs, const CACInterval& rhs) { - return compareForCleanup(lhs.d_interval, rhs.d_interval); - }); - - // First remove intervals that are completely covered by a single other - // interval. This corresponds to removing "redundancies of the first kind" as - // of 4.5.1 The implementation roughly follows - // https://en.cppreference.com/w/cpp/algorithm/remove - std::size_t first = 0; - // Find first interval that is covered. - for (std::size_t n = intervals.size(); first < n - 1; ++first) - { - if (intervalCovers(intervals[first].d_interval, - intervals[first + 1].d_interval)) - { - break; - } - } - // If such an interval exists, remove accordingly. - if (first < intervals.size() - 1) - { - for (std::size_t i = first + 2, n = intervals.size(); i < n; ++i) - { - if (!intervalCovers(intervals[first].d_interval, intervals[i].d_interval)) - { - // Interval is not covered. Move it to the front and bump front. - ++first; - intervals[first] = std::move(intervals[i]); - } - // Else: Interval is covered as well. - } - // Erase trailing values - while (intervals.size() > first + 1) - { - intervals.pop_back(); - } - } - if (Trace.isOn("cdcac")) - { - Trace("cdcac") << "After pruning:" << std::endl; - for (const auto& i : intervals) - { - Trace("cdcac") << "\t[" << i.d_id << "] " << i.d_interval << std::endl; - } - } -} - -void removeRedundantIntervals(std::vector& intervals) -{ - // mid-1 -> interval below - // mid -> current interval - // right -> interval above - size_t mid = 1; - size_t right = 2; - size_t n = intervals.size(); - while (right < n) - { - bool found = false; - for (size_t r = right; r < n; ++r) - { - const auto& below = intervals[mid - 1].d_interval; - const auto& middle = intervals[mid].d_interval; - const auto& above = intervals[r].d_interval; - if (intervalsCover(below, above, middle)) - { - found = true; - break; - } - } - if (found) - { - intervals[mid] = std::move(intervals[right]); - } - else - { - ++mid; - if (mid < right) - { - intervals[mid] = std::move(intervals[right]); - } - } - ++right; - } - while (intervals.size() > mid + 1) - { - intervals.pop_back(); - } -} - -std::vector collectConstraints(const std::vector& intervals) -{ - std::vector res; - for (const auto& i : intervals) - { - res.insert(res.end(), i.d_origins.begin(), i.d_origins.end()); - } - std::sort(res.begin(), res.end()); - auto it = std::unique(res.begin(), res.end()); - res.erase(it, res.end()); - return res; -} - -bool sampleOutside(const std::vector& infeasible, Value& sample) -{ - if (infeasible.empty()) - { - // No infeasible region, just take anything: zero - sample = poly::Integer(); - return true; - } - if (!is_minus_infinity(get_lower(infeasible.front().d_interval))) - { - // First does not cover -oo, just take sufficiently low value - Trace("cdcac") << "Sample before " << infeasible.front().d_interval - << std::endl; - const auto* i = infeasible.front().d_interval.get_internal(); - sample = value_between( - Value::minus_infty().get_internal(), true, &i->a, !i->a_open); - return true; - } - for (std::size_t i = 0, n = infeasible.size(); i < n - 1; ++i) - { - // Search for two subsequent intervals that do not connect - if (!intervalConnect(infeasible[i].d_interval, - infeasible[i + 1].d_interval)) - { - // Two intervals do not connect, take something from the gap - const auto* l = infeasible[i].d_interval.get_internal(); - const auto* r = infeasible[i + 1].d_interval.get_internal(); - - Trace("cdcac") << "Sample between " << infeasible[i].d_interval << " and " - << infeasible[i + 1].d_interval << std::endl; - - if (l->is_point) - { - sample = value_between(&l->a, true, &r->a, !r->a_open); - } - else - { - sample = value_between(&l->b, !l->b_open, &r->a, !r->a_open); - } - return true; - } - else - { - Trace("cdcac") << infeasible[i].d_interval << " and " - << infeasible[i + 1].d_interval << " connect" << std::endl; - } - } - if (!is_plus_infinity(get_upper(infeasible.back().d_interval))) - { - // Last does not cover oo, just take something sufficiently large - Trace("cdcac") << "Sample above " << infeasible.back().d_interval - << std::endl; - const auto* i = infeasible.back().d_interval.get_internal(); - if (i->is_point) - { - sample = - value_between(&i->a, true, Value::plus_infty().get_internal(), true); - } - else - { - sample = value_between( - &i->b, !i->b_open, Value::plus_infty().get_internal(), true); - } - return true; - } - return false; -} - -namespace { -/** - * Replace a polynomial at polys[id] with the given pair of polynomials. - * Also update d_mainPolys in the given interval accordingly. - * If one of the factors (from the pair) is from a lower level (has a different - * main variable), push this factor to the d_downPolys. - * The first factor needs to be a proper polynomial (!is_constant(subst.first)), - * but the second factor may be anything. - */ -void replace_polynomial(PolyVector& polys, - std::size_t id, - std::pair subst, - CACInterval& interval) -{ - Assert(polys[id] == subst.first * subst.second); - Assert(!is_constant(subst.first)); - // Whether polys[id] has already been replaced - bool replaced = false; - poly::Variable var = main_variable(polys[id]); - // The position within interval.d_mainPolys to be replaced. - auto mit = std::find( - interval.d_mainPolys.begin(), interval.d_mainPolys.end(), polys[id]); - if (main_variable(subst.first) == var) - { - // Replace in polys[id] and *mit - polys[id] = subst.first; - if (mit != interval.d_mainPolys.end()) - { - *mit = subst.first; - } - replaced = true; - } - else - { - // Push to d_downPolys - interval.d_downPolys.add(subst.first); - } - // Skip constant poly - if (!is_constant(subst.second)) - { - if (main_variable(subst.second) == var) - { - if (replaced) - { - // Append to polys and d_mainPolys - polys.add(subst.second); - interval.d_mainPolys.add(subst.second); - } - else - { - // Replace in polys[id] and *mit - polys[id] = subst.second; - if (mit != interval.d_mainPolys.end()) - { - *mit = subst.second; - } - replaced = true; - } - } - else - { - // Push to d_downPolys - interval.d_downPolys.add(subst.second); - } - } - Assert(replaced) - << "At least one of the factors should have the main variable"; -} -} // namespace - -void makeFinestSquareFreeBasis(CACInterval& lhs, CACInterval& rhs) -{ - auto& l = lhs.d_upperPolys; - auto& r = rhs.d_lowerPolys; - if (l.empty()) return; - for (std::size_t i = 0, ln = l.size(); i < ln; ++i) - { - for (std::size_t j = 0, rn = r.size(); j < rn; ++j) - { - // All main variables should be the same - Assert(main_variable(l[i]) == main_variable(r[j])); - if (l[i] == r[j]) continue; - Polynomial g = gcd(l[i], r[j]); - if (!is_constant(g)) - { - auto newl = div(l[i], g); - auto newr = div(r[j], g); - replace_polynomial(l, i, std::make_pair(g, newl), lhs); - replace_polynomial(r, j, std::make_pair(g, newr), rhs); - } - } - } - l.reduce(); - r.reduce(); - lhs.d_mainPolys.reduce(); - rhs.d_mainPolys.reduce(); - lhs.d_downPolys.reduce(); - rhs.d_downPolys.reduce(); -} - -} // namespace cad -} // namespace nl -} // namespace arith -} // namespace theory -} // namespace cvc5 - -#endif diff --git a/src/theory/arith/nl/cad/cdcac_utils.h b/src/theory/arith/nl/cad/cdcac_utils.h deleted file mode 100644 index c53e8fbce..000000000 --- a/src/theory/arith/nl/cad/cdcac_utils.h +++ /dev/null @@ -1,115 +0,0 @@ -/****************************************************************************** - * Top contributors (to current version): - * Gereon Kremer - * - * This file is part of the cvc5 project. - * - * Copyright (c) 2009-2021 by the authors listed in the file AUTHORS - * in the top-level source directory and their institutional affiliations. - * All rights reserved. See the file COPYING in the top-level source - * directory for licensing information. - * **************************************************************************** - * - * Implements utilities for cdcac. - */ - -#include "cvc5_private.h" - -#ifndef CVC5__THEORY__ARITH__NL__CAD__CDCAC_UTILS_H -#define CVC5__THEORY__ARITH__NL__CAD__CDCAC_UTILS_H - -#ifdef CVC5_POLY_IMP - -#include - -#include - -#include "expr/node.h" -#include "theory/arith/nl/cad/projections.h" - -namespace cvc5 { -namespace theory { -namespace arith { -namespace nl { -namespace cad { - -/** - * An interval as specified in section 4.1 of - * https://arxiv.org/pdf/2003.05633.pdf. - * - * It consists of - * - the interval id, used to map the interval to its (partial) proof, - * - the actual interval, either an open or a point interal, - * - the characterizing polynomials of the lower and upper bound, - * - the characterizing polynomials in the main variable, - * - the characterizing polynomials in lower variables and - * - the constraints used to derive this interval. - */ -struct CACInterval -{ - /** Id of this interval to couple it to the proof */ - size_t d_id; - /** The actual interval. */ - poly::Interval d_interval; - /** The polynomials characterizing the lower bound. */ - PolyVector d_lowerPolys; - /** The polynomials characterizing the upper bound. */ - PolyVector d_upperPolys; - /** The characterizing polynomials in the main variable. */ - PolyVector d_mainPolys; - /** The characterizing polynomials in lower variables. */ - PolyVector d_downPolys; - /** The constraints used to derive this interval. */ - std::vector d_origins; -}; -/** Check whether to intervals are the same. */ -bool operator==(const CACInterval& lhs, const CACInterval& rhs); -/** Compare two intervals. */ -bool operator<(const CACInterval& lhs, const CACInterval& rhs); - -/** - * Sort intervals according to section 4.4.1. - * Also removes fully redundant intervals as in 4.5. 1.; these are intervals - * that are fully contained within a single other interval. - */ -void cleanIntervals(std::vector& intervals); - -/** - * Removes redundant intervals as in 4.5. 2.; these are intervals that are - * covered by two other intervals, but not by a single one. Assumes the - * intervals to be sorted and cleaned, i.e. that cleanIntervals(intervals) has - * been called beforehand. - */ -void removeRedundantIntervals(std::vector& intervals); - -/** - * Collect all origins from the list of intervals to construct the origins for a - * whole covering. - */ -std::vector collectConstraints(const std::vector& intervals); - -/** - * Sample a point outside of the infeasible intervals. - * Stores the sample in sample, returns whether such a sample exists. - * If false is returned, the infeasible intervals cover the real line. - * Implements sample_outside() from section 4.3 - */ -bool sampleOutside(const std::vector& infeasible, - poly::Value& sample); - -/** - * Compute the finest square of the upper polynomials of lhs and the lower - * polynomials of rhs. Also pushes reduced polynomials to lower level if - * necessary. - */ -void makeFinestSquareFreeBasis(CACInterval& lhs, CACInterval& rhs); - -} // namespace cad -} // namespace nl -} // namespace arith -} // namespace theory -} // namespace cvc5 - -#endif - -#endif diff --git a/src/theory/arith/nl/cad/constraints.cpp b/src/theory/arith/nl/cad/constraints.cpp deleted file mode 100644 index 628859a8f..000000000 --- a/src/theory/arith/nl/cad/constraints.cpp +++ /dev/null @@ -1,83 +0,0 @@ -/****************************************************************************** - * Top contributors (to current version): - * Gereon Kremer - * - * This file is part of the cvc5 project. - * - * Copyright (c) 2009-2021 by the authors listed in the file AUTHORS - * in the top-level source directory and their institutional affiliations. - * All rights reserved. See the file COPYING in the top-level source - * directory for licensing information. - * **************************************************************************** - * - * Implements a container for CAD constraints. - */ - -#include "theory/arith/nl/cad/constraints.h" - -#ifdef CVC5_POLY_IMP - -#include - -#include "theory/arith/nl/poly_conversion.h" -#include "util/poly_util.h" - -namespace cvc5 { -namespace theory { -namespace arith { -namespace nl { -namespace cad { - -void Constraints::addConstraint(const poly::Polynomial& lhs, - poly::SignCondition sc, - Node n) -{ - d_constraints.emplace_back(lhs, sc, n); - sortConstraints(); -} - -void Constraints::addConstraint(Node n) -{ - auto c = as_poly_constraint(n, d_varMapper); - addConstraint(c.first, c.second, n); - sortConstraints(); -} - -const Constraints::ConstraintVector& Constraints::getConstraints() const -{ - return d_constraints; -} - -void Constraints::reset() { d_constraints.clear(); } - -void Constraints::sortConstraints() -{ - using Tpl = std::tuple; - std::sort(d_constraints.begin(), - d_constraints.end(), - [](const Tpl& at, const Tpl& bt) { - // Check if a is smaller than b - const poly::Polynomial& a = std::get<0>(at); - const poly::Polynomial& b = std::get<0>(bt); - bool ua = is_univariate(a); - bool ub = is_univariate(b); - if (ua != ub) return ua; - std::size_t tda = poly_utils::totalDegree(a); - std::size_t tdb = poly_utils::totalDegree(b); - if (tda != tdb) return tda < tdb; - return degree(a) < degree(b); - }); - for (auto& c : d_constraints) - { - auto* p = std::get<0>(c).get_internal(); - lp_polynomial_set_external(p); - } -} - -} // namespace cad -} // namespace nl -} // namespace arith -} // namespace theory -} // namespace cvc5 - -#endif diff --git a/src/theory/arith/nl/cad/constraints.h b/src/theory/arith/nl/cad/constraints.h deleted file mode 100644 index 4321800f2..000000000 --- a/src/theory/arith/nl/cad/constraints.h +++ /dev/null @@ -1,93 +0,0 @@ -/****************************************************************************** - * Top contributors (to current version): - * Gereon Kremer - * - * This file is part of the cvc5 project. - * - * Copyright (c) 2009-2021 by the authors listed in the file AUTHORS - * in the top-level source directory and their institutional affiliations. - * All rights reserved. See the file COPYING in the top-level source - * directory for licensing information. - * **************************************************************************** - * - * Implements a container for CAD constraints. - */ - -#include "cvc5_private.h" - -#ifndef CVC5__THEORY__ARITH__NL__CAD__CONSTRAINTS_H -#define CVC5__THEORY__ARITH__NL__CAD__CONSTRAINTS_H - -#ifdef CVC5_POLY_IMP - -#include - -#include -#include - -#include "theory/arith/nl/poly_conversion.h" - -namespace cvc5 { -namespace theory { -namespace arith { -namespace nl { -namespace cad { - -class Constraints -{ - public: - /** Type alias for a list of constraints. */ - using Constraint = std::tuple; - using ConstraintVector = std::vector; - - VariableMapper& varMapper() { return d_varMapper; } - - /** - * Add a constraint (represented by a polynomial and a sign condition) to the - * list of constraints. - */ - void addConstraint(const poly::Polynomial& lhs, - poly::SignCondition sc, - Node n); - - /** - * Add a constraints (represented by a node) to the list of constraints. - * The given node can either be a negation (NOT) or a suitable relation symbol - * as checked by is_suitable_relation(). - */ - void addConstraint(Node n); - - /** - * Gives the list of added constraints. - */ - const ConstraintVector& getConstraints() const; - - /** - * Remove all constraints. - */ - void reset(); - - private: - /** - * A list of constraints, each comprised of a polynomial and a sign - * condition. - */ - ConstraintVector d_constraints; - - /** - * A mapping from cvc5 variables to poly variables. - */ - VariableMapper d_varMapper; - - void sortConstraints(); -}; - -} // namespace cad -} // namespace nl -} // namespace arith -} // namespace theory -} // namespace cvc5 - -#endif - -#endif diff --git a/src/theory/arith/nl/cad/lazard_evaluation.cpp b/src/theory/arith/nl/cad/lazard_evaluation.cpp deleted file mode 100644 index 91d775d16..000000000 --- a/src/theory/arith/nl/cad/lazard_evaluation.cpp +++ /dev/null @@ -1,995 +0,0 @@ -#include "theory/arith/nl/cad/lazard_evaluation.h" - -#ifdef CVC5_POLY_IMP - -#include "base/check.h" -#include "base/output.h" -#include "smt/smt_statistics_registry.h" -#include "util/statistics_stats.h" - -#ifdef CVC5_USE_COCOA - -#include - -#include - -namespace cvc5::theory::arith::nl::cad { - -struct LazardEvaluationStats -{ - IntStat d_directAssignments = - smtStatisticsRegistry().registerInt("theory::arith::cad::lazard-direct"); - IntStat d_ranAssignments = - smtStatisticsRegistry().registerInt("theory::arith::cad::lazard-rans"); - IntStat d_evaluations = - smtStatisticsRegistry().registerInt("theory::arith::cad::lazard-evals"); - IntStat d_reductions = - smtStatisticsRegistry().registerInt("theory::arith::cad::lazard-reduce"); -}; - -struct LazardEvaluationState; -std::ostream& operator<<(std::ostream& os, const LazardEvaluationState& state); - -/** - * This class holds and implements all the technicalities required to map - * polynomials from libpoly into CoCoALib, perform these computations properly - * within CoCoALib and map the result back to libpoly. - * - * We need to be careful to perform all computations in the proper polynomial - * rings, both to be correct and because CoCoALib explicitly requires it. As we - * change the ring we are computing it all the time, we also need appropriate - * ring homomorphisms to map polynomials from one into the other. We first give - * a short overview of our approach, then describe the various polynomial rings - * that are used, and then discuss which rings are used where. - * - * Inputs: - * - (real) variables x_0, ..., x_n - * - real algebraic numbers a_0, ..., a_{n-1} with - * - defining polynomials p_0, ..., p_{n-1}; p_i from Q[x_i] - * - a polynomial q over all variables x_0, ..., x_n - * - * We first iteratively build the field extensions Q(a_0), Q(a_0, a_2) ... - * Instead of the extension field Q(a_0), we use the isomorphic quotient ring - * Q[x_0]/ and recursively extend it with a_1, etc, in the same way. Doing - * this recursive construction naively fails: (Q[x_0]/)[x_1]/ is not - * necessarily a proper field as p_1 (though a minimal polynomial in Q[x_1]) may - * factor over Q[x_0]/. Consider p_0 = x_0*x_0-2 and p_1 = - * x_1*x_1*x_1*x_1-2 as an example, where p_1 factors into - * (x_1*x_1-x_0)*(x_1*x_1+x_0) over Q[x_0]/. We overcome this by explicitly - * computing this factorization and using the factor that vanishes over {x_0 -> - * a_0, x_1 -> a_1 } as the minimal polynomial of a_1 over Q[x_0]/. - * - * After we have built the field extensions in that way, we iteratively push q - * through the field extensions, each one extended to a polynomial ring over all - * x_0, ..., x_n. When in the k'th field extension, we check whether the k'th - * minimal polynomial divides q. If so, q would vanish in the next step and we - * instead set q = q/p_{k}. Only then we map q into K_{k+1}. - * - * Eventually, we end up with q in Q(a_0, ..., a_{n-1})[x_n]. This polynomial is - * univariate conceptually, and we want to compute its roots. However, it is not - * technically univariate and we need to make it so. We can do this by computing - * the Gröbner basis of the q and all minimal polynomials p_i with an - * elimination order with x_n at the bottom over Q[x_0, ..., x_n]. - * We then collect the polynomials - * that are univariate in x_n from the Gröbner basis. We can show that the roots - * of these polynomials are a superset of the roots we are looking for. - * - * - * To implement all that, we construct the following polynomial rings: - * - K_i: K_0 = Q, K_{i+1} = K_{i}[x_i]/ (with p_i reduced w.r.t. K_i) - * - R_i = K_i[x_i] - * - J_i = K_i[x_i, ..., x_n] = R_i[x_{i+1}, ..., x_n] - * - * While p_i conceptually live in Q[x_i], we immediately convert them from - * libpoly into R_i. We then factor it there, obtaining the actual minimal - * polynomial p_i that we use to construct K_{i+1}. We do this to construct all - * K_i and R_i. We then reduce q, initially in Q[x_0, ..., x_n] = J_0. We check - * in J_i whether p_i divides q (and if so divide q by p_i). To do - * this, we need to embed p_i into J_i. We then - * map q from J_i to J_{i+1}. While obvious in theory, this is somewhat tricky - * in practice as J_i and J_{i+1} have no direct relationship. - * Finally, we need to push all p_i and the final q back into J_0 = Q[x_0, ..., - * x_n] to compute the Gröbner basis. - * - * We thus furthermore store the following ring homomorphisms: - * - phom_i: R_i -> J_i (canonical embedding) - * - qhom_i: J_i -> J_{i+1} (hand-crafted homomorphism) - * - * We can sometimes avoid this construction for individual variables, i.e., if - * the assignment for x_i already lives (algebraically) in K_i. This can be the - * case if a_i is rational; in general, we check whether the vanishing factor - * of p_i is linear. If so, it has the form x_i-r where is some term in lower - * variables. We store r as the "direct assignment" in d_direct[i] and use it - * to directly replace x_i when appropriate. Also, we have K_i = K_{i-1}. - * - */ -struct LazardEvaluationState -{ - CoCoA::GlobalManager d_gm; - - /** - * Statistics about the lazard evaluation. - * Although this class is short-lived, there is no need to make the statistics - * static or store them persistently: this is handled by the statistics - * registry, which recovers statistics from their name. - * This member is mutable to allow collecting statistics from const methods. - */ - mutable LazardEvaluationStats d_stats; - - /** - * Maps libpoly variables to indets in J0. Used when constructing the input - * polynomial q in the first polynomial ring J0. - */ - std::map d_varQ; - /** - * Maps CoCoA indets back to to libpoly variables. - * Use when converting CoCoA RingElems to libpoly polynomials, either when - * checking whether a factor vanishes or when returning the univariate - * elements of the final Gröbner basis. The CoCoA indets are identified by the - * pair of the ring id and the indet identifier. Hence, we can put all of them - * in one map, no matter which ring they belong to. - */ - std::map, poly::Variable> d_varCoCoA; - - /** - * The minimal polynomials p_i used for constructing d_K. - * If a variable x_i has a rational assignment, p_i holds no value (i.e. - * d_p[i] == CoCoA::RingElem()). - */ - std::vector d_p; - - /** - * The sequence of extension fields. - * K_0 = Q, K_{i+1} = K_i[x_i]/ - * Every K_i is a field. - */ - std::vector d_K = {CoCoA::RingQQ()}; - /** - * R_i = K_i[x_i] - * Every R_i is a univariate polynomial ring over the field K_i. - */ - std::vector d_R; - /** - * J_i = K_i[x_i, ..., x_n] - * All J_i are constructed with CoCoA::lex ordering, just to make sure that - * the Gröbner basis of J_0 is computed as necessary. - */ - std::vector d_J; - - /** - * Custom homomorphism from R_i to J_i. PolyAlgebraHom with - * Indets(R_i) = (x_i) --> (x_i) - */ - std::vector d_phom; - /** - * Custom homomorphism from J_i to J_{i+1} - * If assignment of x_i is rational a PolyAlgebraHom with - * Indets(J_i) = (x_i,...,x_n) --> (a_i,x_{i+1},...,x_n) - * Otherwise a PolyRingHom with: - * - CoeffHom: K_{i-1} --> R_{i-1} --> K_i - * - (x_i,...,x_n) --> (x_i,x_{i+1},...,x_n), x_i = Indet(R_{i-1}) - */ - std::vector d_qhom; - - /** - * The base ideal for the Gröbner basis we compute in the end. Contains all - * p_i pushed into J_0. - */ - std::vector d_GBBaseIdeal; - - /** - * The current assignment, used to identify the vanishing factor to construct - * K_i. - */ - poly::Assignment d_assignment; - /** - * The libpoly variables in proper order. Directly correspond to x_0,...,x_n. - */ - std::vector d_variables; - /** - * Direct assignments for variables x_i as polynomials in lower variables. - * If the assignment for x_i is no direct assignment, d_direct[i] holds no - * value. - */ - std::vector> d_direct; - - /** - * Converts a libpoly integer to a CoCoA::BigInt. - */ - CoCoA::BigInt convert(const poly::Integer& i) const - { - return CoCoA::BigIntFromMPZ(poly::detail::cast_to_gmp(&i)->get_mpz_t()); - } - /** - * Converts a libpoly dyadic rational to a CoCoA::BigRat. - */ - CoCoA::BigRat convert(const poly::DyadicRational& dr) const - { - return CoCoA::BigRat(convert(poly::numerator(dr)), - convert(poly::denominator(dr))); - } - /** - * Converts a libpoly rational to a CoCoA::BigRat. - */ - CoCoA::BigRat convert(const poly::Rational& r) const - { - return CoCoA::BigRatFromMPQ(poly::detail::cast_to_gmp(&r)->get_mpq_t()); - } - /** - * Converts a univariate libpoly polynomial p in variable var to CoCoA. It - * assumes that p is a minimal polynomial p_i over variable x_i for the - * highest variable x_i known yet. It thus directly constructs p_i in R_i. - */ - CoCoA::RingElem convertMiPo(const poly::UPolynomial& p, - const poly::Variable& var) const - { - std::vector coeffs = poly::coefficients(p); - CoCoA::RingElem res(d_R.back()); - CoCoA::RingElem v = CoCoA::indet(d_R.back(), 0); - CoCoA::RingElem mult(d_R.back(), 1); - for (const auto& c : coeffs) - { - if (!poly::is_zero(c)) - { - res += convert(c) * mult; - } - mult *= v; - } - return res; - } - - /** - * Checks whether the given CoCoA polynomial evaluates to zero over the - * current libpoly assignment. The polynomial should live over the current - * R_i. - */ - bool evaluatesToZero(const CoCoA::RingElem& cp) const - { - Assert(CoCoA::owner(cp) == d_R.back()); - poly::Polynomial pp = convert(cp); - return poly::evaluate_constraint(pp, d_assignment, poly::SignCondition::EQ); - } - - /** - * Maps p from J_i to J_{i-1}. There can be no suitable homomorphism, and we - * thus manually decompose p into its terms and reconstruct them in J_{i-1}. - * If a_{i-1} is rational, we know that the coefficient rings of J_i and - * J_{i-1} are identical (K_{i-1} and K_{i-2}, respectively). We can thus - * immediately use coefficients from J_i as coefficients in J_{i-1}. - * Otherwise, we map coefficients from K_{i-1} to their canonical - * representation in R_{i-1} and then use d_phom[i-1] to map those into - * J_{i-1}. Afterwards, we iterate over the power product of the term - * reconstruct it in J_{i-1}. - */ - CoCoA::RingElem pushDownJ(const CoCoA::RingElem& p, size_t i) const - { - Trace("cad::lazard") << "Push " << p << " from " << d_J[i] << " to " - << d_J[i - 1] << std::endl; - Assert(CoCoA::owner(p) == d_J[i]); - CoCoA::RingElem res(d_J[i - 1]); - for (CoCoA::SparsePolyIter it = CoCoA::BeginIter(p); !CoCoA::IsEnded(it); - ++it) - { - CoCoA::RingElem coeff = CoCoA::coeff(it); - Assert(CoCoA::owner(coeff) == d_K[i]); - if (d_direct[i - 1]) - { - Assert(CoCoA::CoeffRing(d_J[i]) == CoCoA::CoeffRing(d_J[i - 1])); - coeff = CoCoA::CoeffEmbeddingHom(d_J[i - 1])(coeff); - } - else - { - coeff = CoCoA::CanonicalRepr(coeff); - Assert(CoCoA::owner(coeff) == d_R[i - 1]); - coeff = d_phom[i - 1](coeff); - } - Assert(CoCoA::owner(coeff) == d_J[i - 1]); - auto pp = CoCoA::PP(it); - std::vector indets = CoCoA::IndetsIn(pp); - for (size_t k = 0; k < indets.size(); ++k) - { - long exp = CoCoA::exponent(pp, indets[k]); - auto ind = CoCoA::indet(d_J[i - 1], indets[k] + 1); - coeff *= CoCoA::power(ind, exp); - } - res += coeff; - } - return res; - } - - /** - * Uses pushDownJ repeatedly to map p from J_{i+1} to J_0. - * Is used to map the minimal polynomials p_i and the reduced polynomial q - * into J_0 to eventually compute the Gröbner basis. - */ - CoCoA::RingElem pushDownJ0(const CoCoA::RingElem& p, size_t i) const - { - CoCoA::RingElem res = p; - for (; i > 0; --i) - { - Trace("cad::lazard") << "Pushing " << p << " from J" << i << " to J" - << i - 1 << std::endl; - res = pushDownJ(res, i); - } - return res; - } - - /** - * Add the next R_i: - * - add variable x_i to d_variables - * - extract the variable name - * - construct R_i = K_i[x_i] - * - add new variable to d_varCoCoA - */ - void addR(const poly::Variable& var) - { - d_variables.emplace_back(var); - if (Trace.isOn("cad::lazard")) - { - std::string vname = lp_variable_db_get_name( - poly::Context::get_context().get_variable_db(), var.get_internal()); - d_R.emplace_back(CoCoA::NewPolyRing(d_K.back(), {CoCoA::symbol(vname)})); - } - else - { - d_R.emplace_back(CoCoA::NewPolyRing(d_K.back(), {CoCoA::NewSymbol()})); - } - Trace("cad::lazard") << "R" << d_R.size() - 1 << " = " << d_R.back() - << std::endl; - d_varCoCoA.emplace(std::make_pair(CoCoA::RingID(d_R.back()), 0), var); - } - - /** - * Add the next K_{i+1} from a minimal polynomial: - * - store dummy value in d_direct - * - store the minimal polynomial p_i in d_p - * - construct K_{i+1} = R_i/ - */ - void addK(const poly::Variable& var, const CoCoA::RingElem& p) - { - d_direct.emplace_back(); - d_p.emplace_back(p); - Trace("cad::lazard") << "p" << d_p.size() - 1 << " = " << d_p.back() - << std::endl; - d_K.emplace_back(CoCoA::NewQuotientRing(d_R.back(), CoCoA::ideal(p))); - Trace("cad::lazard") << "K" << d_K.size() - 1 << " = " << d_K.back() - << std::endl; - } - - /** - * Add the next K_{i+1} from a rational assignment: - * - store assignment a_i in d_direct - * - store a dummy minimal polynomial in d_p - * - construct K_{i+1} as copy of K_i - */ - void addKRational(const poly::Variable& var, const CoCoA::RingElem& r) - { - d_direct.emplace_back(r); - d_p.emplace_back(); - Trace("cad::lazard") << "x" << d_p.size() - 1 << " = " << r << std::endl; - d_K.emplace_back(d_K.back()); - Trace("cad::lazard") << "K" << d_K.size() - 1 << " = " << d_K.back() - << std::endl; - } - - /** - * Finish the whole construction by adding the free variable: - * - add R_n by calling addR(var) - * - construct all J_i - * - construct all p homomorphisms (R_i --> J_i) - * - construct all q homomorphisms (J_i --> J_{i+1}) - * - fill the mapping d_varQ (libpoly -> J_0) - * - fill the mapping d_varCoCoA (J_n -> libpoly) - * - fill d_GBBaseIdeal with p_i mapped to J_0 - */ - void addFreeVariable(const poly::Variable& var) - { - Trace("cad::lazard") << "Add free variable " << var << std::endl; - addR(var); - std::vector symbols; - for (size_t i = 0; i < d_R.size(); ++i) - { - symbols.emplace_back(CoCoA::symbols(d_R[i]).back()); - } - for (size_t i = 0; i < d_R.size(); ++i) - { - d_J.emplace_back(CoCoA::NewPolyRing(d_K[i], symbols, CoCoA::lex)); - Trace("cad::lazard") << "J" << d_J.size() - 1 << " = " << d_J.back() - << std::endl; - symbols.erase(symbols.begin()); - // R_i --> J_i - d_phom.emplace_back( - CoCoA::PolyAlgebraHom(d_R[i], d_J[i], {CoCoA::indet(d_J[i], 0)})); - Trace("cad::lazard") << "R" << i << " --> J" << i << ": " << d_phom.back() - << std::endl; - if (i > 0) - { - Trace("cad::lazard") - << "Constructing J" << i - 1 << " --> J" << i << ": " << std::endl; - Trace("cad::lazard") << "Constructing " << d_J[i - 1] << " --> " - << d_J[i] << ": " << std::endl; - if (d_direct[i - 1]) - { - Trace("cad::lazard") << "Using " << d_variables[i - 1] << " for " - << CoCoA::indet(d_J[i - 1], 0) << std::endl; - Assert(CoCoA::CoeffRing(d_J[i]) == CoCoA::owner(*d_direct[i - 1])); - std::vector indets = { - CoCoA::RingElem(d_J[i], *d_direct[i - 1])}; - for (size_t j = 0; j < d_R.size() - i; ++j) - { - indets.push_back(CoCoA::indet(d_J[i], j)); - } - d_qhom.emplace_back( - CoCoA::PolyAlgebraHom(d_J[i - 1], d_J[i], indets)); - } - else - { - // K_{i-1} --> R_{i-1} - auto K2R = CoCoA::CoeffEmbeddingHom(d_R[i - 1]); - Assert(CoCoA::domain(K2R) == d_K[i - 1]); - Assert(CoCoA::codomain(K2R) == d_R[i - 1]); - // R_{i-1} --> K_i - auto R2K = CoCoA::QuotientingHom(d_K[i]); - Assert(CoCoA::domain(R2K) == d_R[i - 1]); - Assert(CoCoA::codomain(R2K) == d_K[i]); - // K_i --> J_i - auto K2J = CoCoA::CoeffEmbeddingHom(d_J[i]); - Assert(CoCoA::domain(K2J) == d_K[i]); - Assert(CoCoA::codomain(K2J) == d_J[i]); - // J_{i-1} --> J_i, consisting of - // - a homomorphism for the coefficients - // - a mapping for the indets - // Constructs [phom_i(x_i), x_i+1, ..., x_n] - std::vector indets = { - K2J(R2K(CoCoA::indet(d_R[i - 1], 0)))}; - for (size_t j = 0; j < d_R.size() - i; ++j) - { - indets.push_back(CoCoA::indet(d_J[i], j)); - } - d_qhom.emplace_back( - CoCoA::PolyRingHom(d_J[i - 1], d_J[i], R2K(K2R), indets)); - } - Trace("cad::lazard") << "J" << i - 1 << " --> J" << i << ": " - << d_qhom.back() << std::endl; - } - } - for (size_t i = 0; i < d_variables.size(); ++i) - { - d_varQ.emplace(d_variables[i], CoCoA::indet(d_J[0], i)); - } - for (size_t i = 0; i < d_variables.size(); ++i) - { - d_varCoCoA.emplace(std::make_pair(CoCoA::RingID(d_J[0]), i), - d_variables[i]); - } - - d_GBBaseIdeal.clear(); - for (size_t i = 0; i < d_p.size(); ++i) - { - if (d_direct[i]) continue; - Trace("cad::lazard") << "Apply " << d_phom[i] << " to " << d_p[i] - << " from " << CoCoA::owner(d_p[i]) << std::endl; - d_GBBaseIdeal.emplace_back(pushDownJ0(d_phom[i](d_p[i]), i)); - } - - Trace("cad::lazard") << "Finished construction" << std::endl - << *this << std::endl; - } - - /** - * Helper class for conversion from libpoly to CoCoA polynomials. - * The lambda can not capture anything, as it needs to be of type - * lp_polynomial_traverse_f. - */ - struct CoCoAPolyConstructor - { - const LazardEvaluationState& d_state; - CoCoA::RingElem d_result; - }; - - /** - * Convert the polynomial q to CoCoA into J_0. - */ - CoCoA::RingElem convertQ(const poly::Polynomial& q) const - { - CoCoAPolyConstructor cmd{*this}; - // Do the actual conversion - cmd.d_result = CoCoA::RingElem(d_J[0]); - lp_polynomial_traverse_f f = [](const lp_polynomial_context_t* ctx, - lp_monomial_t* m, - void* data) { - CoCoAPolyConstructor* d = static_cast(data); - CoCoA::BigInt coeff = d->d_state.convert(*poly::detail::cast_from(&m->a)); - CoCoA::RingElem re(d->d_state.d_J[0], coeff); - for (size_t i = 0; i < m->n; ++i) - { - // variable exponent pair - CoCoA::RingElem var = d->d_state.d_varQ.at(m->p[i].x); - re *= CoCoA::power(var, m->p[i].d); - } - d->d_result += re; - }; - lp_polynomial_traverse(q.get_internal(), f, &cmd); - return cmd.d_result; - } - /** - * Actual (recursive) implementation of converting a CoCoA polynomial to a - * libpoly polynomial. As libpoly polynomials only have integer coefficients, - * we need to maintain an integer denominator to normalize all terms to the - * same denominator. - */ - poly::Polynomial convertImpl(const CoCoA::RingElem& p, - poly::Integer& denominator) const - { - Trace("cad::lazard") << "Converting " << p << std::endl; - denominator = poly::Integer(1); - poly::Polynomial res; - for (CoCoA::SparsePolyIter i = CoCoA::BeginIter(p); !CoCoA::IsEnded(i); ++i) - { - poly::Polynomial coeff; - poly::Integer denom(1); - CoCoA::BigRat numcoeff; - if (CoCoA::IsRational(numcoeff, CoCoA::coeff(i))) - { - poly::Rational rat(mpq_class(CoCoA::mpqref(numcoeff))); - denom = poly::denominator(rat); - coeff = poly::numerator(rat); - } - else - { - coeff = convertImpl(CoCoA::CanonicalRepr(CoCoA::coeff(i)), denom); - } - if (!CoCoA::IsOne(CoCoA::PP(i))) - { - std::vector exponents; - CoCoA::exponents(exponents, CoCoA::PP(i)); - for (size_t vid = 0; vid < exponents.size(); ++vid) - { - if (exponents[vid] == 0) continue; - const auto& ring = CoCoA::owner(p); - poly::Variable v = - d_varCoCoA.at(std::make_pair(CoCoA::RingID(ring), vid)); - coeff *= poly::Polynomial(poly::Integer(1), v, exponents[vid]); - } - } - if (denom != denominator) - { - poly::Integer g = gcd(denom, denominator); - res = res * (denom / g) + coeff * (denominator / g); - denominator *= (denom / g); - } - else - { - res += coeff; - } - } - Trace("cad::lazard") << "-> " << res << std::endl; - return res; - } - /** - * Actually convert a CoCoA RingElem to a libpoly polynomial. - * Requires d_varCoCoA to be filled appropriately. - */ - poly::Polynomial convert(const CoCoA::RingElem& p) const - { - poly::Integer denom; - return convertImpl(p, denom); - } - - /** - * Now reduce the polynomial qpoly: - * - convert qpoly into J_0 and factor it - * - for every factor q: - * - for every x_i: - * - if a_i is rational: - * - while q[x_i -> a_i] == 0 - * - q = q / (x_i - a_i) - * - set q = q[x_i -> a_i] - * - otherwise - * - obtain tmp = phom_i(p_i) - * - while tmp divides q - * - q = q / tmp - * - embed q = qhom_i(q) - * - compute (reduced) GBasis(p_0, ..., p_{n-i}, q) - * - collect and convert basis elements univariate in the free variable - */ - std::vector reduce(const poly::Polynomial& qpoly) const - { - d_stats.d_evaluations++; - std::vector res; - Trace("cad::lazard") << "Reducing " << qpoly << std::endl; - auto input = convertQ(qpoly); - Assert(CoCoA::owner(input) == d_J[0]); - auto factorization = CoCoA::factor(input); - for (const auto& f : factorization.myFactors()) - { - Trace("cad::lazard") << "-> factor " << f << std::endl; - CoCoA::RingElem q = f; - for (size_t i = 0; i < d_J.size() - 1; ++i) - { - Trace("cad::lazard") << "i = " << i << std::endl; - if (d_direct[i]) - { - Trace("cad::lazard") - << "Substitute " << d_variables[i] << " = " << *d_direct[i] - << " into " << q << " from " << CoCoA::owner(q) << std::endl; - auto indets = CoCoA::indets(d_J[i]); - auto var = indets[0]; - Assert(CoCoA::CoeffRing(d_J[i]) == CoCoA::owner(*d_direct[i])); - indets[0] = CoCoA::RingElem(d_J[i], *d_direct[i]); - auto hom = CoCoA::PolyAlgebraHom(d_J[i], d_J[i], indets); - while (CoCoA::IsZero(hom(q))) - { - q = q / (var - indets[0]); - d_stats.d_reductions++; - } - // substitute x_i -> a_i - q = hom(q); - Trace("cad::lazard") - << "-> " << q << " from " << CoCoA::owner(q) << std::endl; - } - else - { - auto tmp = d_phom[i](d_p[i]); - while (CoCoA::IsDivisible(q, tmp)) - { - q /= tmp; - d_stats.d_reductions++; - } - } - q = d_qhom[i](q); - } - Trace("cad::lazard") << "-> reduced to " << q << std::endl; - Assert(CoCoA::owner(q) == d_J.back()); - std::vector ideal = d_GBBaseIdeal; - ideal.emplace_back(pushDownJ0(q, d_J.size() - 1)); - Trace("cad::lazard") << "-> ideal " << ideal << std::endl; - auto basis = CoCoA::ReducedGBasis(CoCoA::ideal(ideal)); - Trace("cad::lazard") << "-> basis " << basis << std::endl; - for (const auto& belem : basis) - { - Trace("cad::lazard") << "-> retrieved " << belem << std::endl; - auto pres = convert(belem); - Trace("cad::lazard") << "-> converted " << pres << std::endl; - // These checks are orthogonal! - if (poly::is_univariate(pres) - && poly::is_univariate_over_assignment(pres, d_assignment)) - { - res.emplace_back(pres); - } - } - } - return res; - } -}; - -std::ostream& operator<<(std::ostream& os, const LazardEvaluationState& state) -{ - for (size_t i = 0; i < state.d_K.size(); ++i) - { - os << "K" << i << " = " << state.d_K[i] << std::endl; - os << "R" << i << " = " << state.d_R[i] << std::endl; - os << "J" << i << " = " << state.d_J[i] << std::endl; - - os << "R" << i << " --> J" << i << ": " << state.d_phom[i] << std::endl; - if (i > 0) - { - os << "J" << (i - 1) << " --> J" << i << ": " << state.d_qhom[i - 1] - << std::endl; - } - } - os << "GBBaseIdeal: " << state.d_GBBaseIdeal << std::endl; - os << "Done" << std::endl; - return os; -} - -LazardEvaluation::LazardEvaluation() - : d_state(std::make_unique()) -{ -} - -LazardEvaluation::~LazardEvaluation() {} - -/** - * Add a new variable with real algebraic number: - * - add var = ran to the assignment - * - add the next R_i by calling addR(var) - * - if ran is actually rational: - * - obtain the rational and call addKRational() - * - otherwise: - * - convert the minimal polynomial and identify vanishing factor - * - add the next K_i with the vanishing factor by valling addK() - */ -void LazardEvaluation::add(const poly::Variable& var, const poly::Value& val) -{ - Trace("cad::lazard") << "Adding " << var << " -> " << val << std::endl; - try - { - d_state->d_assignment.set(var, val); - d_state->addR(var); - - std::optional rational; - poly::UPolynomial polymipo; - if (poly::is_algebraic_number(val)) - { - const poly::AlgebraicNumber& ran = poly::as_algebraic_number(val); - const poly::DyadicInterval& di = poly::get_isolating_interval(ran); - if (poly::is_point(di)) - { - rational = d_state->convert(poly::get_point(di)); - } - else - { - Trace("cad::lazard") << "\tis proper ran" << std::endl; - polymipo = poly::get_defining_polynomial(ran); - } - } - else - { - Assert(poly::is_dyadic_rational(val) || poly::is_integer(val) - || poly::is_rational(val)); - if (poly::is_dyadic_rational(val)) - { - rational = d_state->convert(poly::as_dyadic_rational(val)); - } - else if (poly::is_integer(val)) - { - rational = CoCoA::BigRat(d_state->convert(poly::as_integer(val)), 1); - } - else if (poly::is_rational(val)) - { - rational = d_state->convert(poly::as_rational(val)); - } - } - - if (rational) - { - d_state->addKRational(var, - CoCoA::RingElem(d_state->d_K.back(), *rational)); - d_state->d_stats.d_directAssignments++; - return; - } - Trace("cad::lazard") << "Got mipo " << polymipo << std::endl; - auto mipo = d_state->convertMiPo(polymipo, var); - Trace("cad::lazard") << "Factoring " << mipo << " from " - << CoCoA::owner(mipo) << std::endl; - auto factorization = CoCoA::factor(mipo); - Trace("cad::lazard") << "-> " << factorization << std::endl; - bool used_factor = false; - for (const auto& f : factorization.myFactors()) - { - if (d_state->evaluatesToZero(f)) - { - Assert(CoCoA::deg(f) > 0 && CoCoA::NumTerms(f) <= 2); - if (CoCoA::deg(f) == 1) - { - auto rat = -CoCoA::ConstantCoeff(f) / CoCoA::LC(f); - Trace("cad::lazard") << "Using linear factor " << f << " -> " << var - << " = " << rat << std::endl; - d_state->addKRational(var, rat); - d_state->d_stats.d_directAssignments++; - } - else - { - Trace("cad::lazard") << "Using nonlinear factor " << f << std::endl; - d_state->addK(var, f); - d_state->d_stats.d_ranAssignments++; - } - used_factor = true; - break; - } - else - { - Trace("cad::lazard") << "Skipping " << f << std::endl; - } - } - Assert(used_factor); - } - catch (CoCoA::ErrorInfo& e) - { - e.myOutputSelf(std::cerr); - throw; - } -} - -void LazardEvaluation::addFreeVariable(const poly::Variable& var) -{ - try - { - d_state->addFreeVariable(var); - } - catch (CoCoA::ErrorInfo& e) - { - e.myOutputSelf(std::cerr); - throw; - } -} - -std::vector LazardEvaluation::reducePolynomial( - const poly::Polynomial& p) const -{ - try - { - return d_state->reduce(p); - } - catch (CoCoA::ErrorInfo& e) - { - e.myOutputSelf(std::cerr); - throw; - } - return {p}; -} - -std::vector LazardEvaluation::isolateRealRoots( - const poly::Polynomial& q) const -{ - poly::Assignment a; - std::vector roots; - // reduce q to a set of reduced polynomials p - for (const auto& p : reducePolynomial(q)) - { - // collect all real roots except for -infty, none, +infty - Trace("cad::lazard") << "Isolating roots of " << p << std::endl; - Assert(poly::is_univariate(p) && poly::is_univariate_over_assignment(p, a)); - std::vector proots = poly::isolate_real_roots(p, a); - for (const auto& r : proots) - { - if (poly::is_minus_infinity(r)) continue; - if (poly::is_none(r)) continue; - if (poly::is_plus_infinity(r)) continue; - roots.emplace_back(r); - } - } - std::sort(roots.begin(), roots.end()); - return roots; -} - -/** - * Compute the infeasible regions of the given polynomial according to a sign - * condition. We first reduce the polynomial and isolate the real roots of every - * resulting polynomial. We store all roots (except for -infty, +infty and none) - * in a set. Then, we transform the set of roots into a list of infeasible - * regions by generating intervals between -infty and the first root, in between - * every two consecutive roots and between the last root and +infty. While doing - * this, we only keep those intervals that are actually infeasible for the - * original polynomial q over the partial assignment. Finally, we go over the - * intervals and aggregate consecutive intervals that connect. - */ -std::vector LazardEvaluation::infeasibleRegions( - const poly::Polynomial& q, poly::SignCondition sc) const -{ - std::vector roots = isolateRealRoots(q); - - // generate all intervals - // (-infty,root_0), [root_0], (root_0,root_1), ..., [root_m], (root_m,+infty) - // if q is true over d_assignment x interval (represented by a sample) - std::vector res; - poly::Value last = poly::Value::minus_infty(); - for (const auto& r : roots) - { - poly::Value sample = poly::value_between(last, true, r, true); - d_state->d_assignment.set(d_state->d_variables.back(), sample); - if (!poly::evaluate_constraint(q, d_state->d_assignment, sc)) - { - res.emplace_back(last, true, r, true); - } - d_state->d_assignment.set(d_state->d_variables.back(), r); - if (!poly::evaluate_constraint(q, d_state->d_assignment, sc)) - { - res.emplace_back(r); - } - last = r; - } - poly::Value sample = - poly::value_between(last, true, poly::Value::plus_infty(), true); - d_state->d_assignment.set(d_state->d_variables.back(), sample); - if (!poly::evaluate_constraint(q, d_state->d_assignment, sc)) - { - res.emplace_back(last, true, poly::Value::plus_infty(), true); - } - // clean up assignment - d_state->d_assignment.unset(d_state->d_variables.back()); - - Trace("cad::lazard") << "Shrinking:" << std::endl; - for (const auto& i : res) - { - Trace("cad::lazard") << "-> " << i << std::endl; - } - std::vector combined; - if (res.empty()) - { - // nothing to do if there are no intervals to start with - // return combined to simplify return value optimization - return combined; - } - for (size_t i = 0; i < res.size() - 1; ++i) - { - // Invariant: the intervals do not overlap. Check for our own sanity. - Assert(poly::get_upper(res[i]) <= poly::get_lower(res[i + 1])); - if (poly::get_upper_open(res[i]) && poly::get_lower_open(res[i + 1])) - { - // does not connect, both are open - combined.emplace_back(res[i]); - continue; - } - if (poly::get_upper(res[i]) != poly::get_lower(res[i + 1])) - { - // does not connect, there is some space in between - combined.emplace_back(res[i]); - continue; - } - // combine them into res[i+1], do not copy res[i] over to combined - Trace("cad::lazard") << "Combine " << res[i] << " and " << res[i + 1] - << std::endl; - Assert(poly::get_lower(res[i]) <= poly::get_lower(res[i + 1])); - res[i + 1].set_lower(poly::get_lower(res[i]), poly::get_lower_open(res[i])); - } - - // always use the last one, it is never dropped - combined.emplace_back(res.back()); - Trace("cad::lazard") << "To:" << std::endl; - for (const auto& i : combined) - { - Trace("cad::lazard") << "-> " << i << std::endl; - } - return combined; -} - -} // namespace cvc5::theory::arith::nl::cad - -#else - -namespace cvc5::theory::arith::nl::cad { - -/** - * Do a very simple wrapper around the regular poly::infeasible_regions. - * Warn the user about doing this. - * This allows for a graceful fallback (albeit with a warning) if CoCoA is not - * available. - */ -struct LazardEvaluationState -{ - poly::Assignment d_assignment; -}; -LazardEvaluation::LazardEvaluation() - : d_state(std::make_unique()) -{ -} -LazardEvaluation::~LazardEvaluation() {} - -void LazardEvaluation::add(const poly::Variable& var, const poly::Value& val) -{ - d_state->d_assignment.set(var, val); -} - -void LazardEvaluation::addFreeVariable(const poly::Variable& var) {} - -std::vector LazardEvaluation::reducePolynomial( - const poly::Polynomial& p) const -{ - return {p}; -} - -std::vector LazardEvaluation::isolateRealRoots( - const poly::Polynomial& q) const -{ - WarningOnce() - << "CAD::LazardEvaluation is disabled because CoCoA is not available. " - "Falling back to regular real root isolation." - << std::endl; - return poly::isolate_real_roots(q, d_state->d_assignment); -} -std::vector LazardEvaluation::infeasibleRegions( - const poly::Polynomial& q, poly::SignCondition sc) const -{ - WarningOnce() - << "CAD::LazardEvaluation is disabled because CoCoA is not available. " - "Falling back to regular calculation of infeasible regions." - << std::endl; - return poly::infeasible_regions(q, d_state->d_assignment, sc); -} - -} // namespace cvc5::theory::arith::nl::cad - -#endif -#endif diff --git a/src/theory/arith/nl/cad/lazard_evaluation.h b/src/theory/arith/nl/cad/lazard_evaluation.h deleted file mode 100644 index 2afccb462..000000000 --- a/src/theory/arith/nl/cad/lazard_evaluation.h +++ /dev/null @@ -1,117 +0,0 @@ -/****************************************************************************** - * Top contributors (to current version): - * Gereon Kremer - * - * This file is part of the cvc5 project. - * - * Copyright (c) 2009-2021 by the authors listed in the file AUTHORS - * in the top-level source directory and their institutional affiliations. - * All rights reserved. See the file COPYING in the top-level source - * directory for licensing information. - * **************************************************************************** - * - * Implements the CDCAC approach as described in - * https://arxiv.org/pdf/2003.05633.pdf. - */ - -#include "cvc5_private.h" - -#ifndef CVC5__THEORY__ARITH__NL__CAD__LAZARD_EVALUATION_H -#define CVC5__THEORY__ARITH__NL__CAD__LAZARD_EVALUATION_H - -#ifdef CVC5_POLY_IMP - -#include - -#include - -namespace cvc5::theory::arith::nl::cad { - -struct LazardEvaluationState; -/** - * This class does the heavy lifting for the modified lifting procedure that is - * required for Lazard's projection. - * - * Let p \in Q[x_0, ..., x_n] a multivariate polynomial whose roots we want to - * isolate over the partial sample point A = [x_0 = a_0, ... x_{n-1} = a_{n-1}] - * where a_0, ... a_{n-1} are real algebraic numbers and x_n is the last free - * variable. - * - * The modified lifting procedure conceptually works as follows: - * - * for (x = a) in A: - * while p[x // a] = 0: - * p = p / (x - a) - * p = p[x // a] - * return roots(p) - * - * As the assignment contains real algebraic numbers, though, we can not do any - * of the computations directly, as our polynomials only support coefficients - * from Z or Q, but not extensions (in the algebraic sense) thereof. - * - * Our approach is as follows: - * Let pk be the minimal polynomial for a_k. - * Instead of substituting p[x_k // a_k] we (canonically) embed p into the - * quotient ring Q[x_k]/ and recursively build a tower of such quotient - * rings that is isomorphic to nesting the corresponding field extensions - * Q(a_1)(a_2)... When we have done that, we obtain p that is reduced with - * respect to all minimal polynomials, but may still contain x_0,... x_{n-1}. To - * get rid of these, we compute a Gröbner basis of p and the minimal polynomials - * (using a suitable elimination order) and extract the polynomial in x_n. This - * polynomial has all roots (and possibly some more) that we are looking for. - * Instead of a Gröbner basis, we can also compute the iterated resultant as - * follows: Res(Res(p, p_{n-1}, x_{n-1}), p_{n-2}, x_{n-2})... - * - * Consider - * http://sunsite.informatik.rwth-aachen.de/Publications/AIB/2020/2020-04.pdf - * Section 2.5.1 for a full discussion. - * - * !!! WARNING !!! - * If CoCoALib is not available, this class will simply fall back to - * poly::infeasible_regions and issue a warning about this. - */ -class LazardEvaluation -{ - public: - LazardEvaluation(); - ~LazardEvaluation(); - - /** - * Add the next assigned variable x_k = a_k to this construction. - */ - void add(const poly::Variable& var, const poly::Value& val); - /** - * Finish by adding the free variable x_n. - */ - void addFreeVariable(const poly::Variable& var); - /** - * Reduce the polynomial q. Compared to the above description, there may - * actually be multiple polynomials in the Gröbner basis and instead of - * loosing this knowledge and returning their product, we return them as a - * vector. - */ - std::vector reducePolynomial( - const poly::Polynomial& q) const; - - /** - * Isolates the real roots of the given polynomials. - */ - std::vector isolateRealRoots(const poly::Polynomial& q) const; - - /** - * Compute the infeasible regions of q under the given sign condition. - * Uses reducePolynomial and then performs real root isolation on the - * resulting polynomials to obtain the intervals. Mimics - * poly::infeasible_regions, but uses Lazard's evaluation. - */ - std::vector infeasibleRegions(const poly::Polynomial& q, - poly::SignCondition sc) const; - - private: - std::unique_ptr d_state; -}; - -} // namespace cvc5::theory::arith::nl::cad - -#endif -#endif diff --git a/src/theory/arith/nl/cad/projections.cpp b/src/theory/arith/nl/cad/projections.cpp deleted file mode 100644 index 896d8da89..000000000 --- a/src/theory/arith/nl/cad/projections.cpp +++ /dev/null @@ -1,110 +0,0 @@ -/****************************************************************************** - * Top contributors (to current version): - * Gereon Kremer - * - * This file is part of the cvc5 project. - * - * Copyright (c) 2009-2021 by the authors listed in the file AUTHORS - * in the top-level source directory and their institutional affiliations. - * All rights reserved. See the file COPYING in the top-level source - * directory for licensing information. - * **************************************************************************** - * - * Implements utilities for CAD projection operators. - */ - -#include "theory/arith/nl/cad/projections.h" - -#ifdef CVC5_POLY_IMP - -#include "base/check.h" - -namespace cvc5 { -namespace theory { -namespace arith { -namespace nl { -namespace cad { - -using namespace poly; - -void PolyVector::add(const poly::Polynomial& poly, bool assertMain) -{ - for (const auto& p : poly::square_free_factors(poly)) - { - if (poly::is_constant(p)) continue; - if (assertMain) - { - Assert(main_variable(poly) == main_variable(p)); - } - std::vector::emplace_back(p); - } -} - -void PolyVector::reduce() -{ - std::sort(begin(), end()); - erase(std::unique(begin(), end()), end()); -} - -void PolyVector::makeFinestSquareFreeBasis() -{ - for (std::size_t i = 0, n = size(); i < n; ++i) - { - for (std::size_t j = i + 1; j < n; ++j) - { - Polynomial g = gcd((*this)[i], (*this)[j]); - if (!is_constant(g)) - { - (*this)[i] = div((*this)[i], g); - (*this)[j] = div((*this)[j], g); - add(g); - } - } - } - auto it = std::remove_if( - begin(), end(), [](const Polynomial& p) { return is_constant(p); }); - erase(it, end()); - reduce(); -} -void PolyVector::pushDownPolys(PolyVector& down, poly::Variable var) -{ - auto it = - std::remove_if(begin(), end(), [&down, &var](const poly::Polynomial& p) { - if (main_variable(p) == var) return false; - down.add(p); - return true; - }); - erase(it, end()); -} - -PolyVector projectionMcCallum(const std::vector& polys) -{ - PolyVector res; - - for (const auto& p : polys) - { - for (const auto& coeff : coefficients(p)) - { - res.add(coeff); - } - res.add(discriminant(p)); - } - for (std::size_t i = 0, n = polys.size(); i < n; ++i) - { - for (std::size_t j = i + 1; j < n; ++j) - { - res.add(resultant(polys[i], polys[j])); - } - } - - res.reduce(); - return res; -} - -} // namespace cad -} // namespace nl -} // namespace arith -} // namespace theory -} // namespace cvc5 - -#endif diff --git a/src/theory/arith/nl/cad/projections.h b/src/theory/arith/nl/cad/projections.h deleted file mode 100644 index 129c3515a..000000000 --- a/src/theory/arith/nl/cad/projections.h +++ /dev/null @@ -1,82 +0,0 @@ -/****************************************************************************** - * Top contributors (to current version): - * Gereon Kremer - * - * This file is part of the cvc5 project. - * - * Copyright (c) 2009-2021 by the authors listed in the file AUTHORS - * in the top-level source directory and their institutional affiliations. - * All rights reserved. See the file COPYING in the top-level source - * directory for licensing information. - * **************************************************************************** - * - * Implements utilities for CAD projection operators. - */ - -#include "cvc5_private.h" - -#ifndef CVC5__THEORY__ARITH__NL__CAD_PROJECTIONS_H -#define CVC5__THEORY__ARITH__NL__CAD_PROJECTIONS_H - -#ifdef CVC5_USE_POLY - -#include - -#include - -namespace cvc5 { -namespace theory { -namespace arith { -namespace nl { -namespace cad { - -/** - * A simple wrapper around std::vector that ensures that all - * polynomials are properly factorized and pruned when added to the list. - */ -class PolyVector : public std::vector -{ - private: - /** Disable all emplace() */ - void emplace() {} - /** Disable all emplace_back() */ - void emplace_back() {} - /** Disable all insert() */ - void insert() {} - /** Disable all push_back() */ - void push_back() {} - - public: - PolyVector() {} - /** Construct from a set of polynomials */ - PolyVector(std::initializer_list i) - { - for (const auto& p : i) add(p); - } - /** - * Adds a polynomial to the list of projection polynomials. - * Before adding, it factorizes the polynomials and removed constant factors. - */ - void add(const poly::Polynomial& poly, bool assertMain = false); - /** Sort and remove duplicates from the list of polynomials. */ - void reduce(); - /** Make this list of polynomials a finest square-free basis. */ - void makeFinestSquareFreeBasis(); - /** Push polynomials with a lower main variable to another PolyVector. */ - void pushDownPolys(PolyVector& down, poly::Variable var); -}; - -/** - * Computes McCallum's projection operator. - */ -PolyVector projectionMcCallum(const std::vector& polys); - -} // namespace cad -} // namespace nl -} // namespace arith -} // namespace theory -} // namespace cvc5 - -#endif - -#endif diff --git a/src/theory/arith/nl/cad/proof_checker.cpp b/src/theory/arith/nl/cad/proof_checker.cpp deleted file mode 100644 index 562b57722..000000000 --- a/src/theory/arith/nl/cad/proof_checker.cpp +++ /dev/null @@ -1,62 +0,0 @@ -/****************************************************************************** - * Top contributors (to current version): - * Gereon Kremer, Aina Niemetz - * - * This file is part of the cvc5 project. - * - * Copyright (c) 2009-2021 by the authors listed in the file AUTHORS - * in the top-level source directory and their institutional affiliations. - * All rights reserved. See the file COPYING in the top-level source - * directory for licensing information. - * **************************************************************************** - * - * Implementation of CAD proof checker. - */ - -#include "theory/arith/nl/cad/proof_checker.h" - -#include "expr/sequence.h" -#include "theory/rewriter.h" - -using namespace cvc5::kind; - -namespace cvc5 { -namespace theory { -namespace arith { -namespace nl { -namespace cad { - -void CADProofRuleChecker::registerTo(ProofChecker* pc) -{ - // trusted rules - pc->registerTrustedChecker(PfRule::ARITH_NL_CAD_DIRECT, this, 2); - pc->registerTrustedChecker(PfRule::ARITH_NL_CAD_RECURSIVE, this, 2); -} - -Node CADProofRuleChecker::checkInternal(PfRule id, - const std::vector& children, - const std::vector& args) -{ - Trace("nl-cad-checker") << "Checking " << id << std::endl; - for (const auto& c : children) - { - Trace("nl-cad-checker") << "\t" << c << std::endl; - } - if (id == PfRule::ARITH_NL_CAD_DIRECT) - { - Assert(args.size() == 1); - return args[0]; - } - if (id == PfRule::ARITH_NL_CAD_RECURSIVE) - { - Assert(args.size() == 1); - return args[0]; - } - return Node::null(); -} - -} // namespace cad -} // namespace nl -} // namespace arith -} // namespace theory -} // namespace cvc5 diff --git a/src/theory/arith/nl/cad/proof_checker.h b/src/theory/arith/nl/cad/proof_checker.h deleted file mode 100644 index 8cc544fc4..000000000 --- a/src/theory/arith/nl/cad/proof_checker.h +++ /dev/null @@ -1,59 +0,0 @@ -/****************************************************************************** - * Top contributors (to current version): - * Gereon Kremer - * - * This file is part of the cvc5 project. - * - * Copyright (c) 2009-2021 by the authors listed in the file AUTHORS - * in the top-level source directory and their institutional affiliations. - * All rights reserved. See the file COPYING in the top-level source - * directory for licensing information. - * **************************************************************************** - * - * CAD proof checker utility. - */ - -#include "cvc5_private.h" - -#ifndef CVC5__THEORY__ARITH__NL__CAD__PROOF_CHECKER_H -#define CVC5__THEORY__ARITH__NL__CAD__PROOF_CHECKER_H - -#include "expr/node.h" -#include "proof/proof_checker.h" - -namespace cvc5 { -namespace theory { -namespace arith { -namespace nl { -namespace cad { - -/** - * A checker for CAD proofs - * - * This proof checker takes care of the two CAD proof rules ARITH_NL_CAD_DIRECT - * and ARITH_NL_CAD_RECURSIVE. It does not do any actual proof checking yet, but - * considers them to be trusted rules. - */ -class CADProofRuleChecker : public ProofRuleChecker -{ - public: - CADProofRuleChecker() {} - ~CADProofRuleChecker() {} - - /** Register all rules owned by this rule checker in pc. */ - void registerTo(ProofChecker* pc) override; - - protected: - /** Return the conclusion of the given proof step, or null if it is invalid */ - Node checkInternal(PfRule id, - const std::vector& children, - const std::vector& args) override; -}; - -} // namespace cad -} // namespace nl -} // namespace arith -} // namespace theory -} // namespace cvc5 - -#endif /* CVC5__THEORY__STRINGS__PROOF_CHECKER_H */ diff --git a/src/theory/arith/nl/cad/proof_generator.cpp b/src/theory/arith/nl/cad/proof_generator.cpp deleted file mode 100644 index 1e3c52202..000000000 --- a/src/theory/arith/nl/cad/proof_generator.cpp +++ /dev/null @@ -1,247 +0,0 @@ -/****************************************************************************** - * Top contributors (to current version): - * Gereon Kremer - * - * This file is part of the cvc5 project. - * - * Copyright (c) 2009-2021 by the authors listed in the file AUTHORS - * in the top-level source directory and their institutional affiliations. - * All rights reserved. See the file COPYING in the top-level source - * directory for licensing information. - * **************************************************************************** - * - * Implementation of CAD proof generator. - */ - -#include "theory/arith/nl/cad/proof_generator.h" - -#ifdef CVC5_POLY_IMP - -#include "proof/lazy_tree_proof_generator.h" -#include "theory/arith/nl/poly_conversion.h" -#include "util/indexed_root_predicate.h" - -using namespace cvc5::kind; - -namespace cvc5 { -namespace theory { -namespace arith { -namespace nl { -namespace cad { - -namespace { -/** - * Retrieves the root indices of the sign-invariant region of v. - * - * We assume that roots holds a sorted list of roots from one polynomial. - * If v is equal to one of these roots, we return (id,id) where id is the index - * of this root within roots. Otherwise, we return the id of the largest root - * below v and the id of the smallest root above v. To make sure a smaller root - * and a larger root always exist, we implicitly extend the roots by -infty and - * infty. - * - * ATTENTION: if we return id, the corresponding root is: - * - id = 0: -infty - * - 0 < id <= roots.size(): roots[id-1] - * - id = roots.size() + 1: infty - */ -std::pair getRootIDs( - const std::vector& roots, const poly::Value& v) -{ - for (std::size_t i = 0; i < roots.size(); ++i) - { - if (roots[i] == v) - { - return {i + 1, i + 1}; - } - if (roots[i] > v) - { - return {i, i + 1}; - } - } - return {roots.size(), roots.size() + 1}; -} - -/** - * Constructs an IndexedRootExpression: - * var ~rel~ root_k(poly) - * where root_k(poly) is "the k'th root of the polynomial". - * - * @param var The variable that is bounded - * @param rel The relation for this constraint - * @param zero A node representing Rational(0) - * @param k The index of the root (starting with 1) - * @param poly The polynomial whose root shall be considered - * @param vm A variable mapper from cvc5 to libpoly variables - */ -Node mkIRP(const Node& var, - Kind rel, - const Node& zero, - std::size_t k, - const poly::Polynomial& poly, - VariableMapper& vm) -{ - auto* nm = NodeManager::currentNM(); - auto op = nm->mkConst(IndexedRootPredicate(k)); - return nm->mkNode(Kind::INDEXED_ROOT_PREDICATE, - op, - nm->mkNode(rel, var, zero), - as_cvc_polynomial(poly, vm)); -} - -} // namespace - -CADProofGenerator::CADProofGenerator(context::Context* ctx, - ProofNodeManager* pnm) - : d_pnm(pnm), d_proofs(pnm, ctx), d_current(nullptr) -{ - d_false = NodeManager::currentNM()->mkConst(false); - d_zero = NodeManager::currentNM()->mkConst(CONST_RATIONAL, Rational(0)); -} - -void CADProofGenerator::startNewProof() -{ - d_current = d_proofs.allocateProof(); -} -void CADProofGenerator::startRecursive() { d_current->openChild(); } -void CADProofGenerator::endRecursive(size_t intervalId) -{ - d_current->setCurrent( - intervalId, PfRule::ARITH_NL_CAD_RECURSIVE, {}, {d_false}, d_false); - d_current->closeChild(); -} -void CADProofGenerator::startScope() -{ - d_current->openChild(); - d_current->getCurrent().d_rule = PfRule::SCOPE; -} -void CADProofGenerator::endScope(const std::vector& args) -{ - d_current->setCurrent(0, PfRule::SCOPE, {}, args, d_false); - d_current->closeChild(); -} - -ProofGenerator* CADProofGenerator::getProofGenerator() const -{ - return d_current; -} - -void CADProofGenerator::addDirect(Node var, - VariableMapper& vm, - const poly::Polynomial& poly, - const poly::Assignment& a, - poly::SignCondition& sc, - const poly::Interval& interval, - Node constraint, - size_t intervalId) -{ - if (is_minus_infinity(get_lower(interval)) - && is_plus_infinity(get_upper(interval))) - { - // "Full conflict", constraint excludes (-inf,inf) - d_current->openChild(); - d_current->setCurrent(intervalId, - PfRule::ARITH_NL_CAD_DIRECT, - {constraint}, - {d_false}, - d_false); - d_current->closeChild(); - return; - } - std::vector res; - auto roots = poly::isolate_real_roots(poly, a); - if (get_lower(interval) == get_upper(interval)) - { - // Excludes a single point only - auto ids = getRootIDs(roots, get_lower(interval)); - Assert(ids.first == ids.second); - res.emplace_back(mkIRP(var, Kind::EQUAL, d_zero, ids.first, poly, vm)); - } - else - { - // Excludes an open interval - if (!is_minus_infinity(get_lower(interval))) - { - // Interval has lower bound that is not -inf - auto ids = getRootIDs(roots, get_lower(interval)); - Assert(ids.first == ids.second); - Kind rel = poly::get_lower_open(interval) ? Kind::GT : Kind::GEQ; - res.emplace_back(mkIRP(var, rel, d_zero, ids.first, poly, vm)); - } - if (!is_plus_infinity(get_upper(interval))) - { - // Interval has upper bound that is not inf - auto ids = getRootIDs(roots, get_upper(interval)); - Assert(ids.first == ids.second); - Kind rel = poly::get_upper_open(interval) ? Kind::LT : Kind::LEQ; - res.emplace_back(mkIRP(var, rel, d_zero, ids.first, poly, vm)); - } - } - // Add to proof manager - startScope(); - d_current->openChild(); - d_current->setCurrent(intervalId, - PfRule::ARITH_NL_CAD_DIRECT, - {constraint}, - {d_false}, - d_false); - d_current->closeChild(); - endScope(res); -} - -std::vector CADProofGenerator::constructCell(Node var, - const CACInterval& i, - const poly::Assignment& a, - const poly::Value& s, - VariableMapper& vm) -{ - if (is_minus_infinity(get_lower(i.d_interval)) - && is_plus_infinity(get_upper(i.d_interval))) - { - // "Full conflict", constraint excludes (-inf,inf) - return {}; - } - - std::vector res; - - // Just use bounds for all polynomials - for (const auto& poly : i.d_mainPolys) - { - auto roots = poly::isolate_real_roots(poly, a); - auto ids = getRootIDs(roots, s); - if (ids.first == ids.second) - { - // Excludes a single point only - res.emplace_back(mkIRP(var, Kind::EQUAL, d_zero, ids.first, poly, vm)); - } - else - { - // Excludes an open interval - if (ids.first > 0) - { - // Interval has lower bound that is not -inf - res.emplace_back(mkIRP(var, Kind::GT, d_zero, ids.first, poly, vm)); - } - if (ids.second <= roots.size()) - { - // Interval has upper bound that is not inf - res.emplace_back(mkIRP(var, Kind::LT, d_zero, ids.second, poly, vm)); - } - } - } - - return res; -} - -std::ostream& operator<<(std::ostream& os, const CADProofGenerator& proof) -{ - return os << *proof.d_current; -} - -} // namespace cad -} // namespace nl -} // namespace arith -} // namespace theory -} // namespace cvc5 - -#endif diff --git a/src/theory/arith/nl/cad/proof_generator.h b/src/theory/arith/nl/cad/proof_generator.h deleted file mode 100644 index 521d5aa45..000000000 --- a/src/theory/arith/nl/cad/proof_generator.h +++ /dev/null @@ -1,159 +0,0 @@ -/****************************************************************************** - * Top contributors (to current version): - * Gereon Kremer - * - * This file is part of the cvc5 project. - * - * Copyright (c) 2009-2021 by the authors listed in the file AUTHORS - * in the top-level source directory and their institutional affiliations. - * All rights reserved. See the file COPYING in the top-level source - * directory for licensing information. - * **************************************************************************** - * - * Implements the proof generator for CAD. - */ - -#include "cvc5_private.h" - -#ifndef CVC5__THEORY__ARITH__NL__CAD__PROOF_GENERATOR_H -#define CVC5__THEORY__ARITH__NL__CAD__PROOF_GENERATOR_H - -#ifdef CVC5_POLY_IMP - -#include - -#include - -#include "expr/node.h" -#include "proof/lazy_tree_proof_generator.h" -#include "proof/proof_set.h" -#include "theory/arith/nl/cad/cdcac_utils.h" - -namespace cvc5 { - -class ProofGenerator; - -namespace theory { -namespace arith { -namespace nl { - -struct VariableMapper; - -namespace cad { - -/** - * This class manages the proof creation during a run of the CAD solver. - * - * Though it implements the ProofGenerator interface getProofFor(Node), it only - * gives a proof for a single node. - * - * It uses a LazyTreeProofGenerator internally to manage the tree-based proof - * construction. - */ -class CADProofGenerator -{ - public: - friend std::ostream& operator<<(std::ostream& os, - const CADProofGenerator& proof); - CADProofGenerator(context::Context* ctx, ProofNodeManager* pnm); - - /** Start a new proof in this proof generator */ - void startNewProof(); - /** Start a new recursive call */ - void startRecursive(); - /** Finish the current recursive call */ - void endRecursive(size_t intervalId); - /** Start a new scope, corresponding to a guess in CDCAC */ - void startScope(); - /** Finish a scope and add the (generalized) sample that was refuted */ - void endScope(const std::vector& args); - /** Return the current proof generator */ - ProofGenerator* getProofGenerator() const; - - /** - * Calls LazyTreeProofGenerator::pruneChildren(f), but decorates the - * predicate such that f only accepts the index. - * @param f A Callable bool(std::size_t) - */ - template - void pruneChildren(F&& f) - { - d_current->pruneChildren([&f](const detail::TreeProofNode& tpn) { - // The direct children of recursive rules are scopes, but the ids are - // attached to their children - if (tpn.d_rule == PfRule::SCOPE && tpn.d_children.size() == 1) - { - return f(tpn.d_children[0].d_objectId); - } - return f(tpn.d_objectId); - }); - } - - /** - * Add a direct interval conflict as generated in getUnsatIntervals(). - * Its meaning is: - * over the partial assignment a, var is not in interval because p~sc~0 - * and the origin of this is constraint. - * - * @param var The variable for which the interval is excluded - * @param vm A variable mapper between cvc5 and libpoly variables - * @param p The polynomial of the constraint - * @param a The current partial assignment - * @param sc The sign condition of the constraint - * @param interval The concrete interval that is excluded - * @param constraint The assumption that yields p and sc - */ - void addDirect(Node var, - VariableMapper& vm, - const poly::Polynomial& p, - const poly::Assignment& a, - poly::SignCondition& sc, - const poly::Interval& interval, - Node constraint, - size_t intervalId); - - /** - * Constructs the (generalized) interval that is to be excluded from a - * CACInterval. It should be called after the recursive call to construct the - * generalized sample necessary for endScope(). - * - * @param var The variable for which the interval is excluded - * @param i The concrete interval that is excluded - * @param a The current partial assignment - * @param s The sample point that is refuted for var - * @param vm A variable mapper between cvc5 and libpoly variables - */ - std::vector constructCell(Node var, - const CACInterval& i, - const poly::Assignment& a, - const poly::Value& s, - VariableMapper& vm); - - private: - /** The proof node manager used for the proofs */ - ProofNodeManager* d_pnm; - /** The list of generated proofs */ - CDProofSet d_proofs; - /** The current proof */ - LazyTreeProofGenerator* d_current; - - /** Constant false */ - Node d_false; - /** Constant zero */ - Node d_zero; -}; - -/** - * Prints the underlying LazyTreeProofGenerator. Please check the documentation - * of std::ostream& operator<<(std::ostream&, const LazyTreeProofGenerator&) - */ -std::ostream& operator<<(std::ostream& os, const CADProofGenerator& proof); - -} // namespace cad -} // namespace nl -} // namespace arith -} // namespace theory -} // namespace cvc5 - -#endif -#endif diff --git a/src/theory/arith/nl/cad/variable_ordering.cpp b/src/theory/arith/nl/cad/variable_ordering.cpp deleted file mode 100644 index daf3f48a8..000000000 --- a/src/theory/arith/nl/cad/variable_ordering.cpp +++ /dev/null @@ -1,136 +0,0 @@ -/****************************************************************************** - * Top contributors (to current version): - * Gereon Kremer - * - * This file is part of the cvc5 project. - * - * Copyright (c) 2009-2021 by the authors listed in the file AUTHORS - * in the top-level source directory and their institutional affiliations. - * All rights reserved. See the file COPYING in the top-level source - * directory for licensing information. - * **************************************************************************** - * - * Implements variable orderings tailored to CAD. - */ - -#include "theory/arith/nl/cad/variable_ordering.h" - -#ifdef CVC5_POLY_IMP - -#include "util/poly_util.h" - -namespace cvc5 { -namespace theory { -namespace arith { -namespace nl { -namespace cad { - -std::vector collectInformation( - const Constraints::ConstraintVector& polys, bool with_totals) -{ - poly::VariableCollector vc; - for (const auto& c : polys) - { - vc(std::get<0>(c)); - } - std::vector res; - for (const auto& v : vc.get_variables()) - { - res.emplace_back(); - res.back().var = v; - for (const auto& c : polys) - { - poly_utils::getVariableInformation(res.back(), std::get<0>(c)); - } - } - if (with_totals) - { - res.emplace_back(); - for (const auto& c : polys) - { - poly_utils::getVariableInformation(res.back(), std::get<0>(c)); - } - } - return res; -} - -std::vector getVariables( - const std::vector& vi) -{ - std::vector res; - for (const auto& v : vi) - { - res.emplace_back(v.var); - } - return res; -} - -std::vector sortByid(const Constraints::ConstraintVector& polys) -{ - auto vi = collectInformation(polys); - std::sort( - vi.begin(), - vi.end(), - [](const poly_utils::VariableInformation& a, - const poly_utils::VariableInformation& b) { return a.var < b.var; }); - return getVariables(vi); -}; - -std::vector sortBrown( - const Constraints::ConstraintVector& polys) -{ - auto vi = collectInformation(polys); - std::sort(vi.begin(), - vi.end(), - [](const poly_utils::VariableInformation& a, - const poly_utils::VariableInformation& b) { - if (a.max_degree != b.max_degree) - return a.max_degree > b.max_degree; - if (a.max_terms_tdegree != b.max_terms_tdegree) - return a.max_terms_tdegree > b.max_terms_tdegree; - return a.num_terms > b.num_terms; - }); - return getVariables(vi); -}; - -std::vector sortTriangular( - const Constraints::ConstraintVector& polys) -{ - auto vi = collectInformation(polys); - std::sort(vi.begin(), - vi.end(), - [](const poly_utils::VariableInformation& a, - const poly_utils::VariableInformation& b) { - if (a.max_degree != b.max_degree) - return a.max_degree > b.max_degree; - if (a.max_lc_degree != b.max_lc_degree) - return a.max_lc_degree > b.max_lc_degree; - return a.sum_poly_degree > b.sum_poly_degree; - }); - return getVariables(vi); -}; - -VariableOrdering::VariableOrdering() {} -VariableOrdering::~VariableOrdering() {} - -std::vector VariableOrdering::operator()( - const Constraints::ConstraintVector& polys, - VariableOrderingStrategy vos) const -{ - switch (vos) - { - case VariableOrderingStrategy::BYID: return sortByid(polys); - case VariableOrderingStrategy::BROWN: return sortBrown(polys); - case VariableOrderingStrategy::TRIANGULAR: return sortTriangular(polys); - default: Assert(false) << "Unsupported variable ordering."; - } - return {}; -} - -} // namespace cad -} // namespace nl -} // namespace arith -} // namespace theory -} // namespace cvc5 - -#endif diff --git a/src/theory/arith/nl/cad/variable_ordering.h b/src/theory/arith/nl/cad/variable_ordering.h deleted file mode 100644 index 2de262089..000000000 --- a/src/theory/arith/nl/cad/variable_ordering.h +++ /dev/null @@ -1,71 +0,0 @@ -/****************************************************************************** - * Top contributors (to current version): - * Gereon Kremer - * - * This file is part of the cvc5 project. - * - * Copyright (c) 2009-2021 by the authors listed in the file AUTHORS - * in the top-level source directory and their institutional affiliations. - * All rights reserved. See the file COPYING in the top-level source - * directory for licensing information. - * **************************************************************************** - * - * Implements variable orderings tailored to CAD. - */ - -#include "cvc5_private.h" - -#ifndef CVC5__THEORY__ARITH__NL__CAD__VARIABLE_ORDERING_H -#define CVC5__THEORY__ARITH__NL__CAD__VARIABLE_ORDERING_H - -#ifdef CVC5_POLY_IMP - -#include - -#include "theory/arith/nl/cad/constraints.h" -#include "util/poly_util.h" - -namespace cvc5 { -namespace theory { -namespace arith { -namespace nl { -namespace cad { - -/** Variable orderings for real variables in the context of CAD. */ -enum class VariableOrderingStrategy -{ - /** Dummy ordering by variable ID. */ - BYID, - /** Triangular as of DOI:10.1145/2755996.2756678 */ - TRIANGULAR, - /** Brown as of DOI:10.1145/2755996.2756678 */ - BROWN -}; - -class VariableOrdering -{ - public: - VariableOrdering(); - ~VariableOrdering(); - std::vector operator()( - const Constraints::ConstraintVector& polys, - VariableOrderingStrategy vos) const; -}; - -/** - * Retrieves variable information for all variables with the given polynomials. - * If with_totals is set, the last element of the vector contains totals as - * computed by get_variable_information if no variable is specified. - */ -std::vector collectInformation( - const Constraints::ConstraintVector& polys, bool with_totals = false); - -} // namespace cad -} // namespace nl -} // namespace arith -} // namespace theory -} // namespace cvc5 - -#endif - -#endif diff --git a/src/theory/arith/nl/cad_solver.cpp b/src/theory/arith/nl/cad_solver.cpp deleted file mode 100644 index 57ea8c8ba..000000000 --- a/src/theory/arith/nl/cad_solver.cpp +++ /dev/null @@ -1,255 +0,0 @@ -/****************************************************************************** - * Top contributors (to current version): - * Gereon Kremer, Andrew Reynolds, Andres Noetzli - * - * This file is part of the cvc5 project. - * - * Copyright (c) 2009-2021 by the authors listed in the file AUTHORS - * in the top-level source directory and their institutional affiliations. - * All rights reserved. See the file COPYING in the top-level source - * directory for licensing information. - * **************************************************************************** - * - * Implementation of new non-linear solver. - */ - -#include "theory/arith/nl/cad_solver.h" - -#include "expr/skolem_manager.h" -#include "options/arith_options.h" -#include "smt/env.h" -#include "theory/arith/inference_manager.h" -#include "theory/arith/nl/cad/cdcac.h" -#include "theory/arith/nl/nl_model.h" -#include "theory/arith/nl/poly_conversion.h" -#include "theory/inference_id.h" -#include "theory/theory.h" - -namespace cvc5 { -namespace theory { -namespace arith { -namespace nl { - -CadSolver::CadSolver(Env& env, InferenceManager& im, NlModel& model) - : - EnvObj(env), -#ifdef CVC5_POLY_IMP - d_CAC(env), -#endif - d_foundSatisfiability(false), - d_im(im), - d_model(model), - d_eqsubs(env) -{ - NodeManager* nm = NodeManager::currentNM(); - SkolemManager* sm = nm->getSkolemManager(); - d_ranVariable = sm->mkDummySkolem("__z", nm->realType(), ""); -#ifdef CVC5_POLY_IMP - if (env.isTheoryProofProducing()) - { - ProofChecker* pc = env.getProofNodeManager()->getChecker(); - // add checkers - d_proofChecker.registerTo(pc); - } -#endif -} - -CadSolver::~CadSolver() {} - -void CadSolver::initLastCall(const std::vector& assertions) -{ -#ifdef CVC5_POLY_IMP - if (Trace.isOn("nl-cad")) - { - Trace("nl-cad") << "CadSolver::initLastCall" << std::endl; - Trace("nl-cad") << "* Assertions: " << std::endl; - for (const Node& a : assertions) - { - Trace("nl-cad") << " " << a << std::endl; - } - } - if (options().arith.nlCadVarElim) - { - d_eqsubs.reset(); - std::vector processed = d_eqsubs.eliminateEqualities(assertions); - if (d_eqsubs.hasConflict()) - { - Node lem = NodeManager::currentNM()->mkAnd(d_eqsubs.getConflict()).negate(); - d_im.addPendingLemma(lem, InferenceId::ARITH_NL_CAD_CONFLICT, nullptr); - Trace("nl-cad") << "Found conflict: " << lem << std::endl; - return; - } - if (Trace.isOn("nl-cad")) - { - Trace("nl-cad") << "After simplifications" << std::endl; - Trace("nl-cad") << "* Assertions: " << std::endl; - for (const Node& a : processed) - { - Trace("nl-cad") << " " << a << std::endl; - } - } - d_CAC.reset(); - for (const Node& a : processed) - { - Assert(!a.isConst()); - d_CAC.getConstraints().addConstraint(a); - } - } - else - { - d_CAC.reset(); - for (const Node& a : assertions) - { - Assert(!a.isConst()); - d_CAC.getConstraints().addConstraint(a); - } - } - d_CAC.computeVariableOrdering(); - d_CAC.retrieveInitialAssignment(d_model, d_ranVariable); -#else - warning() << "Tried to use CadSolver but libpoly is not available. Compile " - "with --poly." - << std::endl; -#endif -} - -void CadSolver::checkFull() -{ -#ifdef CVC5_POLY_IMP - if (d_CAC.getConstraints().getConstraints().empty()) { - d_foundSatisfiability = true; - Trace("nl-cad") << "No constraints. Return." << std::endl; - return; - } - d_CAC.startNewProof(); - auto covering = d_CAC.getUnsatCover(); - if (covering.empty()) - { - d_foundSatisfiability = true; - Trace("nl-cad") << "SAT: " << d_CAC.getModel() << std::endl; - } - else - { - d_foundSatisfiability = false; - auto mis = collectConstraints(covering); - Trace("nl-cad") << "Collected MIS: " << mis << std::endl; - Assert(!mis.empty()) << "Infeasible subset can not be empty"; - Trace("nl-cad") << "UNSAT with MIS: " << mis << std::endl; - d_eqsubs.postprocessConflict(mis); - Trace("nl-cad") << "After postprocessing: " << mis << std::endl; - Node lem = NodeManager::currentNM()->mkAnd(mis).notNode(); - ProofGenerator* proof = d_CAC.closeProof(mis); - d_im.addPendingLemma(lem, InferenceId::ARITH_NL_CAD_CONFLICT, proof); - } -#else - warning() << "Tried to use CadSolver but libpoly is not available. Compile " - "with --poly." - << std::endl; -#endif -} - -void CadSolver::checkPartial() -{ -#ifdef CVC5_POLY_IMP - if (d_CAC.getConstraints().getConstraints().empty()) { - Trace("nl-cad") << "No constraints. Return." << std::endl; - return; - } - auto covering = d_CAC.getUnsatCover(true); - if (covering.empty()) - { - d_foundSatisfiability = true; - Trace("nl-cad") << "SAT: " << d_CAC.getModel() << std::endl; - } - else - { - auto* nm = NodeManager::currentNM(); - Node first_var = - d_CAC.getConstraints().varMapper()(d_CAC.getVariableOrdering()[0]); - for (const auto& interval : covering) - { - Node premise; - Assert(!interval.d_origins.empty()); - if (interval.d_origins.size() == 1) - { - premise = interval.d_origins[0]; - } - else - { - premise = nm->mkNode(Kind::AND, interval.d_origins); - } - Node conclusion = - excluding_interval_to_lemma(first_var, interval.d_interval, false); - if (!conclusion.isNull()) - { - Node lemma = nm->mkNode(Kind::IMPLIES, premise, conclusion); - Trace("nl-cad") << "Excluding " << first_var << " -> " - << interval.d_interval << " using " << lemma - << std::endl; - d_im.addPendingLemma(lemma, - InferenceId::ARITH_NL_CAD_EXCLUDED_INTERVAL); - } - } - } -#else - warning() << "Tried to use CadSolver but libpoly is not available. Compile " - "with --poly." - << std::endl; -#endif -} - -bool CadSolver::constructModelIfAvailable(std::vector& assertions) -{ -#ifdef CVC5_POLY_IMP - if (!d_foundSatisfiability) - { - return false; - } - bool foundNonVariable = false; - for (const auto& v : d_CAC.getVariableOrdering()) - { - Node variable = d_CAC.getConstraints().varMapper()(v); - if (!Theory::isLeafOf(variable, TheoryId::THEORY_ARITH)) - { - Trace("nl-cad") << "Not a variable: " << variable << std::endl; - foundNonVariable = true; - } - Node value = value_to_node(d_CAC.getModel().get(v), variable); - addToModel(variable, value); - } - for (const auto& sub : d_eqsubs.getSubstitutions()) - { - Trace("nl-cad") << "EqSubs: " << sub.first << " -> " << sub.second - << std::endl; - addToModel(sub.first, sub.second); - } - if (foundNonVariable) - { - Trace("nl-cad") - << "Some variable was an extended term, don't clear list of assertions." - << std::endl; - return false; - } - Trace("nl-cad") << "Constructed a full assignment, clear list of assertions." - << std::endl; - assertions.clear(); - return true; -#else - warning() << "Tried to use CadSolver but libpoly is not available. Compile " - "with --poly." - << std::endl; - return false; -#endif -} - -void CadSolver::addToModel(TNode var, TNode value) const -{ - Trace("nl-cad") << "-> " << var << " = " << value << std::endl; - Assert(value.getType().isRealOrInt()); - d_model.addSubstitution(var, value); -} - -} // namespace nl -} // namespace arith -} // namespace theory -} // namespace cvc5 diff --git a/src/theory/arith/nl/cad_solver.h b/src/theory/arith/nl/cad_solver.h deleted file mode 100644 index d72c92a8a..000000000 --- a/src/theory/arith/nl/cad_solver.h +++ /dev/null @@ -1,124 +0,0 @@ -/****************************************************************************** - * Top contributors (to current version): - * Gereon Kremer - * - * This file is part of the cvc5 project. - * - * Copyright (c) 2009-2021 by the authors listed in the file AUTHORS - * in the top-level source directory and their institutional affiliations. - * All rights reserved. See the file COPYING in the top-level source - * directory for licensing information. - * **************************************************************************** - * - * CAD-based solver based on https://arxiv.org/pdf/2003.05633.pdf. - */ - -#ifndef CVC5__THEORY__ARITH__CAD_SOLVER_H -#define CVC5__THEORY__ARITH__CAD_SOLVER_H - -#include - -#include "context/context.h" -#include "expr/node.h" -#include "smt/env_obj.h" -#include "theory/arith/nl/cad/cdcac.h" -#include "theory/arith/nl/cad/proof_checker.h" -#include "theory/arith/nl/equality_substitution.h" - -namespace cvc5 { - -class ProofNodeManager; - -namespace theory { -namespace arith { - -class InferenceManager; - -namespace nl { - -class NlModel; - -/** - * A solver for nonlinear arithmetic that implements the CAD-based method - * described in https://arxiv.org/pdf/2003.05633.pdf. - */ -class CadSolver: protected EnvObj -{ - public: - CadSolver(Env& env, InferenceManager& im, NlModel& model); - ~CadSolver(); - - /** - * This is called at the beginning of last call effort check, where - * assertions are the set of assertions belonging to arithmetic, - * false_asserts is the subset of assertions that are false in the current - * model, and xts is the set of extended function terms that are active in - * the current context. - */ - void initLastCall(const std::vector& assertions); - - /** - * Perform a full check, returning either {} or a single lemma. - * If the result is empty, the input is satisfiable and a model is available - * for construct_model_if_available. Otherwise, the single lemma can be used - * as an infeasible subset. - */ - void checkFull(); - - /** - * Perform a partial check, returning either {} or a list of lemmas. - * If the result is empty, the input is satisfiable and a model is available - * for construct_model_if_available. Otherwise, the lemmas exclude some part - * of the search space. - */ - void checkPartial(); - - /** - * If a model is available (indicated by the last call to check_full() or - * check_partial()) this method puts a satisfying assignment in d_model, - * clears the list of assertions, and returns true. - * Otherwise, this method returns false. - */ - bool constructModelIfAvailable(std::vector& assertions); - - private: - /** - * Add the variable assignment `var = value` to the nonlinear model. - * Depending on `value`, it is either added as substitution or witness. - */ - void addToModel(TNode var, TNode value) const; - - /** - * The variable used to encode real algebraic numbers to nodes. - */ - Node d_ranVariable; - -#ifdef CVC5_POLY_IMP - /** - * The object implementing the actual decision procedure. - */ - cad::CDCAC d_CAC; - /** The proof checker for cad proofs */ - cad::CADProofRuleChecker d_proofChecker; -#endif - /** - * Indicates whether we found satisfiability in the last call to - * checkFullRefine. - */ - bool d_foundSatisfiability; - - /** The inference manager we are pushing conflicts and lemmas to. */ - InferenceManager& d_im; - /** Reference to the non-linear model object */ - NlModel& d_model; - /** Utility to eliminate variables from simple equalities before going into - * the actual coverings solver */ - EqualitySubstitution d_eqsubs; -}; /* class CadSolver */ - -} // namespace nl -} // namespace arith -} // namespace theory -} // namespace cvc5 - -#endif /* CVC5__THEORY__ARITH__CAD_SOLVER_H */ diff --git a/src/theory/arith/nl/coverings/cdcac.cpp b/src/theory/arith/nl/coverings/cdcac.cpp new file mode 100644 index 000000000..8091964d7 --- /dev/null +++ b/src/theory/arith/nl/coverings/cdcac.cpp @@ -0,0 +1,768 @@ +/****************************************************************************** + * Top contributors (to current version): + * Gereon Kremer, Aina Niemetz + * + * This file is part of the cvc5 project. + * + * Copyright (c) 2009-2021 by the authors listed in the file AUTHORS + * in the top-level source directory and their institutional affiliations. + * All rights reserved. See the file COPYING in the top-level source + * directory for licensing information. + * **************************************************************************** + * + * Implements the CDCAC approach as described in + * https://arxiv.org/pdf/2003.05633.pdf. + */ + +#include "theory/arith/nl/coverings/cdcac.h" + +#ifdef CVC5_POLY_IMP + +#include "options/arith_options.h" +#include "theory/arith/nl/coverings/lazard_evaluation.h" +#include "theory/arith/nl/coverings/projections.h" +#include "theory/arith/nl/coverings/variable_ordering.h" +#include "theory/arith/nl/nl_model.h" +#include "theory/rewriter.h" + +using namespace cvc5::kind; + +namespace std { +/** Generic streaming operator for std::vector. */ +template +std::ostream& operator<<(std::ostream& os, const std::vector& v) +{ + cvc5::container_to_stream(os, v); + return os; +} +} // namespace std + +namespace cvc5 { +namespace theory { +namespace arith { +namespace nl { +namespace coverings { + +CDCAC::CDCAC(Env& env, const std::vector& ordering) + : EnvObj(env), d_variableOrdering(ordering) +{ + if (d_env.isTheoryProofProducing()) + { + d_proof.reset( + new CoveringsProofGenerator(userContext(), d_env.getProofNodeManager())); + } +} + +void CDCAC::reset() +{ + d_constraints.reset(); + d_assignment.clear(); + d_nextIntervalId = 1; +} + +void CDCAC::computeVariableOrdering() +{ + // Actually compute the variable ordering + d_variableOrdering = d_varOrder(d_constraints.getConstraints(), + VariableOrderingStrategy::BROWN); + Trace("cdcac") << "Variable ordering is now " << d_variableOrdering + << std::endl; + + // Write variable ordering back to libpoly. + lp_variable_order_t* vo = poly::Context::get_context().get_variable_order(); + lp_variable_order_clear(vo); + for (const auto& v : d_variableOrdering) + { + lp_variable_order_push(vo, v.get_internal()); + } +} + +void CDCAC::retrieveInitialAssignment(NlModel& model, const Node& ran_variable) +{ + if (options().arith.nlCovLinearModel == options::nlCovLinearModelMode::NONE) return; + d_initialAssignment.clear(); + Trace("cdcac") << "Retrieving initial assignment:" << std::endl; + for (const auto& var : d_variableOrdering) + { + Node v = getConstraints().varMapper()(var); + Node val = model.computeConcreteModelValue(v); + poly::Value value = node_to_value(val, ran_variable); + Trace("cdcac") << "\t" << var << " = " << value << std::endl; + d_initialAssignment.emplace_back(value); + } +} +Constraints& CDCAC::getConstraints() { return d_constraints; } +const Constraints& CDCAC::getConstraints() const { return d_constraints; } + +const poly::Assignment& CDCAC::getModel() const { return d_assignment; } + +const std::vector& CDCAC::getVariableOrdering() const +{ + return d_variableOrdering; +} + +std::vector CDCAC::getUnsatIntervals(std::size_t cur_variable) +{ + std::vector res; + LazardEvaluation le; + prepareRootIsolation(le, cur_variable); + for (const auto& c : d_constraints.getConstraints()) + { + const poly::Polynomial& p = std::get<0>(c); + poly::SignCondition sc = std::get<1>(c); + const Node& n = std::get<2>(c); + + if (main_variable(p) != d_variableOrdering[cur_variable]) + { + // Constraint is in another variable, ignore it. + continue; + } + + Trace("cdcac") << "Infeasible intervals for " << p << " " << sc + << " 0 over " << d_assignment << std::endl; + std::vector intervals; + if (options().arith.nlCovLifting + == options::nlCovLiftingMode::LAZARD) + { + intervals = le.infeasibleRegions(p, sc); + if (Trace.isOn("cdcac")) + { + auto reference = poly::infeasible_regions(p, d_assignment, sc); + Trace("cdcac") << "Lazard: " << intervals << std::endl; + Trace("cdcac") << "Regular: " << reference << std::endl; + } + } + else + { + intervals = poly::infeasible_regions(p, d_assignment, sc); + } + for (const auto& i : intervals) + { + Trace("cdcac") << "-> " << i << std::endl; + PolyVector l, u, m, d; + m.add(p); + m.pushDownPolys(d, d_variableOrdering[cur_variable]); + if (!is_minus_infinity(get_lower(i))) l = m; + if (!is_plus_infinity(get_upper(i))) u = m; + res.emplace_back(CACInterval{d_nextIntervalId++, i, l, u, m, d, {n}}); + if (isProofEnabled()) + { + d_proof->addDirect( + d_constraints.varMapper()(d_variableOrdering[cur_variable]), + d_constraints.varMapper(), + p, + d_assignment, + sc, + i, + n, + res.back().d_id); + } + } + } + pruneRedundantIntervals(res); + return res; +} + +bool CDCAC::sampleOutsideWithInitial(const std::vector& infeasible, + poly::Value& sample, + std::size_t cur_variable) +{ + if (options().arith.nlCovLinearModel != options::nlCovLinearModelMode::NONE + && cur_variable < d_initialAssignment.size()) + { + const poly::Value& suggested = d_initialAssignment[cur_variable]; + for (const auto& i : infeasible) + { + if (poly::contains(i.d_interval, suggested)) + { + if (options().arith.nlCovLinearModel == options::nlCovLinearModelMode::INITIAL) + { + d_initialAssignment.clear(); + } + return sampleOutside(infeasible, sample); + } + } + Trace("cdcac") << "Using suggested initial value" << std::endl; + sample = suggested; + return true; + } + return sampleOutside(infeasible, sample); +} + +namespace { + +/** + * This method follows the projection operator as detailed in algorithm 6 of + * 10.1016/j.jlamp.2020.100633, which mostly follows the projection operator due + * to McCallum. It uses all coefficients until one is either constant or does + * not vanish over the current assignment. + */ +PolyVector requiredCoefficientsOriginal(const poly::Polynomial& p, + const poly::Assignment& assignment) +{ + PolyVector res; + for (long deg = degree(p); deg >= 0; --deg) + { + auto coeff = coefficient(p, deg); + Assert(poly::is_constant(coeff) + == lp_polynomial_is_constant(coeff.get_internal())); + if (poly::is_constant(coeff)) break; + res.add(coeff); + if (evaluate_constraint(coeff, assignment, poly::SignCondition::NE)) + { + break; + } + } + return res; +} + +/** + * This method follows the original projection operator due to Lazard from + * section 3 of 10.1007/978-1-4612-2628-4_29. It uses the leading and trailing + * coefficient, and makes some limited efforts to avoid them in certain cases: + * We avoid the leading coefficient if it is constant. We avoid the trailing + * coefficient if the leading coefficient does not vanish over the current + * assignment and the trailing coefficient is not constant. + */ +PolyVector requiredCoefficientsLazard(const poly::Polynomial& p, + const poly::Assignment& assignment) +{ + PolyVector res; + auto lc = poly::leading_coefficient(p); + if (poly::is_constant(lc)) return res; + res.add(lc); + if (evaluate_constraint(lc, assignment, poly::SignCondition::NE)) return res; + auto tc = poly::coefficient(p, 0); + if (poly::is_constant(tc)) return res; + res.add(tc); + return res; +} + +/** + * This method follows the enhancements from 10.1007/978-3-030-60026-6_8 for the + * projection operator due to Lazard, more specifically Section 3.3 and + * Definition 4. In essence, we can skip the trailing coefficient if we can show + * that p is not nullified by any n-1 dimensional point. The statement in the + * paper is slightly more general, but there is no simple way to have such a + * procedure T here. We simply try to show that T(f) = {} by using the extended + * rewriter to simplify (and (= f_i 0)) (f_i being the coefficients of f) to + * false. + */ +PolyVector requiredCoefficientsLazardModified( + const poly::Polynomial& p, + const poly::Assignment& assignment, + VariableMapper& vm, + Rewriter* rewriter) +{ + PolyVector res; + auto lc = poly::leading_coefficient(p); + // if leading coefficient is constant + if (poly::is_constant(lc)) return res; + // add leading coefficient + res.add(lc); + auto tc = poly::coefficient(p, 0); + // if trailing coefficient is constant + if (poly::is_constant(tc)) return res; + // if leading coefficient does not vanish over the current assignment + if (evaluate_constraint(lc, assignment, poly::SignCondition::NE)) return res; + + // construct phi := (and (= p_i 0)) with p_i the coefficients of p + std::vector conditions; + auto zero = NodeManager::currentNM()->mkConst(CONST_RATIONAL, Rational(0)); + for (const auto& coeff : poly::coefficients(p)) + { + conditions.emplace_back(NodeManager::currentNM()->mkNode( + Kind::EQUAL, nl::as_cvc_polynomial(coeff, vm), zero)); + } + // if phi is false (i.e. p can not vanish) + Node rewritten = + rewriter->extendedRewrite(NodeManager::currentNM()->mkAnd(conditions)); + if (rewritten.isConst()) + { + Assert(rewritten.getKind() == Kind::CONST_BOOLEAN); + Assert(!rewritten.getConst()); + return res; + } + // otherwise add trailing coefficient as well + res.add(tc); + return res; +} + +} // namespace + +PolyVector CDCAC::requiredCoefficients(const poly::Polynomial& p) +{ + if (Trace.isOn("cdcac::projection")) + { + Trace("cdcac::projection") + << "Poly: " << p << " over " << d_assignment << std::endl; + Trace("cdcac::projection") + << "Lazard: " << requiredCoefficientsLazard(p, d_assignment) + << std::endl; + Trace("cdcac::projection") + << "LMod: " + << requiredCoefficientsLazardModified( + p, d_assignment, d_constraints.varMapper(), d_env.getRewriter()) + << std::endl; + Trace("cdcac::projection") + << "Original: " << requiredCoefficientsOriginal(p, d_assignment) + << std::endl; + } + switch (options().arith.nlCovProjection) + { + case options::nlCovProjectionMode::MCCALLUM: + return requiredCoefficientsOriginal(p, d_assignment); + case options::nlCovProjectionMode::LAZARD: + return requiredCoefficientsLazard(p, d_assignment); + case options::nlCovProjectionMode::LAZARDMOD: + return requiredCoefficientsLazardModified( + p, d_assignment, d_constraints.varMapper(), d_env.getRewriter()); + default: + Assert(false); + return requiredCoefficientsOriginal(p, d_assignment); + } +} + +PolyVector CDCAC::constructCharacterization(std::vector& intervals) +{ + Assert(!intervals.empty()) << "A covering can not be empty"; + Trace("cdcac") << "Constructing characterization now" << std::endl; + PolyVector res; + + for (std::size_t i = 0, n = intervals.size(); i < n - 1; ++i) + { + coverings::makeFinestSquareFreeBasis(intervals[i], intervals[i + 1]); + } + + for (const auto& i : intervals) + { + Trace("cdcac") << "Considering " << i.d_interval << std::endl; + Trace("cdcac") << "-> " << i.d_lowerPolys << " / " << i.d_upperPolys + << " and " << i.d_mainPolys << " / " << i.d_downPolys + << std::endl; + Trace("cdcac") << "-> " << i.d_origins << std::endl; + for (const auto& p : i.d_downPolys) + { + // Add all polynomial from lower levels. + res.add(p); + } + for (const auto& p : i.d_mainPolys) + { + Trace("cdcac::projection") + << "Discriminant of " << p << " -> " << discriminant(p) << std::endl; + // Add all discriminants + res.add(discriminant(p)); + + for (const auto& q : requiredCoefficients(p)) + { + // Add all required coefficients + Trace("cdcac::projection") + << "Coeff of " << p << " -> " << q << std::endl; + res.add(q); + } + for (const auto& q : i.d_lowerPolys) + { + if (p == q) continue; + // Check whether p(s \times a) = 0 for some a <= l + if (!hasRootBelow(q, get_lower(i.d_interval))) continue; + Trace("cdcac::projection") << "Resultant of " << p << " and " << q + << " -> " << resultant(p, q) << std::endl; + res.add(resultant(p, q)); + } + for (const auto& q : i.d_upperPolys) + { + if (p == q) continue; + // Check whether p(s \times a) = 0 for some a >= u + if (!hasRootAbove(q, get_upper(i.d_interval))) continue; + Trace("cdcac::projection") << "Resultant of " << p << " and " << q + << " -> " << resultant(p, q) << std::endl; + res.add(resultant(p, q)); + } + } + } + + for (std::size_t i = 0, n = intervals.size(); i < n - 1; ++i) + { + // Add resultants of consecutive intervals. + for (const auto& p : intervals[i].d_upperPolys) + { + for (const auto& q : intervals[i + 1].d_lowerPolys) + { + Trace("cdcac::projection") << "Resultant of " << p << " and " << q + << " -> " << resultant(p, q) << std::endl; + res.add(resultant(p, q)); + } + } + } + + res.reduce(); + res.makeFinestSquareFreeBasis(); + + return res; +} + +CACInterval CDCAC::intervalFromCharacterization( + const PolyVector& characterization, + std::size_t cur_variable, + const poly::Value& sample) +{ + PolyVector l; + PolyVector u; + PolyVector m; + PolyVector d; + + for (const auto& p : characterization) + { + // Add polynomials to main + m.add(p); + } + // Push lower-dimensional polys to down + m.pushDownPolys(d, d_variableOrdering[cur_variable]); + + // Collect -oo, all roots, oo + + LazardEvaluation le; + prepareRootIsolation(le, cur_variable); + std::vector roots; + roots.emplace_back(poly::Value::minus_infty()); + for (const auto& p : m) + { + Trace("cdcac") << "Isolating real roots of " << p << " over " + << d_assignment << std::endl; + + auto tmp = isolateRealRoots(le, p); + roots.insert(roots.end(), tmp.begin(), tmp.end()); + } + roots.emplace_back(poly::Value::plus_infty()); + std::sort(roots.begin(), roots.end()); + + // Now find the interval bounds + poly::Value lower; + poly::Value upper; + for (std::size_t i = 0, n = roots.size(); i < n; ++i) + { + if (sample < roots[i]) + { + lower = roots[i - 1]; + upper = roots[i]; + break; + } + if (roots[i] == sample) + { + lower = sample; + upper = sample; + break; + } + } + Assert(!is_none(lower) && !is_none(upper)); + + if (!is_minus_infinity(lower)) + { + // Identify polynomials that have a root at the lower bound + d_assignment.set(d_variableOrdering[cur_variable], lower); + for (const auto& p : m) + { + Trace("cdcac") << "Evaluating " << p << " = 0 over " << d_assignment + << std::endl; + if (evaluate_constraint(p, d_assignment, poly::SignCondition::EQ)) + { + l.add(p, true); + } + } + d_assignment.unset(d_variableOrdering[cur_variable]); + } + if (!is_plus_infinity(upper)) + { + // Identify polynomials that have a root at the upper bound + d_assignment.set(d_variableOrdering[cur_variable], upper); + for (const auto& p : m) + { + Trace("cdcac") << "Evaluating " << p << " = 0 over " << d_assignment + << std::endl; + if (evaluate_constraint(p, d_assignment, poly::SignCondition::EQ)) + { + u.add(p, true); + } + } + d_assignment.unset(d_variableOrdering[cur_variable]); + } + + if (lower == upper) + { + // construct a point interval + return CACInterval{d_nextIntervalId++, + poly::Interval(lower, false, upper, false), + l, + u, + m, + d, + {}}; + } + else + { + // construct an open interval + Assert(lower < upper); + return CACInterval{d_nextIntervalId++, + poly::Interval(lower, true, upper, true), + l, + u, + m, + d, + {}}; + } +} + +std::vector CDCAC::getUnsatCoverImpl(std::size_t curVariable, + bool returnFirstInterval) +{ + Trace("cdcac") << "Looking for unsat cover for " + << d_variableOrdering[curVariable] << std::endl; + std::vector intervals = getUnsatIntervals(curVariable); + + if (Trace.isOn("cdcac")) + { + Trace("cdcac") << "Unsat intervals for " << d_variableOrdering[curVariable] + << ":" << std::endl; + for (const auto& i : intervals) + { + Trace("cdcac") << "-> " << i.d_interval << " from " << i.d_origins + << std::endl; + } + } + poly::Value sample; + + while (sampleOutsideWithInitial(intervals, sample, curVariable)) + { + if (!checkIntegrality(curVariable, sample)) + { + // the variable is integral, but the sample is not. + Trace("cdcac") << "Used " << sample << " for integer variable " + << d_variableOrdering[curVariable] << std::endl; + auto newInterval = buildIntegralityInterval(curVariable, sample); + Trace("cdcac") << "Adding integrality interval " << newInterval.d_interval + << std::endl; + intervals.emplace_back(newInterval); + pruneRedundantIntervals(intervals); + continue; + } + d_assignment.set(d_variableOrdering[curVariable], sample); + Trace("cdcac") << "Sample: " << d_assignment << std::endl; + if (curVariable == d_variableOrdering.size() - 1) + { + // We have a full assignment. SAT! + Trace("cdcac") << "Found full assignment: " << d_assignment << std::endl; + return {}; + } + if (isProofEnabled()) + { + d_proof->startScope(); + d_proof->startRecursive(); + } + // Recurse to next variable + auto cov = getUnsatCoverImpl(curVariable + 1); + if (cov.empty()) + { + // Found SAT! + Trace("cdcac") << "SAT!" << std::endl; + return {}; + } + Trace("cdcac") << "Refuting Sample: " << d_assignment << std::endl; + auto characterization = constructCharacterization(cov); + Trace("cdcac") << "Characterization: " << characterization << std::endl; + + d_assignment.unset(d_variableOrdering[curVariable]); + + Trace("cdcac") << "Building interval..." << std::endl; + auto newInterval = + intervalFromCharacterization(characterization, curVariable, sample); + Trace("cdcac") << "New interval: " << newInterval.d_interval << std::endl; + newInterval.d_origins = collectConstraints(cov); + intervals.emplace_back(newInterval); + if (isProofEnabled()) + { + d_proof->endRecursive(newInterval.d_id); + auto cell = d_proof->constructCell( + d_constraints.varMapper()(d_variableOrdering[curVariable]), + newInterval, + d_assignment, + sample, + d_constraints.varMapper()); + d_proof->endScope(cell); + } + + if (returnFirstInterval) + { + return intervals; + } + + Trace("cdcac") << "Added " << intervals.back().d_interval << std::endl; + Trace("cdcac") << "\tlower: " << intervals.back().d_lowerPolys + << std::endl; + Trace("cdcac") << "\tupper: " << intervals.back().d_upperPolys + << std::endl; + Trace("cdcac") << "\tmain: " << intervals.back().d_mainPolys + << std::endl; + Trace("cdcac") << "\tdown: " << intervals.back().d_downPolys + << std::endl; + Trace("cdcac") << "\torigins: " << intervals.back().d_origins << std::endl; + + // Remove redundant intervals + pruneRedundantIntervals(intervals); + } + + if (Trace.isOn("cdcac")) + { + Trace("cdcac") << "Returning intervals for " + << d_variableOrdering[curVariable] << ":" << std::endl; + for (const auto& i : intervals) + { + Trace("cdcac") << "-> " << i.d_interval << std::endl; + } + } + return intervals; +} + +std::vector CDCAC::getUnsatCover(bool returnFirstInterval) +{ + if (isProofEnabled()) + { + d_proof->startRecursive(); + } + auto res = getUnsatCoverImpl(0, returnFirstInterval); + if (isProofEnabled()) + { + d_proof->endRecursive(0); + } + return res; +} + +void CDCAC::startNewProof() +{ + if (isProofEnabled()) + { + d_proof->startNewProof(); + } +} + +ProofGenerator* CDCAC::closeProof(const std::vector& assertions) +{ + if (isProofEnabled()) + { + d_proof->endScope(assertions); + return d_proof->getProofGenerator(); + } + return nullptr; +} + +bool CDCAC::checkIntegrality(std::size_t cur_variable, const poly::Value& value) +{ + Node var = d_constraints.varMapper()(d_variableOrdering[cur_variable]); + if (var.getType() != NodeManager::currentNM()->integerType()) + { + // variable is not integral + return true; + } + return poly::represents_integer(value); +} + +CACInterval CDCAC::buildIntegralityInterval(std::size_t cur_variable, + const poly::Value& value) +{ + poly::Variable var = d_variableOrdering[cur_variable]; + poly::Integer below = poly::floor(value); + poly::Integer above = poly::ceil(value); + // construct var \in (below, above) + return CACInterval{d_nextIntervalId++, + poly::Interval(below, above), + {var - below}, + {var - above}, + {var - below, var - above}, + {}, + {}}; +} + +bool CDCAC::hasRootAbove(const poly::Polynomial& p, + const poly::Value& val) const +{ + auto roots = poly::isolate_real_roots(p, d_assignment); + return std::any_of(roots.begin(), roots.end(), [&val](const poly::Value& r) { + return r >= val; + }); +} + +bool CDCAC::hasRootBelow(const poly::Polynomial& p, + const poly::Value& val) const +{ + auto roots = poly::isolate_real_roots(p, d_assignment); + return std::any_of(roots.begin(), roots.end(), [&val](const poly::Value& r) { + return r <= val; + }); +} + +void CDCAC::pruneRedundantIntervals(std::vector& intervals) +{ + cleanIntervals(intervals); + if (options().arith.nlCovPrune) + { + if (Trace.isOn("cdcac")) + { + auto copy = intervals; + removeRedundantIntervals(intervals); + if (copy.size() != intervals.size()) + { + Trace("cdcac") << "Before pruning:"; + for (const auto& i : copy) Trace("cdcac") << " " << i.d_interval; + Trace("cdcac") << std::endl; + Trace("cdcac") << "After pruning: "; + for (const auto& i : intervals) Trace("cdcac") << " " << i.d_interval; + Trace("cdcac") << std::endl; + } + } + else + { + removeRedundantIntervals(intervals); + } + } + if (isProofEnabled()) + { + d_proof->pruneChildren([&intervals](std::size_t id) { + if (id == 0) return false; + return std::find_if(intervals.begin(), + intervals.end(), + [id](const CACInterval& i) { return i.d_id == id; }) + == intervals.end(); + }); + } +} + +void CDCAC::prepareRootIsolation(LazardEvaluation& le, + size_t cur_variable) const +{ + if (options().arith.nlCovLifting == options::nlCovLiftingMode::LAZARD) + { + for (size_t vid = 0; vid < cur_variable; ++vid) + { + const auto& val = d_assignment.get(d_variableOrdering[vid]); + le.add(d_variableOrdering[vid], val); + } + le.addFreeVariable(d_variableOrdering[cur_variable]); + } +} + +std::vector CDCAC::isolateRealRoots( + LazardEvaluation& le, const poly::Polynomial& p) const +{ + if (options().arith.nlCovLifting == options::nlCovLiftingMode::LAZARD) + { + return le.isolateRealRoots(p); + } + return poly::isolate_real_roots(p, d_assignment); +} + +} // namespace coverings +} // namespace nl +} // namespace arith +} // namespace theory +} // namespace cvc5 + +#endif diff --git a/src/theory/arith/nl/coverings/cdcac.h b/src/theory/arith/nl/coverings/cdcac.h new file mode 100644 index 000000000..e7f57095d --- /dev/null +++ b/src/theory/arith/nl/coverings/cdcac.h @@ -0,0 +1,246 @@ +/****************************************************************************** + * Top contributors (to current version): + * Gereon Kremer + * + * This file is part of the cvc5 project. + * + * Copyright (c) 2009-2021 by the authors listed in the file AUTHORS + * in the top-level source directory and their institutional affiliations. + * All rights reserved. See the file COPYING in the top-level source + * directory for licensing information. + * **************************************************************************** + * + * Implements the CDCAC approach as described in + * https://arxiv.org/pdf/2003.05633.pdf. + */ + +#include "cvc5_private.h" + +#ifndef CVC5__THEORY__ARITH__NL__COVERINGS__CDCAC_H +#define CVC5__THEORY__ARITH__NL__COVERINGS__CDCAC_H + +#ifdef CVC5_POLY_IMP + +#include + +#include + +#include "smt/env.h" +#include "smt/env_obj.h" +#include "theory/arith/nl/coverings/cdcac_utils.h" +#include "theory/arith/nl/coverings/constraints.h" +#include "theory/arith/nl/coverings/lazard_evaluation.h" +#include "theory/arith/nl/coverings/proof_generator.h" +#include "theory/arith/nl/coverings/variable_ordering.h" + +namespace cvc5 { +namespace theory { +namespace arith { +namespace nl { + +class NlModel; + +namespace coverings { + +/** + * This class implements Cylindrical Algebraic Coverings as presented in + * https://arxiv.org/pdf/2003.05633.pdf + */ +class CDCAC : protected EnvObj +{ + public: + /** Initialize this method with the given variable ordering. */ + CDCAC(Env& env, const std::vector& ordering = {}); + + /** Reset this instance. */ + void reset(); + + /** Collect variables from the constraints and compute a variable ordering. */ + void computeVariableOrdering(); + + /** + * Extract an initial assignment from the given model. + * This initial assignment is used to guide sampling if possible. + * The ran_variable should be the variable used to encode real algebraic + * numbers in the model and is simply passed on to node_to_value. + */ + void retrieveInitialAssignment(NlModel& model, const Node& ran_variable); + + /** + * Returns the constraints as a non-const reference. Can be used to add new + * constraints. + */ + Constraints& getConstraints(); + /** Returns the constraints as a const reference. */ + const Constraints& getConstraints() const; + + /** + * Returns the current assignment. This is a satisfying model if + * get_unsat_cover() returned an empty vector. + */ + const poly::Assignment& getModel() const; + + /** Returns the current variable ordering. */ + const std::vector& getVariableOrdering() const; + + /** + * Collect all unsatisfiable intervals for the given variable. + * Combines unsatisfiable regions from d_constraints evaluated over + * d_assignment. Implements Algorithm 2. + */ + std::vector getUnsatIntervals(std::size_t cur_variable); + + /** + * Sample outside of the set of intervals. + * Uses a given initial value from mInitialAssignment if possible. + * Returns whether a sample was found (true) or the infeasible intervals cover + * the whole real line (false). + */ + bool sampleOutsideWithInitial(const std::vector& infeasible, + poly::Value& sample, + std::size_t cur_variable); + + /** + * Collects the coefficients required for projection from the given + * polynomial. Implements Algorithm 6, depending on the command line + * arguments. Either directly implements Algorithm 6, or improved variants + * based on Lazard's projection. + */ + PolyVector requiredCoefficients(const poly::Polynomial& p); + + /** + * Constructs a characterization of the given covering. + * A characterization contains polynomials whose roots bound the region around + * the current assignment. Implements Algorithm 4. + */ + PolyVector constructCharacterization(std::vector& intervals); + + /** + * Constructs an infeasible interval from a characterization. + * Implements Algorithm 5. + */ + CACInterval intervalFromCharacterization(const PolyVector& characterization, + std::size_t cur_variable, + const poly::Value& sample); + + /** + * Internal implementation of getUnsatCover(). + * @param curVariable The id of the variable (within d_variableOrdering) to + * be considered. This argument is used to manage the recursion internally and + * should always be zero if called externally. + * @param returnFirstInterval If true, the function returns after the first + * interval obtained from a recursive call. The result is not (necessarily) an + * unsat cover, but merely a list of infeasible intervals. + */ + std::vector getUnsatCoverImpl(std::size_t curVariable = 0, + bool returnFirstInterval = false); + + /** + * Main method that checks for the satisfiability of the constraints. + * Recursively explores possible assignments and excludes regions based on the + * coverings. Returns either a covering for the lowest dimension or an empty + * vector. If the covering is empty, the result is SAT and an assignment can + * be obtained from d_assignment. If the covering is not empty, the result is + * UNSAT and an infeasible subset can be extracted from the returned covering. + * Implements Algorithm 2. + * This method itself only takes care of the outermost proof scope and calls + * out to getUnsatCoverImpl() with curVariable set to zero. + * @param returnFirstInterval If true, the function returns after the first + * interval obtained from a recursive call. The result is not (necessarily) an + * unsat cover, but merely a list of infeasible intervals. + */ + std::vector getUnsatCover(bool returnFirstInterval = false); + + void startNewProof(); + /** + * Finish the generated proof (if proofs are enabled) with a scope over the + * given assertions. + */ + ProofGenerator* closeProof(const std::vector& assertions); + + /** Get the proof generator */ + CoveringsProofGenerator* getProof() { return d_proof.get(); } + + private: + /** Check whether proofs are enabled */ + bool isProofEnabled() const { return d_proof != nullptr; } + + /** + * Check whether the current sample satisfies the integrality condition of the + * current variable. Returns true if the variable is not integral or the + * sample is integral. + */ + bool checkIntegrality(std::size_t cur_variable, const poly::Value& value); + /** + * Constructs an interval that excludes the non-integral region around the + * current sample. Assumes !check_integrality(cur_variable, value). + */ + CACInterval buildIntegralityInterval(std::size_t cur_variable, + const poly::Value& value); + + /** + * Check whether the polynomial has a real root above the given value (when + * evaluated over the current assignment). + */ + bool hasRootAbove(const poly::Polynomial& p, const poly::Value& val) const; + /** + * Check whether the polynomial has a real root below the given value (when + * evaluated over the current assignment). + */ + bool hasRootBelow(const poly::Polynomial& p, const poly::Value& val) const; + + /** + * Sort intervals according to section 4.4.1. and removes fully redundant + * intervals as in 4.5. 1. by calling out to cleanIntervals. + * Additionally makes sure to prune proofs for removed intervals. + */ + void pruneRedundantIntervals(std::vector& intervals); + + /** + * Prepare the lazard evaluation object with the current assignment, if the + * lazard lifting is enabled. Otherwise, this function does nothing. + */ + void prepareRootIsolation(LazardEvaluation& le, size_t cur_variable) const; + + /** + * Isolates the real roots of the polynomial `p`. If the lazard lifting is + * enabled, this function uses `le.isolateRealRoots()`, otherwise uses the + * regular `poly::isolate_real_roots()`. + */ + std::vector isolateRealRoots(LazardEvaluation& le, + const poly::Polynomial& p) const; + + /** + * The current assignment. When the method terminates with SAT, it contains a + * model for the input constraints. + */ + poly::Assignment d_assignment; + + /** The set of input constraints to be checked for consistency. */ + Constraints d_constraints; + + /** The computed variable ordering used for this method. */ + std::vector d_variableOrdering; + + /** The object computing the variable ordering. */ + VariableOrdering d_varOrder; + + /** The linear assignment used as an initial guess. */ + std::vector d_initialAssignment; + + /** The proof generator */ + std::unique_ptr d_proof; + + /** The next interval id */ + size_t d_nextIntervalId = 1; +}; + +} // namespace coverings +} // namespace nl +} // namespace arith +} // namespace theory +} // namespace cvc5 + +#endif + +#endif diff --git a/src/theory/arith/nl/coverings/cdcac_utils.cpp b/src/theory/arith/nl/coverings/cdcac_utils.cpp new file mode 100644 index 000000000..ae1abe9d0 --- /dev/null +++ b/src/theory/arith/nl/coverings/cdcac_utils.cpp @@ -0,0 +1,466 @@ +/****************************************************************************** + * Top contributors (to current version): + * Gereon Kremer + * + * This file is part of the cvc5 project. + * + * Copyright (c) 2009-2021 by the authors listed in the file AUTHORS + * in the top-level source directory and their institutional affiliations. + * All rights reserved. See the file COPYING in the top-level source + * directory for licensing information. + * **************************************************************************** + * + * Implements utilities for cdcac. + */ + +#include "theory/arith/nl/coverings/cdcac_utils.h" + +#ifdef CVC5_POLY_IMP + +#include + +#include "theory/arith/nl/coverings/projections.h" + +namespace cvc5 { +namespace theory { +namespace arith { +namespace nl { +namespace coverings { + +using namespace poly; + +bool operator==(const CACInterval& lhs, const CACInterval& rhs) +{ + return lhs.d_interval == rhs.d_interval; +} +bool operator<(const CACInterval& lhs, const CACInterval& rhs) +{ + return lhs.d_interval < rhs.d_interval; +} + +namespace { +/** + * Induces an ordering on poly intervals that is suitable for redundancy + * removal as implemented in clean_intervals. + */ +bool compareForCleanup(const Interval& lhs, const Interval& rhs) +{ + const lp_value_t* ll = &(lhs.get_internal()->a); + const lp_value_t* lu = + lhs.get_internal()->is_point ? ll : &(lhs.get_internal()->b); + const lp_value_t* rl = &(rhs.get_internal()->a); + const lp_value_t* ru = + rhs.get_internal()->is_point ? rl : &(rhs.get_internal()->b); + + int lc = lp_value_cmp(ll, rl); + // Lower bound is smaller + if (lc < 0) return true; + // Lower bound is larger + if (lc > 0) return false; + // Lower bound type is smaller + if (!lhs.get_internal()->a_open && rhs.get_internal()->a_open) return true; + // Lower bound type is larger + if (lhs.get_internal()->a_open && !rhs.get_internal()->a_open) return false; + + // Attention: Here it differs from the regular interval ordering! + int uc = lp_value_cmp(lu, ru); + // Upper bound is smaller + if (uc < 0) return false; + // Upper bound is larger + if (uc > 0) return true; + // Upper bound type is smaller + if (lhs.get_internal()->b_open && !rhs.get_internal()->b_open) return false; + // Upper bound type is larger + if (!lhs.get_internal()->b_open && rhs.get_internal()->b_open) return true; + + // Identical + return false; +} + +/** + * Check whether lhs covers rhs. + */ +bool intervalCovers(const Interval& lhs, const Interval& rhs) +{ + const lp_value_t* ll = &(lhs.get_internal()->a); + const lp_value_t* lu = + lhs.get_internal()->is_point ? ll : &(lhs.get_internal()->b); + const lp_value_t* rl = &(rhs.get_internal()->a); + const lp_value_t* ru = + rhs.get_internal()->is_point ? rl : &(rhs.get_internal()->b); + + int lc = lp_value_cmp(ll, rl); + int uc = lp_value_cmp(lu, ru); + + // Lower bound is smaller and upper bound is larger + if (lc < 0 && uc > 0) return true; + // Lower bound is larger or upper bound is smaller + if (lc > 0 || uc < 0) return false; + + // Now both bounds are identical. + Assert(lc <= 0 && uc >= 0); + Assert(lc == 0 || uc == 0); + + // Lower bound is the same and the bound type is stricter + if (lc == 0 && lhs.get_internal()->a_open && !rhs.get_internal()->a_open) + return false; + // Upper bound is the same and the bound type is stricter + if (uc == 0 && lhs.get_internal()->b_open && !rhs.get_internal()->b_open) + return false; + + // Both bounds are weaker + return true; +} + +/** + * Check whether two intervals connect, assuming lhs < rhs. + * They connect, if their union has no gap. + */ +bool intervalConnect(const Interval& lhs, const Interval& rhs) +{ + Assert(lhs < rhs) << "Can only check for a connection if lhs < rhs."; + + const lp_value_t* lu = poly::get_upper(lhs).get_internal(); + const lp_value_t* rl = poly::get_lower(rhs).get_internal(); + + int c = lp_value_cmp(lu, rl); + if (c < 0) + { + Trace("libpoly::interval_connect") + << lhs << " and " << rhs << " are separated." << std::endl; + return false; + } + if (c > 0) + { + Trace("libpoly::interval_connect") + << lhs << " and " << rhs << " overlap." << std::endl; + return true; + } + Assert(c == 0); + if (poly::get_upper_open(lhs) && poly::get_lower_open(rhs)) + { + Trace("libpoly::interval_connect") + << lhs << " and " << rhs + << " touch and the intermediate point is not covered." << std::endl; + return false; + } + Trace("libpoly::interval_connect") + << lhs << " and " << rhs + << " touch and the intermediate point is covered." << std::endl; + return true; +} + +/** + * Check whether the union of a and b covers rhs. + * First check whether a and b connect, and then defer the containment check to + * intervalCovers. + */ +std::optional intervalsCover(const Interval& a, + const Interval& b, + const Interval& rhs) +{ + if (!intervalConnect(a, b)) return {}; + + Interval c(poly::get_lower(a), + poly::get_lower_open(a), + poly::get_upper(b), + poly::get_upper_open(b)); + + return intervalCovers(c, rhs); +} +} // namespace + +void cleanIntervals(std::vector& intervals) +{ + // Simplifies removal of redundancies later on. + if (intervals.size() < 2) return; + + if (Trace.isOn("cdcac")) + { + Trace("cdcac") << "Before pruning:" << std::endl; + for (const auto& i : intervals) + { + Trace("cdcac") << "\t[" << i.d_id << "] " << i.d_interval << std::endl; + } + } + + // Sort intervals. + std::sort(intervals.begin(), + intervals.end(), + [](const CACInterval& lhs, const CACInterval& rhs) { + return compareForCleanup(lhs.d_interval, rhs.d_interval); + }); + + // First remove intervals that are completely covered by a single other + // interval. This corresponds to removing "redundancies of the first kind" as + // of 4.5.1 The implementation roughly follows + // https://en.cppreference.com/w/cpp/algorithm/remove + std::size_t first = 0; + // Find first interval that is covered. + for (std::size_t n = intervals.size(); first < n - 1; ++first) + { + if (intervalCovers(intervals[first].d_interval, + intervals[first + 1].d_interval)) + { + break; + } + } + // If such an interval exists, remove accordingly. + if (first < intervals.size() - 1) + { + for (std::size_t i = first + 2, n = intervals.size(); i < n; ++i) + { + if (!intervalCovers(intervals[first].d_interval, intervals[i].d_interval)) + { + // Interval is not covered. Move it to the front and bump front. + ++first; + intervals[first] = std::move(intervals[i]); + } + // Else: Interval is covered as well. + } + // Erase trailing values + while (intervals.size() > first + 1) + { + intervals.pop_back(); + } + } + if (Trace.isOn("cdcac")) + { + Trace("cdcac") << "After pruning:" << std::endl; + for (const auto& i : intervals) + { + Trace("cdcac") << "\t[" << i.d_id << "] " << i.d_interval << std::endl; + } + } +} + +void removeRedundantIntervals(std::vector& intervals) +{ + // mid-1 -> interval below + // mid -> current interval + // right -> interval above + size_t mid = 1; + size_t right = 2; + size_t n = intervals.size(); + while (right < n) + { + bool found = false; + for (size_t r = right; r < n; ++r) + { + const auto& below = intervals[mid - 1].d_interval; + const auto& middle = intervals[mid].d_interval; + const auto& above = intervals[r].d_interval; + if (intervalsCover(below, above, middle)) + { + found = true; + break; + } + } + if (found) + { + intervals[mid] = std::move(intervals[right]); + } + else + { + ++mid; + if (mid < right) + { + intervals[mid] = std::move(intervals[right]); + } + } + ++right; + } + while (intervals.size() > mid + 1) + { + intervals.pop_back(); + } +} + +std::vector collectConstraints(const std::vector& intervals) +{ + std::vector res; + for (const auto& i : intervals) + { + res.insert(res.end(), i.d_origins.begin(), i.d_origins.end()); + } + std::sort(res.begin(), res.end()); + auto it = std::unique(res.begin(), res.end()); + res.erase(it, res.end()); + return res; +} + +bool sampleOutside(const std::vector& infeasible, Value& sample) +{ + if (infeasible.empty()) + { + // No infeasible region, just take anything: zero + sample = poly::Integer(); + return true; + } + if (!is_minus_infinity(get_lower(infeasible.front().d_interval))) + { + // First does not cover -oo, just take sufficiently low value + Trace("cdcac") << "Sample before " << infeasible.front().d_interval + << std::endl; + const auto* i = infeasible.front().d_interval.get_internal(); + sample = value_between( + Value::minus_infty().get_internal(), true, &i->a, !i->a_open); + return true; + } + for (std::size_t i = 0, n = infeasible.size(); i < n - 1; ++i) + { + // Search for two subsequent intervals that do not connect + if (!intervalConnect(infeasible[i].d_interval, + infeasible[i + 1].d_interval)) + { + // Two intervals do not connect, take something from the gap + const auto* l = infeasible[i].d_interval.get_internal(); + const auto* r = infeasible[i + 1].d_interval.get_internal(); + + Trace("cdcac") << "Sample between " << infeasible[i].d_interval << " and " + << infeasible[i + 1].d_interval << std::endl; + + if (l->is_point) + { + sample = value_between(&l->a, true, &r->a, !r->a_open); + } + else + { + sample = value_between(&l->b, !l->b_open, &r->a, !r->a_open); + } + return true; + } + else + { + Trace("cdcac") << infeasible[i].d_interval << " and " + << infeasible[i + 1].d_interval << " connect" << std::endl; + } + } + if (!is_plus_infinity(get_upper(infeasible.back().d_interval))) + { + // Last does not cover oo, just take something sufficiently large + Trace("cdcac") << "Sample above " << infeasible.back().d_interval + << std::endl; + const auto* i = infeasible.back().d_interval.get_internal(); + if (i->is_point) + { + sample = + value_between(&i->a, true, Value::plus_infty().get_internal(), true); + } + else + { + sample = value_between( + &i->b, !i->b_open, Value::plus_infty().get_internal(), true); + } + return true; + } + return false; +} + +namespace { +/** + * Replace a polynomial at polys[id] with the given pair of polynomials. + * Also update d_mainPolys in the given interval accordingly. + * If one of the factors (from the pair) is from a lower level (has a different + * main variable), push this factor to the d_downPolys. + * The first factor needs to be a proper polynomial (!is_constant(subst.first)), + * but the second factor may be anything. + */ +void replace_polynomial(PolyVector& polys, + std::size_t id, + std::pair subst, + CACInterval& interval) +{ + Assert(polys[id] == subst.first * subst.second); + Assert(!is_constant(subst.first)); + // Whether polys[id] has already been replaced + bool replaced = false; + poly::Variable var = main_variable(polys[id]); + // The position within interval.d_mainPolys to be replaced. + auto mit = std::find( + interval.d_mainPolys.begin(), interval.d_mainPolys.end(), polys[id]); + if (main_variable(subst.first) == var) + { + // Replace in polys[id] and *mit + polys[id] = subst.first; + if (mit != interval.d_mainPolys.end()) + { + *mit = subst.first; + } + replaced = true; + } + else + { + // Push to d_downPolys + interval.d_downPolys.add(subst.first); + } + // Skip constant poly + if (!is_constant(subst.second)) + { + if (main_variable(subst.second) == var) + { + if (replaced) + { + // Append to polys and d_mainPolys + polys.add(subst.second); + interval.d_mainPolys.add(subst.second); + } + else + { + // Replace in polys[id] and *mit + polys[id] = subst.second; + if (mit != interval.d_mainPolys.end()) + { + *mit = subst.second; + } + replaced = true; + } + } + else + { + // Push to d_downPolys + interval.d_downPolys.add(subst.second); + } + } + Assert(replaced) + << "At least one of the factors should have the main variable"; +} +} // namespace + +void makeFinestSquareFreeBasis(CACInterval& lhs, CACInterval& rhs) +{ + auto& l = lhs.d_upperPolys; + auto& r = rhs.d_lowerPolys; + if (l.empty()) return; + for (std::size_t i = 0, ln = l.size(); i < ln; ++i) + { + for (std::size_t j = 0, rn = r.size(); j < rn; ++j) + { + // All main variables should be the same + Assert(main_variable(l[i]) == main_variable(r[j])); + if (l[i] == r[j]) continue; + Polynomial g = gcd(l[i], r[j]); + if (!is_constant(g)) + { + auto newl = div(l[i], g); + auto newr = div(r[j], g); + replace_polynomial(l, i, std::make_pair(g, newl), lhs); + replace_polynomial(r, j, std::make_pair(g, newr), rhs); + } + } + } + l.reduce(); + r.reduce(); + lhs.d_mainPolys.reduce(); + rhs.d_mainPolys.reduce(); + lhs.d_downPolys.reduce(); + rhs.d_downPolys.reduce(); +} + +} // namespace coverings +} // namespace nl +} // namespace arith +} // namespace theory +} // namespace cvc5 + +#endif diff --git a/src/theory/arith/nl/coverings/cdcac_utils.h b/src/theory/arith/nl/coverings/cdcac_utils.h new file mode 100644 index 000000000..16c01fe62 --- /dev/null +++ b/src/theory/arith/nl/coverings/cdcac_utils.h @@ -0,0 +1,115 @@ +/****************************************************************************** + * Top contributors (to current version): + * Gereon Kremer + * + * This file is part of the cvc5 project. + * + * Copyright (c) 2009-2021 by the authors listed in the file AUTHORS + * in the top-level source directory and their institutional affiliations. + * All rights reserved. See the file COPYING in the top-level source + * directory for licensing information. + * **************************************************************************** + * + * Implements utilities for cdcac. + */ + +#include "cvc5_private.h" + +#ifndef CVC5__THEORY__ARITH__NL__COVERINGS__CDCAC_UTILS_H +#define CVC5__THEORY__ARITH__NL__COVERINGS__CDCAC_UTILS_H + +#ifdef CVC5_POLY_IMP + +#include + +#include + +#include "expr/node.h" +#include "theory/arith/nl/coverings/projections.h" + +namespace cvc5 { +namespace theory { +namespace arith { +namespace nl { +namespace coverings { + +/** + * An interval as specified in section 4.1 of + * https://arxiv.org/pdf/2003.05633.pdf. + * + * It consists of + * - the interval id, used to map the interval to its (partial) proof, + * - the actual interval, either an open or a point interal, + * - the characterizing polynomials of the lower and upper bound, + * - the characterizing polynomials in the main variable, + * - the characterizing polynomials in lower variables and + * - the constraints used to derive this interval. + */ +struct CACInterval +{ + /** Id of this interval to couple it to the proof */ + size_t d_id; + /** The actual interval. */ + poly::Interval d_interval; + /** The polynomials characterizing the lower bound. */ + PolyVector d_lowerPolys; + /** The polynomials characterizing the upper bound. */ + PolyVector d_upperPolys; + /** The characterizing polynomials in the main variable. */ + PolyVector d_mainPolys; + /** The characterizing polynomials in lower variables. */ + PolyVector d_downPolys; + /** The constraints used to derive this interval. */ + std::vector d_origins; +}; +/** Check whether to intervals are the same. */ +bool operator==(const CACInterval& lhs, const CACInterval& rhs); +/** Compare two intervals. */ +bool operator<(const CACInterval& lhs, const CACInterval& rhs); + +/** + * Sort intervals according to section 4.4.1. + * Also removes fully redundant intervals as in 4.5. 1.; these are intervals + * that are fully contained within a single other interval. + */ +void cleanIntervals(std::vector& intervals); + +/** + * Removes redundant intervals as in 4.5. 2.; these are intervals that are + * covered by two other intervals, but not by a single one. Assumes the + * intervals to be sorted and cleaned, i.e. that cleanIntervals(intervals) has + * been called beforehand. + */ +void removeRedundantIntervals(std::vector& intervals); + +/** + * Collect all origins from the list of intervals to construct the origins for a + * whole covering. + */ +std::vector collectConstraints(const std::vector& intervals); + +/** + * Sample a point outside of the infeasible intervals. + * Stores the sample in sample, returns whether such a sample exists. + * If false is returned, the infeasible intervals cover the real line. + * Implements sample_outside() from section 4.3 + */ +bool sampleOutside(const std::vector& infeasible, + poly::Value& sample); + +/** + * Compute the finest square of the upper polynomials of lhs and the lower + * polynomials of rhs. Also pushes reduced polynomials to lower level if + * necessary. + */ +void makeFinestSquareFreeBasis(CACInterval& lhs, CACInterval& rhs); + +} // namespace coverings +} // namespace nl +} // namespace arith +} // namespace theory +} // namespace cvc5 + +#endif + +#endif diff --git a/src/theory/arith/nl/coverings/constraints.cpp b/src/theory/arith/nl/coverings/constraints.cpp new file mode 100644 index 000000000..d857113a3 --- /dev/null +++ b/src/theory/arith/nl/coverings/constraints.cpp @@ -0,0 +1,83 @@ +/****************************************************************************** + * Top contributors (to current version): + * Gereon Kremer + * + * This file is part of the cvc5 project. + * + * Copyright (c) 2009-2021 by the authors listed in the file AUTHORS + * in the top-level source directory and their institutional affiliations. + * All rights reserved. See the file COPYING in the top-level source + * directory for licensing information. + * **************************************************************************** + * + * Implements a container for coverings constraints. + */ + +#include "theory/arith/nl/coverings/constraints.h" + +#ifdef CVC5_POLY_IMP + +#include + +#include "theory/arith/nl/poly_conversion.h" +#include "util/poly_util.h" + +namespace cvc5 { +namespace theory { +namespace arith { +namespace nl { +namespace coverings { + +void Constraints::addConstraint(const poly::Polynomial& lhs, + poly::SignCondition sc, + Node n) +{ + d_constraints.emplace_back(lhs, sc, n); + sortConstraints(); +} + +void Constraints::addConstraint(Node n) +{ + auto c = as_poly_constraint(n, d_varMapper); + addConstraint(c.first, c.second, n); + sortConstraints(); +} + +const Constraints::ConstraintVector& Constraints::getConstraints() const +{ + return d_constraints; +} + +void Constraints::reset() { d_constraints.clear(); } + +void Constraints::sortConstraints() +{ + using Tpl = std::tuple; + std::sort(d_constraints.begin(), + d_constraints.end(), + [](const Tpl& at, const Tpl& bt) { + // Check if a is smaller than b + const poly::Polynomial& a = std::get<0>(at); + const poly::Polynomial& b = std::get<0>(bt); + bool ua = is_univariate(a); + bool ub = is_univariate(b); + if (ua != ub) return ua; + std::size_t tda = poly_utils::totalDegree(a); + std::size_t tdb = poly_utils::totalDegree(b); + if (tda != tdb) return tda < tdb; + return degree(a) < degree(b); + }); + for (auto& c : d_constraints) + { + auto* p = std::get<0>(c).get_internal(); + lp_polynomial_set_external(p); + } +} + +} // namespace coverings +} // namespace nl +} // namespace arith +} // namespace theory +} // namespace cvc5 + +#endif diff --git a/src/theory/arith/nl/coverings/constraints.h b/src/theory/arith/nl/coverings/constraints.h new file mode 100644 index 000000000..7fe521a1e --- /dev/null +++ b/src/theory/arith/nl/coverings/constraints.h @@ -0,0 +1,93 @@ +/****************************************************************************** + * Top contributors (to current version): + * Gereon Kremer + * + * This file is part of the cvc5 project. + * + * Copyright (c) 2009-2021 by the authors listed in the file AUTHORS + * in the top-level source directory and their institutional affiliations. + * All rights reserved. See the file COPYING in the top-level source + * directory for licensing information. + * **************************************************************************** + * + * Implements a container for coverings constraints. + */ + +#include "cvc5_private.h" + +#ifndef CVC5__THEORY__ARITH__NL__COVERINGS__CONSTRAINTS_H +#define CVC5__THEORY__ARITH__NL__COVERINGS__CONSTRAINTS_H + +#ifdef CVC5_POLY_IMP + +#include + +#include +#include + +#include "theory/arith/nl/poly_conversion.h" + +namespace cvc5 { +namespace theory { +namespace arith { +namespace nl { +namespace coverings { + +class Constraints +{ + public: + /** Type alias for a list of constraints. */ + using Constraint = std::tuple; + using ConstraintVector = std::vector; + + VariableMapper& varMapper() { return d_varMapper; } + + /** + * Add a constraint (represented by a polynomial and a sign condition) to the + * list of constraints. + */ + void addConstraint(const poly::Polynomial& lhs, + poly::SignCondition sc, + Node n); + + /** + * Add a constraints (represented by a node) to the list of constraints. + * The given node can either be a negation (NOT) or a suitable relation symbol + * as checked by is_suitable_relation(). + */ + void addConstraint(Node n); + + /** + * Gives the list of added constraints. + */ + const ConstraintVector& getConstraints() const; + + /** + * Remove all constraints. + */ + void reset(); + + private: + /** + * A list of constraints, each comprised of a polynomial and a sign + * condition. + */ + ConstraintVector d_constraints; + + /** + * A mapping from cvc5 variables to poly variables. + */ + VariableMapper d_varMapper; + + void sortConstraints(); +}; + +} // namespace coverings +} // namespace nl +} // namespace arith +} // namespace theory +} // namespace cvc5 + +#endif + +#endif diff --git a/src/theory/arith/nl/coverings/lazard_evaluation.cpp b/src/theory/arith/nl/coverings/lazard_evaluation.cpp new file mode 100644 index 000000000..3d60e8677 --- /dev/null +++ b/src/theory/arith/nl/coverings/lazard_evaluation.cpp @@ -0,0 +1,995 @@ +#include "theory/arith/nl/coverings/lazard_evaluation.h" + +#ifdef CVC5_POLY_IMP + +#include "base/check.h" +#include "base/output.h" +#include "smt/smt_statistics_registry.h" +#include "util/statistics_stats.h" + +#ifdef CVC5_USE_COCOA + +#include + +#include + +namespace cvc5::theory::arith::nl::coverings { + +struct LazardEvaluationStats +{ + IntStat d_directAssignments = + smtStatisticsRegistry().registerInt("theory::arith::coverings::lazard-direct"); + IntStat d_ranAssignments = + smtStatisticsRegistry().registerInt("theory::arith::coverings::lazard-rans"); + IntStat d_evaluations = + smtStatisticsRegistry().registerInt("theory::arith::coverings::lazard-evals"); + IntStat d_reductions = + smtStatisticsRegistry().registerInt("theory::arith::coverings::lazard-reduce"); +}; + +struct LazardEvaluationState; +std::ostream& operator<<(std::ostream& os, const LazardEvaluationState& state); + +/** + * This class holds and implements all the technicalities required to map + * polynomials from libpoly into CoCoALib, perform these computations properly + * within CoCoALib and map the result back to libpoly. + * + * We need to be careful to perform all computations in the proper polynomial + * rings, both to be correct and because CoCoALib explicitly requires it. As we + * change the ring we are computing it all the time, we also need appropriate + * ring homomorphisms to map polynomials from one into the other. We first give + * a short overview of our approach, then describe the various polynomial rings + * that are used, and then discuss which rings are used where. + * + * Inputs: + * - (real) variables x_0, ..., x_n + * - real algebraic numbers a_0, ..., a_{n-1} with + * - defining polynomials p_0, ..., p_{n-1}; p_i from Q[x_i] + * - a polynomial q over all variables x_0, ..., x_n + * + * We first iteratively build the field extensions Q(a_0), Q(a_0, a_2) ... + * Instead of the extension field Q(a_0), we use the isomorphic quotient ring + * Q[x_0]/ and recursively extend it with a_1, etc, in the same way. Doing + * this recursive construction naively fails: (Q[x_0]/)[x_1]/ is not + * necessarily a proper field as p_1 (though a minimal polynomial in Q[x_1]) may + * factor over Q[x_0]/. Consider p_0 = x_0*x_0-2 and p_1 = + * x_1*x_1*x_1*x_1-2 as an example, where p_1 factors into + * (x_1*x_1-x_0)*(x_1*x_1+x_0) over Q[x_0]/. We overcome this by explicitly + * computing this factorization and using the factor that vanishes over {x_0 -> + * a_0, x_1 -> a_1 } as the minimal polynomial of a_1 over Q[x_0]/. + * + * After we have built the field extensions in that way, we iteratively push q + * through the field extensions, each one extended to a polynomial ring over all + * x_0, ..., x_n. When in the k'th field extension, we check whether the k'th + * minimal polynomial divides q. If so, q would vanish in the next step and we + * instead set q = q/p_{k}. Only then we map q into K_{k+1}. + * + * Eventually, we end up with q in Q(a_0, ..., a_{n-1})[x_n]. This polynomial is + * univariate conceptually, and we want to compute its roots. However, it is not + * technically univariate and we need to make it so. We can do this by computing + * the Gröbner basis of the q and all minimal polynomials p_i with an + * elimination order with x_n at the bottom over Q[x_0, ..., x_n]. + * We then collect the polynomials + * that are univariate in x_n from the Gröbner basis. We can show that the roots + * of these polynomials are a superset of the roots we are looking for. + * + * + * To implement all that, we construct the following polynomial rings: + * - K_i: K_0 = Q, K_{i+1} = K_{i}[x_i]/ (with p_i reduced w.r.t. K_i) + * - R_i = K_i[x_i] + * - J_i = K_i[x_i, ..., x_n] = R_i[x_{i+1}, ..., x_n] + * + * While p_i conceptually live in Q[x_i], we immediately convert them from + * libpoly into R_i. We then factor it there, obtaining the actual minimal + * polynomial p_i that we use to construct K_{i+1}. We do this to construct all + * K_i and R_i. We then reduce q, initially in Q[x_0, ..., x_n] = J_0. We check + * in J_i whether p_i divides q (and if so divide q by p_i). To do + * this, we need to embed p_i into J_i. We then + * map q from J_i to J_{i+1}. While obvious in theory, this is somewhat tricky + * in practice as J_i and J_{i+1} have no direct relationship. + * Finally, we need to push all p_i and the final q back into J_0 = Q[x_0, ..., + * x_n] to compute the Gröbner basis. + * + * We thus furthermore store the following ring homomorphisms: + * - phom_i: R_i -> J_i (canonical embedding) + * - qhom_i: J_i -> J_{i+1} (hand-crafted homomorphism) + * + * We can sometimes avoid this construction for individual variables, i.e., if + * the assignment for x_i already lives (algebraically) in K_i. This can be the + * case if a_i is rational; in general, we check whether the vanishing factor + * of p_i is linear. If so, it has the form x_i-r where is some term in lower + * variables. We store r as the "direct assignment" in d_direct[i] and use it + * to directly replace x_i when appropriate. Also, we have K_i = K_{i-1}. + * + */ +struct LazardEvaluationState +{ + CoCoA::GlobalManager d_gm; + + /** + * Statistics about the lazard evaluation. + * Although this class is short-lived, there is no need to make the statistics + * static or store them persistently: this is handled by the statistics + * registry, which recovers statistics from their name. + * This member is mutable to allow collecting statistics from const methods. + */ + mutable LazardEvaluationStats d_stats; + + /** + * Maps libpoly variables to indets in J0. Used when constructing the input + * polynomial q in the first polynomial ring J0. + */ + std::map d_varQ; + /** + * Maps CoCoA indets back to to libpoly variables. + * Use when converting CoCoA RingElems to libpoly polynomials, either when + * checking whether a factor vanishes or when returning the univariate + * elements of the final Gröbner basis. The CoCoA indets are identified by the + * pair of the ring id and the indet identifier. Hence, we can put all of them + * in one map, no matter which ring they belong to. + */ + std::map, poly::Variable> d_varCoCoA; + + /** + * The minimal polynomials p_i used for constructing d_K. + * If a variable x_i has a rational assignment, p_i holds no value (i.e. + * d_p[i] == CoCoA::RingElem()). + */ + std::vector d_p; + + /** + * The sequence of extension fields. + * K_0 = Q, K_{i+1} = K_i[x_i]/ + * Every K_i is a field. + */ + std::vector d_K = {CoCoA::RingQQ()}; + /** + * R_i = K_i[x_i] + * Every R_i is a univariate polynomial ring over the field K_i. + */ + std::vector d_R; + /** + * J_i = K_i[x_i, ..., x_n] + * All J_i are constructed with CoCoA::lex ordering, just to make sure that + * the Gröbner basis of J_0 is computed as necessary. + */ + std::vector d_J; + + /** + * Custom homomorphism from R_i to J_i. PolyAlgebraHom with + * Indets(R_i) = (x_i) --> (x_i) + */ + std::vector d_phom; + /** + * Custom homomorphism from J_i to J_{i+1} + * If assignment of x_i is rational a PolyAlgebraHom with + * Indets(J_i) = (x_i,...,x_n) --> (a_i,x_{i+1},...,x_n) + * Otherwise a PolyRingHom with: + * - CoeffHom: K_{i-1} --> R_{i-1} --> K_i + * - (x_i,...,x_n) --> (x_i,x_{i+1},...,x_n), x_i = Indet(R_{i-1}) + */ + std::vector d_qhom; + + /** + * The base ideal for the Gröbner basis we compute in the end. Contains all + * p_i pushed into J_0. + */ + std::vector d_GBBaseIdeal; + + /** + * The current assignment, used to identify the vanishing factor to construct + * K_i. + */ + poly::Assignment d_assignment; + /** + * The libpoly variables in proper order. Directly correspond to x_0,...,x_n. + */ + std::vector d_variables; + /** + * Direct assignments for variables x_i as polynomials in lower variables. + * If the assignment for x_i is no direct assignment, d_direct[i] holds no + * value. + */ + std::vector> d_direct; + + /** + * Converts a libpoly integer to a CoCoA::BigInt. + */ + CoCoA::BigInt convert(const poly::Integer& i) const + { + return CoCoA::BigIntFromMPZ(poly::detail::cast_to_gmp(&i)->get_mpz_t()); + } + /** + * Converts a libpoly dyadic rational to a CoCoA::BigRat. + */ + CoCoA::BigRat convert(const poly::DyadicRational& dr) const + { + return CoCoA::BigRat(convert(poly::numerator(dr)), + convert(poly::denominator(dr))); + } + /** + * Converts a libpoly rational to a CoCoA::BigRat. + */ + CoCoA::BigRat convert(const poly::Rational& r) const + { + return CoCoA::BigRatFromMPQ(poly::detail::cast_to_gmp(&r)->get_mpq_t()); + } + /** + * Converts a univariate libpoly polynomial p in variable var to CoCoA. It + * assumes that p is a minimal polynomial p_i over variable x_i for the + * highest variable x_i known yet. It thus directly constructs p_i in R_i. + */ + CoCoA::RingElem convertMiPo(const poly::UPolynomial& p, + const poly::Variable& var) const + { + std::vector coeffs = poly::coefficients(p); + CoCoA::RingElem res(d_R.back()); + CoCoA::RingElem v = CoCoA::indet(d_R.back(), 0); + CoCoA::RingElem mult(d_R.back(), 1); + for (const auto& c : coeffs) + { + if (!poly::is_zero(c)) + { + res += convert(c) * mult; + } + mult *= v; + } + return res; + } + + /** + * Checks whether the given CoCoA polynomial evaluates to zero over the + * current libpoly assignment. The polynomial should live over the current + * R_i. + */ + bool evaluatesToZero(const CoCoA::RingElem& cp) const + { + Assert(CoCoA::owner(cp) == d_R.back()); + poly::Polynomial pp = convert(cp); + return poly::evaluate_constraint(pp, d_assignment, poly::SignCondition::EQ); + } + + /** + * Maps p from J_i to J_{i-1}. There can be no suitable homomorphism, and we + * thus manually decompose p into its terms and reconstruct them in J_{i-1}. + * If a_{i-1} is rational, we know that the coefficient rings of J_i and + * J_{i-1} are identical (K_{i-1} and K_{i-2}, respectively). We can thus + * immediately use coefficients from J_i as coefficients in J_{i-1}. + * Otherwise, we map coefficients from K_{i-1} to their canonical + * representation in R_{i-1} and then use d_phom[i-1] to map those into + * J_{i-1}. Afterwards, we iterate over the power product of the term + * reconstruct it in J_{i-1}. + */ + CoCoA::RingElem pushDownJ(const CoCoA::RingElem& p, size_t i) const + { + Trace("nl-cov::lazard") << "Push " << p << " from " << d_J[i] << " to " + << d_J[i - 1] << std::endl; + Assert(CoCoA::owner(p) == d_J[i]); + CoCoA::RingElem res(d_J[i - 1]); + for (CoCoA::SparsePolyIter it = CoCoA::BeginIter(p); !CoCoA::IsEnded(it); + ++it) + { + CoCoA::RingElem coeff = CoCoA::coeff(it); + Assert(CoCoA::owner(coeff) == d_K[i]); + if (d_direct[i - 1]) + { + Assert(CoCoA::CoeffRing(d_J[i]) == CoCoA::CoeffRing(d_J[i - 1])); + coeff = CoCoA::CoeffEmbeddingHom(d_J[i - 1])(coeff); + } + else + { + coeff = CoCoA::CanonicalRepr(coeff); + Assert(CoCoA::owner(coeff) == d_R[i - 1]); + coeff = d_phom[i - 1](coeff); + } + Assert(CoCoA::owner(coeff) == d_J[i - 1]); + auto pp = CoCoA::PP(it); + std::vector indets = CoCoA::IndetsIn(pp); + for (size_t k = 0; k < indets.size(); ++k) + { + long exp = CoCoA::exponent(pp, indets[k]); + auto ind = CoCoA::indet(d_J[i - 1], indets[k] + 1); + coeff *= CoCoA::power(ind, exp); + } + res += coeff; + } + return res; + } + + /** + * Uses pushDownJ repeatedly to map p from J_{i+1} to J_0. + * Is used to map the minimal polynomials p_i and the reduced polynomial q + * into J_0 to eventually compute the Gröbner basis. + */ + CoCoA::RingElem pushDownJ0(const CoCoA::RingElem& p, size_t i) const + { + CoCoA::RingElem res = p; + for (; i > 0; --i) + { + Trace("nl-cov::lazard") << "Pushing " << p << " from J" << i << " to J" + << i - 1 << std::endl; + res = pushDownJ(res, i); + } + return res; + } + + /** + * Add the next R_i: + * - add variable x_i to d_variables + * - extract the variable name + * - construct R_i = K_i[x_i] + * - add new variable to d_varCoCoA + */ + void addR(const poly::Variable& var) + { + d_variables.emplace_back(var); + if (Trace.isOn("nl-cov::lazard")) + { + std::string vname = lp_variable_db_get_name( + poly::Context::get_context().get_variable_db(), var.get_internal()); + d_R.emplace_back(CoCoA::NewPolyRing(d_K.back(), {CoCoA::symbol(vname)})); + } + else + { + d_R.emplace_back(CoCoA::NewPolyRing(d_K.back(), {CoCoA::NewSymbol()})); + } + Trace("nl-cov::lazard") << "R" << d_R.size() - 1 << " = " << d_R.back() + << std::endl; + d_varCoCoA.emplace(std::make_pair(CoCoA::RingID(d_R.back()), 0), var); + } + + /** + * Add the next K_{i+1} from a minimal polynomial: + * - store dummy value in d_direct + * - store the minimal polynomial p_i in d_p + * - construct K_{i+1} = R_i/ + */ + void addK(const poly::Variable& var, const CoCoA::RingElem& p) + { + d_direct.emplace_back(); + d_p.emplace_back(p); + Trace("nl-cov::lazard") << "p" << d_p.size() - 1 << " = " << d_p.back() + << std::endl; + d_K.emplace_back(CoCoA::NewQuotientRing(d_R.back(), CoCoA::ideal(p))); + Trace("nl-cov::lazard") << "K" << d_K.size() - 1 << " = " << d_K.back() + << std::endl; + } + + /** + * Add the next K_{i+1} from a rational assignment: + * - store assignment a_i in d_direct + * - store a dummy minimal polynomial in d_p + * - construct K_{i+1} as copy of K_i + */ + void addKRational(const poly::Variable& var, const CoCoA::RingElem& r) + { + d_direct.emplace_back(r); + d_p.emplace_back(); + Trace("nl-cov::lazard") << "x" << d_p.size() - 1 << " = " << r << std::endl; + d_K.emplace_back(d_K.back()); + Trace("nl-cov::lazard") << "K" << d_K.size() - 1 << " = " << d_K.back() + << std::endl; + } + + /** + * Finish the whole construction by adding the free variable: + * - add R_n by calling addR(var) + * - construct all J_i + * - construct all p homomorphisms (R_i --> J_i) + * - construct all q homomorphisms (J_i --> J_{i+1}) + * - fill the mapping d_varQ (libpoly -> J_0) + * - fill the mapping d_varCoCoA (J_n -> libpoly) + * - fill d_GBBaseIdeal with p_i mapped to J_0 + */ + void addFreeVariable(const poly::Variable& var) + { + Trace("nl-cov::lazard") << "Add free variable " << var << std::endl; + addR(var); + std::vector symbols; + for (size_t i = 0; i < d_R.size(); ++i) + { + symbols.emplace_back(CoCoA::symbols(d_R[i]).back()); + } + for (size_t i = 0; i < d_R.size(); ++i) + { + d_J.emplace_back(CoCoA::NewPolyRing(d_K[i], symbols, CoCoA::lex)); + Trace("nl-cov::lazard") << "J" << d_J.size() - 1 << " = " << d_J.back() + << std::endl; + symbols.erase(symbols.begin()); + // R_i --> J_i + d_phom.emplace_back( + CoCoA::PolyAlgebraHom(d_R[i], d_J[i], {CoCoA::indet(d_J[i], 0)})); + Trace("nl-cov::lazard") << "R" << i << " --> J" << i << ": " << d_phom.back() + << std::endl; + if (i > 0) + { + Trace("nl-cov::lazard") + << "Constructing J" << i - 1 << " --> J" << i << ": " << std::endl; + Trace("nl-cov::lazard") << "Constructing " << d_J[i - 1] << " --> " + << d_J[i] << ": " << std::endl; + if (d_direct[i - 1]) + { + Trace("nl-cov::lazard") << "Using " << d_variables[i - 1] << " for " + << CoCoA::indet(d_J[i - 1], 0) << std::endl; + Assert(CoCoA::CoeffRing(d_J[i]) == CoCoA::owner(*d_direct[i - 1])); + std::vector indets = { + CoCoA::RingElem(d_J[i], *d_direct[i - 1])}; + for (size_t j = 0; j < d_R.size() - i; ++j) + { + indets.push_back(CoCoA::indet(d_J[i], j)); + } + d_qhom.emplace_back( + CoCoA::PolyAlgebraHom(d_J[i - 1], d_J[i], indets)); + } + else + { + // K_{i-1} --> R_{i-1} + auto K2R = CoCoA::CoeffEmbeddingHom(d_R[i - 1]); + Assert(CoCoA::domain(K2R) == d_K[i - 1]); + Assert(CoCoA::codomain(K2R) == d_R[i - 1]); + // R_{i-1} --> K_i + auto R2K = CoCoA::QuotientingHom(d_K[i]); + Assert(CoCoA::domain(R2K) == d_R[i - 1]); + Assert(CoCoA::codomain(R2K) == d_K[i]); + // K_i --> J_i + auto K2J = CoCoA::CoeffEmbeddingHom(d_J[i]); + Assert(CoCoA::domain(K2J) == d_K[i]); + Assert(CoCoA::codomain(K2J) == d_J[i]); + // J_{i-1} --> J_i, consisting of + // - a homomorphism for the coefficients + // - a mapping for the indets + // Constructs [phom_i(x_i), x_i+1, ..., x_n] + std::vector indets = { + K2J(R2K(CoCoA::indet(d_R[i - 1], 0)))}; + for (size_t j = 0; j < d_R.size() - i; ++j) + { + indets.push_back(CoCoA::indet(d_J[i], j)); + } + d_qhom.emplace_back( + CoCoA::PolyRingHom(d_J[i - 1], d_J[i], R2K(K2R), indets)); + } + Trace("nl-cov::lazard") << "J" << i - 1 << " --> J" << i << ": " + << d_qhom.back() << std::endl; + } + } + for (size_t i = 0; i < d_variables.size(); ++i) + { + d_varQ.emplace(d_variables[i], CoCoA::indet(d_J[0], i)); + } + for (size_t i = 0; i < d_variables.size(); ++i) + { + d_varCoCoA.emplace(std::make_pair(CoCoA::RingID(d_J[0]), i), + d_variables[i]); + } + + d_GBBaseIdeal.clear(); + for (size_t i = 0; i < d_p.size(); ++i) + { + if (d_direct[i]) continue; + Trace("nl-cov::lazard") << "Apply " << d_phom[i] << " to " << d_p[i] + << " from " << CoCoA::owner(d_p[i]) << std::endl; + d_GBBaseIdeal.emplace_back(pushDownJ0(d_phom[i](d_p[i]), i)); + } + + Trace("nl-cov::lazard") << "Finished construction" << std::endl + << *this << std::endl; + } + + /** + * Helper class for conversion from libpoly to CoCoA polynomials. + * The lambda can not capture anything, as it needs to be of type + * lp_polynomial_traverse_f. + */ + struct CoCoAPolyConstructor + { + const LazardEvaluationState& d_state; + CoCoA::RingElem d_result; + }; + + /** + * Convert the polynomial q to CoCoA into J_0. + */ + CoCoA::RingElem convertQ(const poly::Polynomial& q) const + { + CoCoAPolyConstructor cmd{*this}; + // Do the actual conversion + cmd.d_result = CoCoA::RingElem(d_J[0]); + lp_polynomial_traverse_f f = [](const lp_polynomial_context_t* ctx, + lp_monomial_t* m, + void* data) { + CoCoAPolyConstructor* d = static_cast(data); + CoCoA::BigInt coeff = d->d_state.convert(*poly::detail::cast_from(&m->a)); + CoCoA::RingElem re(d->d_state.d_J[0], coeff); + for (size_t i = 0; i < m->n; ++i) + { + // variable exponent pair + CoCoA::RingElem var = d->d_state.d_varQ.at(m->p[i].x); + re *= CoCoA::power(var, m->p[i].d); + } + d->d_result += re; + }; + lp_polynomial_traverse(q.get_internal(), f, &cmd); + return cmd.d_result; + } + /** + * Actual (recursive) implementation of converting a CoCoA polynomial to a + * libpoly polynomial. As libpoly polynomials only have integer coefficients, + * we need to maintain an integer denominator to normalize all terms to the + * same denominator. + */ + poly::Polynomial convertImpl(const CoCoA::RingElem& p, + poly::Integer& denominator) const + { + Trace("nl-cov::lazard") << "Converting " << p << std::endl; + denominator = poly::Integer(1); + poly::Polynomial res; + for (CoCoA::SparsePolyIter i = CoCoA::BeginIter(p); !CoCoA::IsEnded(i); ++i) + { + poly::Polynomial coeff; + poly::Integer denom(1); + CoCoA::BigRat numcoeff; + if (CoCoA::IsRational(numcoeff, CoCoA::coeff(i))) + { + poly::Rational rat(mpq_class(CoCoA::mpqref(numcoeff))); + denom = poly::denominator(rat); + coeff = poly::numerator(rat); + } + else + { + coeff = convertImpl(CoCoA::CanonicalRepr(CoCoA::coeff(i)), denom); + } + if (!CoCoA::IsOne(CoCoA::PP(i))) + { + std::vector exponents; + CoCoA::exponents(exponents, CoCoA::PP(i)); + for (size_t vid = 0; vid < exponents.size(); ++vid) + { + if (exponents[vid] == 0) continue; + const auto& ring = CoCoA::owner(p); + poly::Variable v = + d_varCoCoA.at(std::make_pair(CoCoA::RingID(ring), vid)); + coeff *= poly::Polynomial(poly::Integer(1), v, exponents[vid]); + } + } + if (denom != denominator) + { + poly::Integer g = gcd(denom, denominator); + res = res * (denom / g) + coeff * (denominator / g); + denominator *= (denom / g); + } + else + { + res += coeff; + } + } + Trace("nl-cov::lazard") << "-> " << res << std::endl; + return res; + } + /** + * Actually convert a CoCoA RingElem to a libpoly polynomial. + * Requires d_varCoCoA to be filled appropriately. + */ + poly::Polynomial convert(const CoCoA::RingElem& p) const + { + poly::Integer denom; + return convertImpl(p, denom); + } + + /** + * Now reduce the polynomial qpoly: + * - convert qpoly into J_0 and factor it + * - for every factor q: + * - for every x_i: + * - if a_i is rational: + * - while q[x_i -> a_i] == 0 + * - q = q / (x_i - a_i) + * - set q = q[x_i -> a_i] + * - otherwise + * - obtain tmp = phom_i(p_i) + * - while tmp divides q + * - q = q / tmp + * - embed q = qhom_i(q) + * - compute (reduced) GBasis(p_0, ..., p_{n-i}, q) + * - collect and convert basis elements univariate in the free variable + */ + std::vector reduce(const poly::Polynomial& qpoly) const + { + d_stats.d_evaluations++; + std::vector res; + Trace("nl-cov::lazard") << "Reducing " << qpoly << std::endl; + auto input = convertQ(qpoly); + Assert(CoCoA::owner(input) == d_J[0]); + auto factorization = CoCoA::factor(input); + for (const auto& f : factorization.myFactors()) + { + Trace("nl-cov::lazard") << "-> factor " << f << std::endl; + CoCoA::RingElem q = f; + for (size_t i = 0; i < d_J.size() - 1; ++i) + { + Trace("nl-cov::lazard") << "i = " << i << std::endl; + if (d_direct[i]) + { + Trace("nl-cov::lazard") + << "Substitute " << d_variables[i] << " = " << *d_direct[i] + << " into " << q << " from " << CoCoA::owner(q) << std::endl; + auto indets = CoCoA::indets(d_J[i]); + auto var = indets[0]; + Assert(CoCoA::CoeffRing(d_J[i]) == CoCoA::owner(*d_direct[i])); + indets[0] = CoCoA::RingElem(d_J[i], *d_direct[i]); + auto hom = CoCoA::PolyAlgebraHom(d_J[i], d_J[i], indets); + while (CoCoA::IsZero(hom(q))) + { + q = q / (var - indets[0]); + d_stats.d_reductions++; + } + // substitute x_i -> a_i + q = hom(q); + Trace("nl-cov::lazard") + << "-> " << q << " from " << CoCoA::owner(q) << std::endl; + } + else + { + auto tmp = d_phom[i](d_p[i]); + while (CoCoA::IsDivisible(q, tmp)) + { + q /= tmp; + d_stats.d_reductions++; + } + } + q = d_qhom[i](q); + } + Trace("nl-cov::lazard") << "-> reduced to " << q << std::endl; + Assert(CoCoA::owner(q) == d_J.back()); + std::vector ideal = d_GBBaseIdeal; + ideal.emplace_back(pushDownJ0(q, d_J.size() - 1)); + Trace("nl-cov::lazard") << "-> ideal " << ideal << std::endl; + auto basis = CoCoA::ReducedGBasis(CoCoA::ideal(ideal)); + Trace("nl-cov::lazard") << "-> basis " << basis << std::endl; + for (const auto& belem : basis) + { + Trace("nl-cov::lazard") << "-> retrieved " << belem << std::endl; + auto pres = convert(belem); + Trace("nl-cov::lazard") << "-> converted " << pres << std::endl; + // These checks are orthogonal! + if (poly::is_univariate(pres) + && poly::is_univariate_over_assignment(pres, d_assignment)) + { + res.emplace_back(pres); + } + } + } + return res; + } +}; + +std::ostream& operator<<(std::ostream& os, const LazardEvaluationState& state) +{ + for (size_t i = 0; i < state.d_K.size(); ++i) + { + os << "K" << i << " = " << state.d_K[i] << std::endl; + os << "R" << i << " = " << state.d_R[i] << std::endl; + os << "J" << i << " = " << state.d_J[i] << std::endl; + + os << "R" << i << " --> J" << i << ": " << state.d_phom[i] << std::endl; + if (i > 0) + { + os << "J" << (i - 1) << " --> J" << i << ": " << state.d_qhom[i - 1] + << std::endl; + } + } + os << "GBBaseIdeal: " << state.d_GBBaseIdeal << std::endl; + os << "Done" << std::endl; + return os; +} + +LazardEvaluation::LazardEvaluation() + : d_state(std::make_unique()) +{ +} + +LazardEvaluation::~LazardEvaluation() {} + +/** + * Add a new variable with real algebraic number: + * - add var = ran to the assignment + * - add the next R_i by calling addR(var) + * - if ran is actually rational: + * - obtain the rational and call addKRational() + * - otherwise: + * - convert the minimal polynomial and identify vanishing factor + * - add the next K_i with the vanishing factor by valling addK() + */ +void LazardEvaluation::add(const poly::Variable& var, const poly::Value& val) +{ + Trace("nl-cov::lazard") << "Adding " << var << " -> " << val << std::endl; + try + { + d_state->d_assignment.set(var, val); + d_state->addR(var); + + std::optional rational; + poly::UPolynomial polymipo; + if (poly::is_algebraic_number(val)) + { + const poly::AlgebraicNumber& ran = poly::as_algebraic_number(val); + const poly::DyadicInterval& di = poly::get_isolating_interval(ran); + if (poly::is_point(di)) + { + rational = d_state->convert(poly::get_point(di)); + } + else + { + Trace("nl-cov::lazard") << "\tis proper ran" << std::endl; + polymipo = poly::get_defining_polynomial(ran); + } + } + else + { + Assert(poly::is_dyadic_rational(val) || poly::is_integer(val) + || poly::is_rational(val)); + if (poly::is_dyadic_rational(val)) + { + rational = d_state->convert(poly::as_dyadic_rational(val)); + } + else if (poly::is_integer(val)) + { + rational = CoCoA::BigRat(d_state->convert(poly::as_integer(val)), 1); + } + else if (poly::is_rational(val)) + { + rational = d_state->convert(poly::as_rational(val)); + } + } + + if (rational) + { + d_state->addKRational(var, + CoCoA::RingElem(d_state->d_K.back(), *rational)); + d_state->d_stats.d_directAssignments++; + return; + } + Trace("nl-cov::lazard") << "Got mipo " << polymipo << std::endl; + auto mipo = d_state->convertMiPo(polymipo, var); + Trace("nl-cov::lazard") << "Factoring " << mipo << " from " + << CoCoA::owner(mipo) << std::endl; + auto factorization = CoCoA::factor(mipo); + Trace("nl-cov::lazard") << "-> " << factorization << std::endl; + bool used_factor = false; + for (const auto& f : factorization.myFactors()) + { + if (d_state->evaluatesToZero(f)) + { + Assert(CoCoA::deg(f) > 0 && CoCoA::NumTerms(f) <= 2); + if (CoCoA::deg(f) == 1) + { + auto rat = -CoCoA::ConstantCoeff(f) / CoCoA::LC(f); + Trace("nl-cov::lazard") << "Using linear factor " << f << " -> " << var + << " = " << rat << std::endl; + d_state->addKRational(var, rat); + d_state->d_stats.d_directAssignments++; + } + else + { + Trace("nl-cov::lazard") << "Using nonlinear factor " << f << std::endl; + d_state->addK(var, f); + d_state->d_stats.d_ranAssignments++; + } + used_factor = true; + break; + } + else + { + Trace("nl-cov::lazard") << "Skipping " << f << std::endl; + } + } + Assert(used_factor); + } + catch (CoCoA::ErrorInfo& e) + { + e.myOutputSelf(std::cerr); + throw; + } +} + +void LazardEvaluation::addFreeVariable(const poly::Variable& var) +{ + try + { + d_state->addFreeVariable(var); + } + catch (CoCoA::ErrorInfo& e) + { + e.myOutputSelf(std::cerr); + throw; + } +} + +std::vector LazardEvaluation::reducePolynomial( + const poly::Polynomial& p) const +{ + try + { + return d_state->reduce(p); + } + catch (CoCoA::ErrorInfo& e) + { + e.myOutputSelf(std::cerr); + throw; + } + return {p}; +} + +std::vector LazardEvaluation::isolateRealRoots( + const poly::Polynomial& q) const +{ + poly::Assignment a; + std::vector roots; + // reduce q to a set of reduced polynomials p + for (const auto& p : reducePolynomial(q)) + { + // collect all real roots except for -infty, none, +infty + Trace("nl-cov::lazard") << "Isolating roots of " << p << std::endl; + Assert(poly::is_univariate(p) && poly::is_univariate_over_assignment(p, a)); + std::vector proots = poly::isolate_real_roots(p, a); + for (const auto& r : proots) + { + if (poly::is_minus_infinity(r)) continue; + if (poly::is_none(r)) continue; + if (poly::is_plus_infinity(r)) continue; + roots.emplace_back(r); + } + } + std::sort(roots.begin(), roots.end()); + return roots; +} + +/** + * Compute the infeasible regions of the given polynomial according to a sign + * condition. We first reduce the polynomial and isolate the real roots of every + * resulting polynomial. We store all roots (except for -infty, +infty and none) + * in a set. Then, we transform the set of roots into a list of infeasible + * regions by generating intervals between -infty and the first root, in between + * every two consecutive roots and between the last root and +infty. While doing + * this, we only keep those intervals that are actually infeasible for the + * original polynomial q over the partial assignment. Finally, we go over the + * intervals and aggregate consecutive intervals that connect. + */ +std::vector LazardEvaluation::infeasibleRegions( + const poly::Polynomial& q, poly::SignCondition sc) const +{ + std::vector roots = isolateRealRoots(q); + + // generate all intervals + // (-infty,root_0), [root_0], (root_0,root_1), ..., [root_m], (root_m,+infty) + // if q is true over d_assignment x interval (represented by a sample) + std::vector res; + poly::Value last = poly::Value::minus_infty(); + for (const auto& r : roots) + { + poly::Value sample = poly::value_between(last, true, r, true); + d_state->d_assignment.set(d_state->d_variables.back(), sample); + if (!poly::evaluate_constraint(q, d_state->d_assignment, sc)) + { + res.emplace_back(last, true, r, true); + } + d_state->d_assignment.set(d_state->d_variables.back(), r); + if (!poly::evaluate_constraint(q, d_state->d_assignment, sc)) + { + res.emplace_back(r); + } + last = r; + } + poly::Value sample = + poly::value_between(last, true, poly::Value::plus_infty(), true); + d_state->d_assignment.set(d_state->d_variables.back(), sample); + if (!poly::evaluate_constraint(q, d_state->d_assignment, sc)) + { + res.emplace_back(last, true, poly::Value::plus_infty(), true); + } + // clean up assignment + d_state->d_assignment.unset(d_state->d_variables.back()); + + Trace("nl-cov::lazard") << "Shrinking:" << std::endl; + for (const auto& i : res) + { + Trace("nl-cov::lazard") << "-> " << i << std::endl; + } + std::vector combined; + if (res.empty()) + { + // nothing to do if there are no intervals to start with + // return combined to simplify return value optimization + return combined; + } + for (size_t i = 0; i < res.size() - 1; ++i) + { + // Invariant: the intervals do not overlap. Check for our own sanity. + Assert(poly::get_upper(res[i]) <= poly::get_lower(res[i + 1])); + if (poly::get_upper_open(res[i]) && poly::get_lower_open(res[i + 1])) + { + // does not connect, both are open + combined.emplace_back(res[i]); + continue; + } + if (poly::get_upper(res[i]) != poly::get_lower(res[i + 1])) + { + // does not connect, there is some space in between + combined.emplace_back(res[i]); + continue; + } + // combine them into res[i+1], do not copy res[i] over to combined + Trace("nl-cov::lazard") << "Combine " << res[i] << " and " << res[i + 1] + << std::endl; + Assert(poly::get_lower(res[i]) <= poly::get_lower(res[i + 1])); + res[i + 1].set_lower(poly::get_lower(res[i]), poly::get_lower_open(res[i])); + } + + // always use the last one, it is never dropped + combined.emplace_back(res.back()); + Trace("nl-cov::lazard") << "To:" << std::endl; + for (const auto& i : combined) + { + Trace("nl-cov::lazard") << "-> " << i << std::endl; + } + return combined; +} + +} // namespace cvc5::theory::arith::nl::coverings + +#else + +namespace cvc5::theory::arith::nl::coverings { + +/** + * Do a very simple wrapper around the regular poly::infeasible_regions. + * Warn the user about doing this. + * This allows for a graceful fallback (albeit with a warning) if CoCoA is not + * available. + */ +struct LazardEvaluationState +{ + poly::Assignment d_assignment; +}; +LazardEvaluation::LazardEvaluation() + : d_state(std::make_unique()) +{ +} +LazardEvaluation::~LazardEvaluation() {} + +void LazardEvaluation::add(const poly::Variable& var, const poly::Value& val) +{ + d_state->d_assignment.set(var, val); +} + +void LazardEvaluation::addFreeVariable(const poly::Variable& var) {} + +std::vector LazardEvaluation::reducePolynomial( + const poly::Polynomial& p) const +{ + return {p}; +} + +std::vector LazardEvaluation::isolateRealRoots( + const poly::Polynomial& q) const +{ + WarningOnce() + << "nl-cov::LazardEvaluation is disabled because CoCoA is not available. " + "Falling back to regular real root isolation." + << std::endl; + return poly::isolate_real_roots(q, d_state->d_assignment); +} +std::vector LazardEvaluation::infeasibleRegions( + const poly::Polynomial& q, poly::SignCondition sc) const +{ + WarningOnce() + << "nl-cov::LazardEvaluation is disabled because CoCoA is not available. " + "Falling back to regular calculation of infeasible regions." + << std::endl; + return poly::infeasible_regions(q, d_state->d_assignment, sc); +} + +} // namespace cvc5::theory::arith::nl::coverings + +#endif +#endif diff --git a/src/theory/arith/nl/coverings/lazard_evaluation.h b/src/theory/arith/nl/coverings/lazard_evaluation.h new file mode 100644 index 000000000..a03630d89 --- /dev/null +++ b/src/theory/arith/nl/coverings/lazard_evaluation.h @@ -0,0 +1,117 @@ +/****************************************************************************** + * Top contributors (to current version): + * Gereon Kremer + * + * This file is part of the cvc5 project. + * + * Copyright (c) 2009-2021 by the authors listed in the file AUTHORS + * in the top-level source directory and their institutional affiliations. + * All rights reserved. See the file COPYING in the top-level source + * directory for licensing information. + * **************************************************************************** + * + * Implements the CDCAC approach as described in + * https://arxiv.org/pdf/2003.05633.pdf. + */ + +#include "cvc5_private.h" + +#ifndef CVC5__THEORY__ARITH__NL__COVERINGS__LAZARD_EVALUATION_H +#define CVC5__THEORY__ARITH__NL__COVERINGS__LAZARD_EVALUATION_H + +#ifdef CVC5_POLY_IMP + +#include + +#include + +namespace cvc5::theory::arith::nl::coverings { + +struct LazardEvaluationState; +/** + * This class does the heavy lifting for the modified lifting procedure that is + * required for Lazard's projection. + * + * Let p \in Q[x_0, ..., x_n] a multivariate polynomial whose roots we want to + * isolate over the partial sample point A = [x_0 = a_0, ... x_{n-1} = a_{n-1}] + * where a_0, ... a_{n-1} are real algebraic numbers and x_n is the last free + * variable. + * + * The modified lifting procedure conceptually works as follows: + * + * for (x = a) in A: + * while p[x // a] = 0: + * p = p / (x - a) + * p = p[x // a] + * return roots(p) + * + * As the assignment contains real algebraic numbers, though, we can not do any + * of the computations directly, as our polynomials only support coefficients + * from Z or Q, but not extensions (in the algebraic sense) thereof. + * + * Our approach is as follows: + * Let pk be the minimal polynomial for a_k. + * Instead of substituting p[x_k // a_k] we (canonically) embed p into the + * quotient ring Q[x_k]/ and recursively build a tower of such quotient + * rings that is isomorphic to nesting the corresponding field extensions + * Q(a_1)(a_2)... When we have done that, we obtain p that is reduced with + * respect to all minimal polynomials, but may still contain x_0,... x_{n-1}. To + * get rid of these, we compute a Gröbner basis of p and the minimal polynomials + * (using a suitable elimination order) and extract the polynomial in x_n. This + * polynomial has all roots (and possibly some more) that we are looking for. + * Instead of a Gröbner basis, we can also compute the iterated resultant as + * follows: Res(Res(p, p_{n-1}, x_{n-1}), p_{n-2}, x_{n-2})... + * + * Consider + * http://sunsite.informatik.rwth-aachen.de/Publications/AIB/2020/2020-04.pdf + * Section 2.5.1 for a full discussion. + * + * !!! WARNING !!! + * If CoCoALib is not available, this class will simply fall back to + * poly::infeasible_regions and issue a warning about this. + */ +class LazardEvaluation +{ + public: + LazardEvaluation(); + ~LazardEvaluation(); + + /** + * Add the next assigned variable x_k = a_k to this construction. + */ + void add(const poly::Variable& var, const poly::Value& val); + /** + * Finish by adding the free variable x_n. + */ + void addFreeVariable(const poly::Variable& var); + /** + * Reduce the polynomial q. Compared to the above description, there may + * actually be multiple polynomials in the Gröbner basis and instead of + * loosing this knowledge and returning their product, we return them as a + * vector. + */ + std::vector reducePolynomial( + const poly::Polynomial& q) const; + + /** + * Isolates the real roots of the given polynomials. + */ + std::vector isolateRealRoots(const poly::Polynomial& q) const; + + /** + * Compute the infeasible regions of q under the given sign condition. + * Uses reducePolynomial and then performs real root isolation on the + * resulting polynomials to obtain the intervals. Mimics + * poly::infeasible_regions, but uses Lazard's evaluation. + */ + std::vector infeasibleRegions(const poly::Polynomial& q, + poly::SignCondition sc) const; + + private: + std::unique_ptr d_state; +}; + +} // namespace cvc5::theory::arith::nl::coverings + +#endif +#endif diff --git a/src/theory/arith/nl/coverings/projections.cpp b/src/theory/arith/nl/coverings/projections.cpp new file mode 100644 index 000000000..4340f99b4 --- /dev/null +++ b/src/theory/arith/nl/coverings/projections.cpp @@ -0,0 +1,110 @@ +/****************************************************************************** + * Top contributors (to current version): + * Gereon Kremer + * + * This file is part of the cvc5 project. + * + * Copyright (c) 2009-2021 by the authors listed in the file AUTHORS + * in the top-level source directory and their institutional affiliations. + * All rights reserved. See the file COPYING in the top-level source + * directory for licensing information. + * **************************************************************************** + * + * Implements utilities for coverings projection operators. + */ + +#include "theory/arith/nl/coverings/projections.h" + +#ifdef CVC5_POLY_IMP + +#include "base/check.h" + +namespace cvc5 { +namespace theory { +namespace arith { +namespace nl { +namespace coverings { + +using namespace poly; + +void PolyVector::add(const poly::Polynomial& poly, bool assertMain) +{ + for (const auto& p : poly::square_free_factors(poly)) + { + if (poly::is_constant(p)) continue; + if (assertMain) + { + Assert(main_variable(poly) == main_variable(p)); + } + std::vector::emplace_back(p); + } +} + +void PolyVector::reduce() +{ + std::sort(begin(), end()); + erase(std::unique(begin(), end()), end()); +} + +void PolyVector::makeFinestSquareFreeBasis() +{ + for (std::size_t i = 0, n = size(); i < n; ++i) + { + for (std::size_t j = i + 1; j < n; ++j) + { + Polynomial g = gcd((*this)[i], (*this)[j]); + if (!is_constant(g)) + { + (*this)[i] = div((*this)[i], g); + (*this)[j] = div((*this)[j], g); + add(g); + } + } + } + auto it = std::remove_if( + begin(), end(), [](const Polynomial& p) { return is_constant(p); }); + erase(it, end()); + reduce(); +} +void PolyVector::pushDownPolys(PolyVector& down, poly::Variable var) +{ + auto it = + std::remove_if(begin(), end(), [&down, &var](const poly::Polynomial& p) { + if (main_variable(p) == var) return false; + down.add(p); + return true; + }); + erase(it, end()); +} + +PolyVector projectionMcCallum(const std::vector& polys) +{ + PolyVector res; + + for (const auto& p : polys) + { + for (const auto& coeff : coefficients(p)) + { + res.add(coeff); + } + res.add(discriminant(p)); + } + for (std::size_t i = 0, n = polys.size(); i < n; ++i) + { + for (std::size_t j = i + 1; j < n; ++j) + { + res.add(resultant(polys[i], polys[j])); + } + } + + res.reduce(); + return res; +} + +} // namespace coverings +} // namespace nl +} // namespace arith +} // namespace theory +} // namespace cvc5 + +#endif diff --git a/src/theory/arith/nl/coverings/projections.h b/src/theory/arith/nl/coverings/projections.h new file mode 100644 index 000000000..7df36a607 --- /dev/null +++ b/src/theory/arith/nl/coverings/projections.h @@ -0,0 +1,82 @@ +/****************************************************************************** + * Top contributors (to current version): + * Gereon Kremer + * + * This file is part of the cvc5 project. + * + * Copyright (c) 2009-2021 by the authors listed in the file AUTHORS + * in the top-level source directory and their institutional affiliations. + * All rights reserved. See the file COPYING in the top-level source + * directory for licensing information. + * **************************************************************************** + * + * Implements utilities for coverings projection operators. + */ + +#include "cvc5_private.h" + +#ifndef CVC5__THEORY__ARITH__NL__COVERINGS_PROJECTIONS_H +#define CVC5__THEORY__ARITH__NL__COVERINGS_PROJECTIONS_H + +#ifdef CVC5_USE_POLY + +#include + +#include + +namespace cvc5 { +namespace theory { +namespace arith { +namespace nl { +namespace coverings { + +/** + * A simple wrapper around std::vector that ensures that all + * polynomials are properly factorized and pruned when added to the list. + */ +class PolyVector : public std::vector +{ + private: + /** Disable all emplace() */ + void emplace() {} + /** Disable all emplace_back() */ + void emplace_back() {} + /** Disable all insert() */ + void insert() {} + /** Disable all push_back() */ + void push_back() {} + + public: + PolyVector() {} + /** Construct from a set of polynomials */ + PolyVector(std::initializer_list i) + { + for (const auto& p : i) add(p); + } + /** + * Adds a polynomial to the list of projection polynomials. + * Before adding, it factorizes the polynomials and removed constant factors. + */ + void add(const poly::Polynomial& poly, bool assertMain = false); + /** Sort and remove duplicates from the list of polynomials. */ + void reduce(); + /** Make this list of polynomials a finest square-free basis. */ + void makeFinestSquareFreeBasis(); + /** Push polynomials with a lower main variable to another PolyVector. */ + void pushDownPolys(PolyVector& down, poly::Variable var); +}; + +/** + * Computes McCallum's projection operator. + */ +PolyVector projectionMcCallum(const std::vector& polys); + +} // namespace coverings +} // namespace nl +} // namespace arith +} // namespace theory +} // namespace cvc5 + +#endif + +#endif diff --git a/src/theory/arith/nl/coverings/proof_checker.cpp b/src/theory/arith/nl/coverings/proof_checker.cpp new file mode 100644 index 000000000..d4f8c6f50 --- /dev/null +++ b/src/theory/arith/nl/coverings/proof_checker.cpp @@ -0,0 +1,62 @@ +/****************************************************************************** + * Top contributors (to current version): + * Gereon Kremer, Aina Niemetz + * + * This file is part of the cvc5 project. + * + * Copyright (c) 2009-2021 by the authors listed in the file AUTHORS + * in the top-level source directory and their institutional affiliations. + * All rights reserved. See the file COPYING in the top-level source + * directory for licensing information. + * **************************************************************************** + * + * Implementation of CAD proof checker. + */ + +#include "theory/arith/nl/coverings/proof_checker.h" + +#include "expr/sequence.h" +#include "theory/rewriter.h" + +using namespace cvc5::kind; + +namespace cvc5 { +namespace theory { +namespace arith { +namespace nl { +namespace coverings { + +void CoveringsProofRuleChecker::registerTo(ProofChecker* pc) +{ + // trusted rules + pc->registerTrustedChecker(PfRule::ARITH_NL_COVERING_DIRECT, this, 2); + pc->registerTrustedChecker(PfRule::ARITH_NL_COVERING_RECURSIVE, this, 2); +} + +Node CoveringsProofRuleChecker::checkInternal(PfRule id, + const std::vector& children, + const std::vector& args) +{ + Trace("nl-cov-checker") << "Checking " << id << std::endl; + for (const auto& c : children) + { + Trace("nl-cov-checker") << "\t" << c << std::endl; + } + if (id == PfRule::ARITH_NL_COVERING_DIRECT) + { + Assert(args.size() == 1); + return args[0]; + } + if (id == PfRule::ARITH_NL_COVERING_RECURSIVE) + { + Assert(args.size() == 1); + return args[0]; + } + return Node::null(); +} + +} // namespace coverings +} // namespace nl +} // namespace arith +} // namespace theory +} // namespace cvc5 diff --git a/src/theory/arith/nl/coverings/proof_checker.h b/src/theory/arith/nl/coverings/proof_checker.h new file mode 100644 index 000000000..cc33787f7 --- /dev/null +++ b/src/theory/arith/nl/coverings/proof_checker.h @@ -0,0 +1,59 @@ +/****************************************************************************** + * Top contributors (to current version): + * Gereon Kremer + * + * This file is part of the cvc5 project. + * + * Copyright (c) 2009-2021 by the authors listed in the file AUTHORS + * in the top-level source directory and their institutional affiliations. + * All rights reserved. See the file COPYING in the top-level source + * directory for licensing information. + * **************************************************************************** + * + * Coverings proof checker utility. + */ + +#include "cvc5_private.h" + +#ifndef CVC5__THEORY__ARITH__NL__COVERINGS__PROOF_CHECKER_H +#define CVC5__THEORY__ARITH__NL__COVERINGS__PROOF_CHECKER_H + +#include "expr/node.h" +#include "proof/proof_checker.h" + +namespace cvc5 { +namespace theory { +namespace arith { +namespace nl { +namespace coverings { + +/** + * A checker for coverings proofs + * + * This proof checker takes care of the two coverings proof rules ARITH_NL_COVERING_DIRECT + * and ARITH_NL_COVERING_RECURSIVE. It does not do any actual proof checking yet, but + * considers them to be trusted rules. + */ +class CoveringsProofRuleChecker : public ProofRuleChecker +{ + public: + CoveringsProofRuleChecker() {} + ~CoveringsProofRuleChecker() {} + + /** Register all rules owned by this rule checker in pc. */ + void registerTo(ProofChecker* pc) override; + + protected: + /** Return the conclusion of the given proof step, or null if it is invalid */ + Node checkInternal(PfRule id, + const std::vector& children, + const std::vector& args) override; +}; + +} // namespace coverings +} // namespace nl +} // namespace arith +} // namespace theory +} // namespace cvc5 + +#endif /* CVC5__THEORY__STRINGS__PROOF_CHECKER_H */ diff --git a/src/theory/arith/nl/coverings/proof_generator.cpp b/src/theory/arith/nl/coverings/proof_generator.cpp new file mode 100644 index 000000000..b91af55a8 --- /dev/null +++ b/src/theory/arith/nl/coverings/proof_generator.cpp @@ -0,0 +1,247 @@ +/****************************************************************************** + * Top contributors (to current version): + * Gereon Kremer + * + * This file is part of the cvc5 project. + * + * Copyright (c) 2009-2021 by the authors listed in the file AUTHORS + * in the top-level source directory and their institutional affiliations. + * All rights reserved. See the file COPYING in the top-level source + * directory for licensing information. + * **************************************************************************** + * + * Implementation of coverings proof generator. + */ + +#include "theory/arith/nl/coverings/proof_generator.h" + +#ifdef CVC5_POLY_IMP + +#include "proof/lazy_tree_proof_generator.h" +#include "theory/arith/nl/poly_conversion.h" +#include "util/indexed_root_predicate.h" + +using namespace cvc5::kind; + +namespace cvc5 { +namespace theory { +namespace arith { +namespace nl { +namespace coverings { + +namespace { +/** + * Retrieves the root indices of the sign-invariant region of v. + * + * We assume that roots holds a sorted list of roots from one polynomial. + * If v is equal to one of these roots, we return (id,id) where id is the index + * of this root within roots. Otherwise, we return the id of the largest root + * below v and the id of the smallest root above v. To make sure a smaller root + * and a larger root always exist, we implicitly extend the roots by -infty and + * infty. + * + * ATTENTION: if we return id, the corresponding root is: + * - id = 0: -infty + * - 0 < id <= roots.size(): roots[id-1] + * - id = roots.size() + 1: infty + */ +std::pair getRootIDs( + const std::vector& roots, const poly::Value& v) +{ + for (std::size_t i = 0; i < roots.size(); ++i) + { + if (roots[i] == v) + { + return {i + 1, i + 1}; + } + if (roots[i] > v) + { + return {i, i + 1}; + } + } + return {roots.size(), roots.size() + 1}; +} + +/** + * Constructs an IndexedRootExpression: + * var ~rel~ root_k(poly) + * where root_k(poly) is "the k'th root of the polynomial". + * + * @param var The variable that is bounded + * @param rel The relation for this constraint + * @param zero A node representing Rational(0) + * @param k The index of the root (starting with 1) + * @param poly The polynomial whose root shall be considered + * @param vm A variable mapper from cvc5 to libpoly variables + */ +Node mkIRP(const Node& var, + Kind rel, + const Node& zero, + std::size_t k, + const poly::Polynomial& poly, + VariableMapper& vm) +{ + auto* nm = NodeManager::currentNM(); + auto op = nm->mkConst(IndexedRootPredicate(k)); + return nm->mkNode(Kind::INDEXED_ROOT_PREDICATE, + op, + nm->mkNode(rel, var, zero), + as_cvc_polynomial(poly, vm)); +} + +} // namespace + +CoveringsProofGenerator::CoveringsProofGenerator(context::Context* ctx, + ProofNodeManager* pnm) + : d_pnm(pnm), d_proofs(pnm, ctx), d_current(nullptr) +{ + d_false = NodeManager::currentNM()->mkConst(false); + d_zero = NodeManager::currentNM()->mkConst(CONST_RATIONAL, Rational(0)); +} + +void CoveringsProofGenerator::startNewProof() +{ + d_current = d_proofs.allocateProof(); +} +void CoveringsProofGenerator::startRecursive() { d_current->openChild(); } +void CoveringsProofGenerator::endRecursive(size_t intervalId) +{ + d_current->setCurrent( + intervalId, PfRule::ARITH_NL_COVERING_RECURSIVE, {}, {d_false}, d_false); + d_current->closeChild(); +} +void CoveringsProofGenerator::startScope() +{ + d_current->openChild(); + d_current->getCurrent().d_rule = PfRule::SCOPE; +} +void CoveringsProofGenerator::endScope(const std::vector& args) +{ + d_current->setCurrent(0, PfRule::SCOPE, {}, args, d_false); + d_current->closeChild(); +} + +ProofGenerator* CoveringsProofGenerator::getProofGenerator() const +{ + return d_current; +} + +void CoveringsProofGenerator::addDirect(Node var, + VariableMapper& vm, + const poly::Polynomial& poly, + const poly::Assignment& a, + poly::SignCondition& sc, + const poly::Interval& interval, + Node constraint, + size_t intervalId) +{ + if (is_minus_infinity(get_lower(interval)) + && is_plus_infinity(get_upper(interval))) + { + // "Full conflict", constraint excludes (-inf,inf) + d_current->openChild(); + d_current->setCurrent(intervalId, + PfRule::ARITH_NL_COVERING_DIRECT, + {constraint}, + {d_false}, + d_false); + d_current->closeChild(); + return; + } + std::vector res; + auto roots = poly::isolate_real_roots(poly, a); + if (get_lower(interval) == get_upper(interval)) + { + // Excludes a single point only + auto ids = getRootIDs(roots, get_lower(interval)); + Assert(ids.first == ids.second); + res.emplace_back(mkIRP(var, Kind::EQUAL, d_zero, ids.first, poly, vm)); + } + else + { + // Excludes an open interval + if (!is_minus_infinity(get_lower(interval))) + { + // Interval has lower bound that is not -inf + auto ids = getRootIDs(roots, get_lower(interval)); + Assert(ids.first == ids.second); + Kind rel = poly::get_lower_open(interval) ? Kind::GT : Kind::GEQ; + res.emplace_back(mkIRP(var, rel, d_zero, ids.first, poly, vm)); + } + if (!is_plus_infinity(get_upper(interval))) + { + // Interval has upper bound that is not inf + auto ids = getRootIDs(roots, get_upper(interval)); + Assert(ids.first == ids.second); + Kind rel = poly::get_upper_open(interval) ? Kind::LT : Kind::LEQ; + res.emplace_back(mkIRP(var, rel, d_zero, ids.first, poly, vm)); + } + } + // Add to proof manager + startScope(); + d_current->openChild(); + d_current->setCurrent(intervalId, + PfRule::ARITH_NL_COVERING_DIRECT, + {constraint}, + {d_false}, + d_false); + d_current->closeChild(); + endScope(res); +} + +std::vector CoveringsProofGenerator::constructCell(Node var, + const CACInterval& i, + const poly::Assignment& a, + const poly::Value& s, + VariableMapper& vm) +{ + if (is_minus_infinity(get_lower(i.d_interval)) + && is_plus_infinity(get_upper(i.d_interval))) + { + // "Full conflict", constraint excludes (-inf,inf) + return {}; + } + + std::vector res; + + // Just use bounds for all polynomials + for (const auto& poly : i.d_mainPolys) + { + auto roots = poly::isolate_real_roots(poly, a); + auto ids = getRootIDs(roots, s); + if (ids.first == ids.second) + { + // Excludes a single point only + res.emplace_back(mkIRP(var, Kind::EQUAL, d_zero, ids.first, poly, vm)); + } + else + { + // Excludes an open interval + if (ids.first > 0) + { + // Interval has lower bound that is not -inf + res.emplace_back(mkIRP(var, Kind::GT, d_zero, ids.first, poly, vm)); + } + if (ids.second <= roots.size()) + { + // Interval has upper bound that is not inf + res.emplace_back(mkIRP(var, Kind::LT, d_zero, ids.second, poly, vm)); + } + } + } + + return res; +} + +std::ostream& operator<<(std::ostream& os, const CoveringsProofGenerator& proof) +{ + return os << *proof.d_current; +} + +} // namespace coverings +} // namespace nl +} // namespace arith +} // namespace theory +} // namespace cvc5 + +#endif diff --git a/src/theory/arith/nl/coverings/proof_generator.h b/src/theory/arith/nl/coverings/proof_generator.h new file mode 100644 index 000000000..46d3843ed --- /dev/null +++ b/src/theory/arith/nl/coverings/proof_generator.h @@ -0,0 +1,159 @@ +/****************************************************************************** + * Top contributors (to current version): + * Gereon Kremer + * + * This file is part of the cvc5 project. + * + * Copyright (c) 2009-2021 by the authors listed in the file AUTHORS + * in the top-level source directory and their institutional affiliations. + * All rights reserved. See the file COPYING in the top-level source + * directory for licensing information. + * **************************************************************************** + * + * Implements the proof generator for coverings. + */ + +#include "cvc5_private.h" + +#ifndef CVC5__THEORY__ARITH__NL__COVERINGS__PROOF_GENERATOR_H +#define CVC5__THEORY__ARITH__NL__COVERINGS__PROOF_GENERATOR_H + +#ifdef CVC5_POLY_IMP + +#include + +#include + +#include "expr/node.h" +#include "proof/lazy_tree_proof_generator.h" +#include "proof/proof_set.h" +#include "theory/arith/nl/coverings/cdcac_utils.h" + +namespace cvc5 { + +class ProofGenerator; + +namespace theory { +namespace arith { +namespace nl { + +struct VariableMapper; + +namespace coverings { + +/** + * This class manages the proof creation during a run of the coverings solver. + * + * Though it implements the ProofGenerator interface getProofFor(Node), it only + * gives a proof for a single node. + * + * It uses a LazyTreeProofGenerator internally to manage the tree-based proof + * construction. + */ +class CoveringsProofGenerator +{ + public: + friend std::ostream& operator<<(std::ostream& os, + const CoveringsProofGenerator& proof); + CoveringsProofGenerator(context::Context* ctx, ProofNodeManager* pnm); + + /** Start a new proof in this proof generator */ + void startNewProof(); + /** Start a new recursive call */ + void startRecursive(); + /** Finish the current recursive call */ + void endRecursive(size_t intervalId); + /** Start a new scope, corresponding to a guess in CDCAC */ + void startScope(); + /** Finish a scope and add the (generalized) sample that was refuted */ + void endScope(const std::vector& args); + /** Return the current proof generator */ + ProofGenerator* getProofGenerator() const; + + /** + * Calls LazyTreeProofGenerator::pruneChildren(f), but decorates the + * predicate such that f only accepts the index. + * @param f A Callable bool(std::size_t) + */ + template + void pruneChildren(F&& f) + { + d_current->pruneChildren([&f](const detail::TreeProofNode& tpn) { + // The direct children of recursive rules are scopes, but the ids are + // attached to their children + if (tpn.d_rule == PfRule::SCOPE && tpn.d_children.size() == 1) + { + return f(tpn.d_children[0].d_objectId); + } + return f(tpn.d_objectId); + }); + } + + /** + * Add a direct interval conflict as generated in getUnsatIntervals(). + * Its meaning is: + * over the partial assignment a, var is not in interval because p~sc~0 + * and the origin of this is constraint. + * + * @param var The variable for which the interval is excluded + * @param vm A variable mapper between cvc5 and libpoly variables + * @param p The polynomial of the constraint + * @param a The current partial assignment + * @param sc The sign condition of the constraint + * @param interval The concrete interval that is excluded + * @param constraint The assumption that yields p and sc + */ + void addDirect(Node var, + VariableMapper& vm, + const poly::Polynomial& p, + const poly::Assignment& a, + poly::SignCondition& sc, + const poly::Interval& interval, + Node constraint, + size_t intervalId); + + /** + * Constructs the (generalized) interval that is to be excluded from a + * CACInterval. It should be called after the recursive call to construct the + * generalized sample necessary for endScope(). + * + * @param var The variable for which the interval is excluded + * @param i The concrete interval that is excluded + * @param a The current partial assignment + * @param s The sample point that is refuted for var + * @param vm A variable mapper between cvc5 and libpoly variables + */ + std::vector constructCell(Node var, + const CACInterval& i, + const poly::Assignment& a, + const poly::Value& s, + VariableMapper& vm); + + private: + /** The proof node manager used for the proofs */ + ProofNodeManager* d_pnm; + /** The list of generated proofs */ + CDProofSet d_proofs; + /** The current proof */ + LazyTreeProofGenerator* d_current; + + /** Constant false */ + Node d_false; + /** Constant zero */ + Node d_zero; +}; + +/** + * Prints the underlying LazyTreeProofGenerator. Please check the documentation + * of std::ostream& operator<<(std::ostream&, const LazyTreeProofGenerator&) + */ +std::ostream& operator<<(std::ostream& os, const CoveringsProofGenerator& proof); + +} // namespace coverings +} // namespace nl +} // namespace arith +} // namespace theory +} // namespace cvc5 + +#endif +#endif diff --git a/src/theory/arith/nl/coverings/variable_ordering.cpp b/src/theory/arith/nl/coverings/variable_ordering.cpp new file mode 100644 index 000000000..58a42d13d --- /dev/null +++ b/src/theory/arith/nl/coverings/variable_ordering.cpp @@ -0,0 +1,136 @@ +/****************************************************************************** + * Top contributors (to current version): + * Gereon Kremer + * + * This file is part of the cvc5 project. + * + * Copyright (c) 2009-2021 by the authors listed in the file AUTHORS + * in the top-level source directory and their institutional affiliations. + * All rights reserved. See the file COPYING in the top-level source + * directory for licensing information. + * **************************************************************************** + * + * Implements variable orderings tailored to coverings. + */ + +#include "theory/arith/nl/coverings/variable_ordering.h" + +#ifdef CVC5_POLY_IMP + +#include "util/poly_util.h" + +namespace cvc5 { +namespace theory { +namespace arith { +namespace nl { +namespace coverings { + +std::vector collectInformation( + const Constraints::ConstraintVector& polys, bool with_totals) +{ + poly::VariableCollector vc; + for (const auto& c : polys) + { + vc(std::get<0>(c)); + } + std::vector res; + for (const auto& v : vc.get_variables()) + { + res.emplace_back(); + res.back().var = v; + for (const auto& c : polys) + { + poly_utils::getVariableInformation(res.back(), std::get<0>(c)); + } + } + if (with_totals) + { + res.emplace_back(); + for (const auto& c : polys) + { + poly_utils::getVariableInformation(res.back(), std::get<0>(c)); + } + } + return res; +} + +std::vector getVariables( + const std::vector& vi) +{ + std::vector res; + for (const auto& v : vi) + { + res.emplace_back(v.var); + } + return res; +} + +std::vector sortByid(const Constraints::ConstraintVector& polys) +{ + auto vi = collectInformation(polys); + std::sort( + vi.begin(), + vi.end(), + [](const poly_utils::VariableInformation& a, + const poly_utils::VariableInformation& b) { return a.var < b.var; }); + return getVariables(vi); +}; + +std::vector sortBrown( + const Constraints::ConstraintVector& polys) +{ + auto vi = collectInformation(polys); + std::sort(vi.begin(), + vi.end(), + [](const poly_utils::VariableInformation& a, + const poly_utils::VariableInformation& b) { + if (a.max_degree != b.max_degree) + return a.max_degree > b.max_degree; + if (a.max_terms_tdegree != b.max_terms_tdegree) + return a.max_terms_tdegree > b.max_terms_tdegree; + return a.num_terms > b.num_terms; + }); + return getVariables(vi); +}; + +std::vector sortTriangular( + const Constraints::ConstraintVector& polys) +{ + auto vi = collectInformation(polys); + std::sort(vi.begin(), + vi.end(), + [](const poly_utils::VariableInformation& a, + const poly_utils::VariableInformation& b) { + if (a.max_degree != b.max_degree) + return a.max_degree > b.max_degree; + if (a.max_lc_degree != b.max_lc_degree) + return a.max_lc_degree > b.max_lc_degree; + return a.sum_poly_degree > b.sum_poly_degree; + }); + return getVariables(vi); +}; + +VariableOrdering::VariableOrdering() {} +VariableOrdering::~VariableOrdering() {} + +std::vector VariableOrdering::operator()( + const Constraints::ConstraintVector& polys, + VariableOrderingStrategy vos) const +{ + switch (vos) + { + case VariableOrderingStrategy::BYID: return sortByid(polys); + case VariableOrderingStrategy::BROWN: return sortBrown(polys); + case VariableOrderingStrategy::TRIANGULAR: return sortTriangular(polys); + default: Assert(false) << "Unsupported variable ordering."; + } + return {}; +} + +} // namespace coverings +} // namespace nl +} // namespace arith +} // namespace theory +} // namespace cvc5 + +#endif diff --git a/src/theory/arith/nl/coverings/variable_ordering.h b/src/theory/arith/nl/coverings/variable_ordering.h new file mode 100644 index 000000000..e3f287f06 --- /dev/null +++ b/src/theory/arith/nl/coverings/variable_ordering.h @@ -0,0 +1,71 @@ +/****************************************************************************** + * Top contributors (to current version): + * Gereon Kremer + * + * This file is part of the cvc5 project. + * + * Copyright (c) 2009-2021 by the authors listed in the file AUTHORS + * in the top-level source directory and their institutional affiliations. + * All rights reserved. See the file COPYING in the top-level source + * directory for licensing information. + * **************************************************************************** + * + * Implements variable orderings tailored to coverings. + */ + +#include "cvc5_private.h" + +#ifndef CVC5__THEORY__ARITH__NL__COVERINGS__VARIABLE_ORDERING_H +#define CVC5__THEORY__ARITH__NL__COVERINGS__VARIABLE_ORDERING_H + +#ifdef CVC5_POLY_IMP + +#include + +#include "theory/arith/nl/coverings/constraints.h" +#include "util/poly_util.h" + +namespace cvc5 { +namespace theory { +namespace arith { +namespace nl { +namespace coverings { + +/** Variable orderings for real variables in the context of coverings. */ +enum class VariableOrderingStrategy +{ + /** Dummy ordering by variable ID. */ + BYID, + /** Triangular as of DOI:10.1145/2755996.2756678 */ + TRIANGULAR, + /** Brown as of DOI:10.1145/2755996.2756678 */ + BROWN +}; + +class VariableOrdering +{ + public: + VariableOrdering(); + ~VariableOrdering(); + std::vector operator()( + const Constraints::ConstraintVector& polys, + VariableOrderingStrategy vos) const; +}; + +/** + * Retrieves variable information for all variables with the given polynomials. + * If with_totals is set, the last element of the vector contains totals as + * computed by get_variable_information if no variable is specified. + */ +std::vector collectInformation( + const Constraints::ConstraintVector& polys, bool with_totals = false); + +} // namespace coverings +} // namespace nl +} // namespace arith +} // namespace theory +} // namespace cvc5 + +#endif + +#endif diff --git a/src/theory/arith/nl/coverings_solver.cpp b/src/theory/arith/nl/coverings_solver.cpp new file mode 100644 index 000000000..2ec366d2a --- /dev/null +++ b/src/theory/arith/nl/coverings_solver.cpp @@ -0,0 +1,255 @@ +/****************************************************************************** + * Top contributors (to current version): + * Gereon Kremer, Andrew Reynolds, Andres Noetzli + * + * This file is part of the cvc5 project. + * + * Copyright (c) 2009-2021 by the authors listed in the file AUTHORS + * in the top-level source directory and their institutional affiliations. + * All rights reserved. See the file COPYING in the top-level source + * directory for licensing information. + * **************************************************************************** + * + * Implementation of new non-linear solver. + */ + +#include "theory/arith/nl/coverings_solver.h" + +#include "expr/skolem_manager.h" +#include "options/arith_options.h" +#include "smt/env.h" +#include "theory/arith/inference_manager.h" +#include "theory/arith/nl/coverings/cdcac.h" +#include "theory/arith/nl/nl_model.h" +#include "theory/arith/nl/poly_conversion.h" +#include "theory/inference_id.h" +#include "theory/theory.h" + +namespace cvc5 { +namespace theory { +namespace arith { +namespace nl { + +CoveringsSolver::CoveringsSolver(Env& env, InferenceManager& im, NlModel& model) + : + EnvObj(env), +#ifdef CVC5_POLY_IMP + d_CAC(env), +#endif + d_foundSatisfiability(false), + d_im(im), + d_model(model), + d_eqsubs(env) +{ + NodeManager* nm = NodeManager::currentNM(); + SkolemManager* sm = nm->getSkolemManager(); + d_ranVariable = sm->mkDummySkolem("__z", nm->realType(), ""); +#ifdef CVC5_POLY_IMP + if (env.isTheoryProofProducing()) + { + ProofChecker* pc = env.getProofNodeManager()->getChecker(); + // add checkers + d_proofChecker.registerTo(pc); + } +#endif +} + +CoveringsSolver::~CoveringsSolver() {} + +void CoveringsSolver::initLastCall(const std::vector& assertions) +{ +#ifdef CVC5_POLY_IMP + if (Trace.isOn("nl-cov")) + { + Trace("nl-cov") << "CoveringsSolver::initLastCall" << std::endl; + Trace("nl-cov") << "* Assertions: " << std::endl; + for (const Node& a : assertions) + { + Trace("nl-cov") << " " << a << std::endl; + } + } + if (options().arith.nlCovVarElim) + { + d_eqsubs.reset(); + std::vector processed = d_eqsubs.eliminateEqualities(assertions); + if (d_eqsubs.hasConflict()) + { + Node lem = NodeManager::currentNM()->mkAnd(d_eqsubs.getConflict()).negate(); + d_im.addPendingLemma(lem, InferenceId::ARITH_NL_COVERING_CONFLICT, nullptr); + Trace("nl-cov") << "Found conflict: " << lem << std::endl; + return; + } + if (Trace.isOn("nl-cov")) + { + Trace("nl-cov") << "After simplifications" << std::endl; + Trace("nl-cov") << "* Assertions: " << std::endl; + for (const Node& a : processed) + { + Trace("nl-cov") << " " << a << std::endl; + } + } + d_CAC.reset(); + for (const Node& a : processed) + { + Assert(!a.isConst()); + d_CAC.getConstraints().addConstraint(a); + } + } + else + { + d_CAC.reset(); + for (const Node& a : assertions) + { + Assert(!a.isConst()); + d_CAC.getConstraints().addConstraint(a); + } + } + d_CAC.computeVariableOrdering(); + d_CAC.retrieveInitialAssignment(d_model, d_ranVariable); +#else + warning() << "Tried to use CoveringsSolver but libpoly is not available. Compile " + "with --poly." + << std::endl; +#endif +} + +void CoveringsSolver::checkFull() +{ +#ifdef CVC5_POLY_IMP + if (d_CAC.getConstraints().getConstraints().empty()) { + d_foundSatisfiability = true; + Trace("nl-cov") << "No constraints. Return." << std::endl; + return; + } + d_CAC.startNewProof(); + auto covering = d_CAC.getUnsatCover(); + if (covering.empty()) + { + d_foundSatisfiability = true; + Trace("nl-cov") << "SAT: " << d_CAC.getModel() << std::endl; + } + else + { + d_foundSatisfiability = false; + auto mis = collectConstraints(covering); + Trace("nl-cov") << "Collected MIS: " << mis << std::endl; + Assert(!mis.empty()) << "Infeasible subset can not be empty"; + Trace("nl-cov") << "UNSAT with MIS: " << mis << std::endl; + d_eqsubs.postprocessConflict(mis); + Trace("nl-cov") << "After postprocessing: " << mis << std::endl; + Node lem = NodeManager::currentNM()->mkAnd(mis).notNode(); + ProofGenerator* proof = d_CAC.closeProof(mis); + d_im.addPendingLemma(lem, InferenceId::ARITH_NL_COVERING_CONFLICT, proof); + } +#else + warning() << "Tried to use CoveringsSolver but libpoly is not available. Compile " + "with --poly." + << std::endl; +#endif +} + +void CoveringsSolver::checkPartial() +{ +#ifdef CVC5_POLY_IMP + if (d_CAC.getConstraints().getConstraints().empty()) { + Trace("nl-cov") << "No constraints. Return." << std::endl; + return; + } + auto covering = d_CAC.getUnsatCover(true); + if (covering.empty()) + { + d_foundSatisfiability = true; + Trace("nl-cov") << "SAT: " << d_CAC.getModel() << std::endl; + } + else + { + auto* nm = NodeManager::currentNM(); + Node first_var = + d_CAC.getConstraints().varMapper()(d_CAC.getVariableOrdering()[0]); + for (const auto& interval : covering) + { + Node premise; + Assert(!interval.d_origins.empty()); + if (interval.d_origins.size() == 1) + { + premise = interval.d_origins[0]; + } + else + { + premise = nm->mkNode(Kind::AND, interval.d_origins); + } + Node conclusion = + excluding_interval_to_lemma(first_var, interval.d_interval, false); + if (!conclusion.isNull()) + { + Node lemma = nm->mkNode(Kind::IMPLIES, premise, conclusion); + Trace("nl-cov") << "Excluding " << first_var << " -> " + << interval.d_interval << " using " << lemma + << std::endl; + d_im.addPendingLemma(lemma, + InferenceId::ARITH_NL_COVERING_EXCLUDED_INTERVAL); + } + } + } +#else + warning() << "Tried to use CoveringsSolver but libpoly is not available. Compile " + "with --poly." + << std::endl; +#endif +} + +bool CoveringsSolver::constructModelIfAvailable(std::vector& assertions) +{ +#ifdef CVC5_POLY_IMP + if (!d_foundSatisfiability) + { + return false; + } + bool foundNonVariable = false; + for (const auto& v : d_CAC.getVariableOrdering()) + { + Node variable = d_CAC.getConstraints().varMapper()(v); + if (!Theory::isLeafOf(variable, TheoryId::THEORY_ARITH)) + { + Trace("nl-cov") << "Not a variable: " << variable << std::endl; + foundNonVariable = true; + } + Node value = value_to_node(d_CAC.getModel().get(v), variable); + addToModel(variable, value); + } + for (const auto& sub : d_eqsubs.getSubstitutions()) + { + Trace("nl-cov") << "EqSubs: " << sub.first << " -> " << sub.second + << std::endl; + addToModel(sub.first, sub.second); + } + if (foundNonVariable) + { + Trace("nl-cov") + << "Some variable was an extended term, don't clear list of assertions." + << std::endl; + return false; + } + Trace("nl-cov") << "Constructed a full assignment, clear list of assertions." + << std::endl; + assertions.clear(); + return true; +#else + warning() << "Tried to use CoveringsSolver but libpoly is not available. Compile " + "with --poly." + << std::endl; + return false; +#endif +} + +void CoveringsSolver::addToModel(TNode var, TNode value) const +{ + Trace("nl-cov") << "-> " << var << " = " << value << std::endl; + Assert(value.getType().isRealOrInt()); + d_model.addSubstitution(var, value); +} + +} // namespace nl +} // namespace arith +} // namespace theory +} // namespace cvc5 diff --git a/src/theory/arith/nl/coverings_solver.h b/src/theory/arith/nl/coverings_solver.h new file mode 100644 index 000000000..73ba352f5 --- /dev/null +++ b/src/theory/arith/nl/coverings_solver.h @@ -0,0 +1,124 @@ +/****************************************************************************** + * Top contributors (to current version): + * Gereon Kremer + * + * This file is part of the cvc5 project. + * + * Copyright (c) 2009-2021 by the authors listed in the file AUTHORS + * in the top-level source directory and their institutional affiliations. + * All rights reserved. See the file COPYING in the top-level source + * directory for licensing information. + * **************************************************************************** + * + * CAD-based solver based on https://arxiv.org/pdf/2003.05633.pdf. + */ + +#ifndef CVC5__THEORY__ARITH__COVERINGS_SOLVER_H +#define CVC5__THEORY__ARITH__COVERINGS_SOLVER_H + +#include + +#include "context/context.h" +#include "expr/node.h" +#include "smt/env_obj.h" +#include "theory/arith/nl/coverings/cdcac.h" +#include "theory/arith/nl/coverings/proof_checker.h" +#include "theory/arith/nl/equality_substitution.h" + +namespace cvc5 { + +class ProofNodeManager; + +namespace theory { +namespace arith { + +class InferenceManager; + +namespace nl { + +class NlModel; + +/** + * A solver for nonlinear arithmetic that implements the CAD-based method + * described in https://arxiv.org/pdf/2003.05633.pdf. + */ +class CoveringsSolver: protected EnvObj +{ + public: + CoveringsSolver(Env& env, InferenceManager& im, NlModel& model); + ~CoveringsSolver(); + + /** + * This is called at the beginning of last call effort check, where + * assertions are the set of assertions belonging to arithmetic, + * false_asserts is the subset of assertions that are false in the current + * model, and xts is the set of extended function terms that are active in + * the current context. + */ + void initLastCall(const std::vector& assertions); + + /** + * Perform a full check, returning either {} or a single lemma. + * If the result is empty, the input is satisfiable and a model is available + * for construct_model_if_available. Otherwise, the single lemma can be used + * as an infeasible subset. + */ + void checkFull(); + + /** + * Perform a partial check, returning either {} or a list of lemmas. + * If the result is empty, the input is satisfiable and a model is available + * for construct_model_if_available. Otherwise, the lemmas exclude some part + * of the search space. + */ + void checkPartial(); + + /** + * If a model is available (indicated by the last call to check_full() or + * check_partial()) this method puts a satisfying assignment in d_model, + * clears the list of assertions, and returns true. + * Otherwise, this method returns false. + */ + bool constructModelIfAvailable(std::vector& assertions); + + private: + /** + * Add the variable assignment `var = value` to the nonlinear model. + * Depending on `value`, it is either added as substitution or witness. + */ + void addToModel(TNode var, TNode value) const; + + /** + * The variable used to encode real algebraic numbers to nodes. + */ + Node d_ranVariable; + +#ifdef CVC5_POLY_IMP + /** + * The object implementing the actual decision procedure. + */ + coverings::CDCAC d_CAC; + /** The proof checker for coverings proofs */ + coverings::CoveringsProofRuleChecker d_proofChecker; +#endif + /** + * Indicates whether we found satisfiability in the last call to + * checkFullRefine. + */ + bool d_foundSatisfiability; + + /** The inference manager we are pushing conflicts and lemmas to. */ + InferenceManager& d_im; + /** Reference to the non-linear model object */ + NlModel& d_model; + /** Utility to eliminate variables from simple equalities before going into + * the actual coverings solver */ + EqualitySubstitution d_eqsubs; +}; /* class CoveringsSolver */ + +} // namespace nl +} // namespace arith +} // namespace theory +} // namespace cvc5 + +#endif /* CVC5__THEORY__ARITH__COVERINGS_SOLVER_H */ diff --git a/src/theory/arith/nl/nonlinear_extension.cpp b/src/theory/arith/nl/nonlinear_extension.cpp index a26dbf173..43004c21e 100644 --- a/src/theory/arith/nl/nonlinear_extension.cpp +++ b/src/theory/arith/nl/nonlinear_extension.cpp @@ -56,7 +56,7 @@ NonlinearExtension::NonlinearExtension(Env& env, d_monomialSlv(d_env, &d_extState), d_splitZeroSlv(d_env, &d_extState), d_tangentPlaneSlv(d_env, &d_extState), - d_cadSlv(d_env, d_im, d_model), + d_covSlv(d_env, d_im, d_model), d_icpSlv(d_env, d_im), d_iandSlv(env, d_im, state, d_model), d_pow2Slv(env, d_im, state, d_model) @@ -223,9 +223,9 @@ bool NonlinearExtension::checkModel(const std::vector& assertions) return false; } } - if (options().arith.nlCad) + if (options().arith.nlCov) { - d_cadSlv.constructModelIfAvailable(passertions); + d_covSlv.constructModelIfAvailable(passertions); } Trace("nl-ext-cm") << "-----" << std::endl; @@ -442,8 +442,8 @@ void NonlinearExtension::runStrategy(Theory::Effort effort, { case InferStep::BREAK: stop = d_im.hasPendingLemma(); break; case InferStep::FLUSH_WAITING_LEMMAS: d_im.flushWaitingLemmas(); break; - case InferStep::CAD_FULL: d_cadSlv.checkFull(); break; - case InferStep::CAD_INIT: d_cadSlv.initLastCall(assertions); break; + case InferStep::COVERINGS_FULL: d_covSlv.checkFull(); break; + case InferStep::COVERINGS_INIT: d_covSlv.initLastCall(assertions); break; case InferStep::NL_FACTORING: d_factoringSlv.check(assertions, false_asserts); break; diff --git a/src/theory/arith/nl/nonlinear_extension.h b/src/theory/arith/nl/nonlinear_extension.h index 0c94bbc08..0080f8948 100644 --- a/src/theory/arith/nl/nonlinear_extension.h +++ b/src/theory/arith/nl/nonlinear_extension.h @@ -22,7 +22,7 @@ #include "expr/node.h" #include "smt/env_obj.h" -#include "theory/arith/nl/cad_solver.h" +#include "theory/arith/nl/coverings_solver.h" #include "theory/arith/nl/ext/ext_state.h" #include "theory/arith/nl/ext/factoring_check.h" #include "theory/arith/nl/ext/monomial_bounds_check.h" @@ -242,8 +242,8 @@ class NonlinearExtension : EnvObj SplitZeroCheck d_splitZeroSlv; /** Solver for tangent plane lemmas. */ TangentPlaneCheck d_tangentPlaneSlv; - /** The CAD-based solver */ - CadSolver d_cadSlv; + /** The coverings-based solver */ + CoveringsSolver d_covSlv; /** The ICP-based solver */ icp::ICPSolver d_icpSlv; /** The integer and solver diff --git a/src/theory/arith/nl/poly_conversion.cpp b/src/theory/arith/nl/poly_conversion.cpp index 43c3d152d..0327af22c 100644 --- a/src/theory/arith/nl/poly_conversion.cpp +++ b/src/theory/arith/nl/poly_conversion.cpp @@ -567,7 +567,7 @@ Node excluding_interval_to_lemma(const Node& variable, const poly::AlgebraicNumber& alg = as_algebraic_number(lv); if (poly::is_rational(alg)) { - Trace("nl-cad") << "Rational point interval: " << interval << std::endl; + Trace("nl-cov") << "Rational point interval: " << interval << std::endl; return nm->mkNode( Kind::DISTINCT, variable, @@ -575,7 +575,7 @@ Node excluding_interval_to_lemma(const Node& variable, CONST_RATIONAL, poly_utils::toRational(poly::to_rational_approximation(alg)))); } - Trace("nl-cad") << "Algebraic point interval: " << interval << std::endl; + Trace("nl-cov") << "Algebraic point interval: " << interval << std::endl; // p(x) != 0 or x <= lb or ub <= x if (allowNonlinearLemma) { @@ -597,7 +597,7 @@ Node excluding_interval_to_lemma(const Node& variable, } else { - Trace("nl-cad") << "Rational point interval: " << interval << std::endl; + Trace("nl-cov") << "Rational point interval: " << interval << std::endl; return nm->mkNode( Kind::DISTINCT, variable, @@ -606,17 +606,17 @@ Node excluding_interval_to_lemma(const Node& variable, } if (li) { - Trace("nl-cad") << "Only upper bound: " << interval << std::endl; + Trace("nl-cov") << "Only upper bound: " << interval << std::endl; return upper_bound_as_node( variable, uv, poly::get_upper_open(interval), allowNonlinearLemma); } if (ui) { - Trace("nl-cad") << "Only lower bound: " << interval << std::endl; + Trace("nl-cov") << "Only lower bound: " << interval << std::endl; return lower_bound_as_node( variable, lv, poly::get_lower_open(interval), allowNonlinearLemma); } - Trace("nl-cad") << "Proper interval: " << interval << std::endl; + Trace("nl-cov") << "Proper interval: " << interval << std::endl; Node lb = lower_bound_as_node( variable, lv, poly::get_lower_open(interval), allowNonlinearLemma); Node ub = upper_bound_as_node( diff --git a/src/theory/arith/nl/poly_conversion.h b/src/theory/arith/nl/poly_conversion.h index dddde3c0f..f9c82fa1f 100644 --- a/src/theory/arith/nl/poly_conversion.h +++ b/src/theory/arith/nl/poly_conversion.h @@ -68,7 +68,7 @@ poly::UPolynomial as_poly_upolynomial(const cvc5::Node& n, * Once the polynomial has been fully constructed, we can oftentimes ignore the * denominator (except for its sign, which is always positive, though). * This is the case if we are solely interested in the roots of the polynomials - * (like in the context of CAD). If we need the actual polynomial (for example + * (like in the context of coverings). If we need the actual polynomial (for example * in the context of ICP) the second overload provides the denominator in the * third argument. */ diff --git a/src/theory/arith/nl/strategy.cpp b/src/theory/arith/nl/strategy.cpp index a14841f67..8ea46099c 100644 --- a/src/theory/arith/nl/strategy.cpp +++ b/src/theory/arith/nl/strategy.cpp @@ -31,8 +31,8 @@ std::ostream& operator<<(std::ostream& os, InferStep step) { case InferStep::BREAK: return os << "BREAK"; case InferStep::FLUSH_WAITING_LEMMAS: return os << "FLUSH_WAITING_LEMMAS"; - case InferStep::CAD_INIT: return os << "CAD_INIT"; - case InferStep::CAD_FULL: return os << "CAD_FULL"; + case InferStep::COVERINGS_INIT: return os << "COVERINGS_INIT"; + case InferStep::COVERINGS_FULL: return os << "COVERINGS_FULL"; case InferStep::NL_FACTORING: return os << "NL_FACTORING"; case InferStep::IAND_INIT: return os << "IAND_INIT"; case InferStep::IAND_FULL: return os << "IAND_FULL"; @@ -170,10 +170,10 @@ void Strategy::initializeStrategy(const Options& options) } one << InferStep::IAND_FULL << InferStep::BREAK; one << InferStep::POW2_FULL << InferStep::BREAK; - if (options.arith.nlCad) + if (options.arith.nlCov) { - one << InferStep::CAD_INIT << InferStep::BREAK; - one << InferStep::CAD_FULL << InferStep::BREAK; + one << InferStep::COVERINGS_INIT << InferStep::BREAK; + one << InferStep::COVERINGS_FULL << InferStep::BREAK; } d_interleaving.add(one); diff --git a/src/theory/arith/nl/strategy.h b/src/theory/arith/nl/strategy.h index d50108851..a0d257d2a 100644 --- a/src/theory/arith/nl/strategy.h +++ b/src/theory/arith/nl/strategy.h @@ -34,10 +34,10 @@ enum class InferStep /** Flush waiting lemmas to be pending */ FLUSH_WAITING_LEMMAS, - /** Initialize the CAD solver */ - CAD_INIT, - /** A full CAD check */ - CAD_FULL, + /** Initialize the coverings solver */ + COVERINGS_INIT, + /** A full coverings check */ + COVERINGS_FULL, /** Initialize the IAND solver */ IAND_INIT, diff --git a/src/theory/inference_id.cpp b/src/theory/inference_id.cpp index b0d218e81..5968a2ff2 100644 --- a/src/theory/inference_id.cpp +++ b/src/theory/inference_id.cpp @@ -100,9 +100,9 @@ const char* toString(InferenceId i) return "ARITH_NL_POW2_MONOTONE_REFINE"; case InferenceId::ARITH_NL_POW2_TRIVIAL_CASE_REFINE: return "ARITH_NL_POW2_TRIVIAL_CASE_REFINE"; - case InferenceId::ARITH_NL_CAD_CONFLICT: return "ARITH_NL_CAD_CONFLICT"; - case InferenceId::ARITH_NL_CAD_EXCLUDED_INTERVAL: - return "ARITH_NL_CAD_EXCLUDED_INTERVAL"; + case InferenceId::ARITH_NL_COVERING_CONFLICT: return "ARITH_NL_COVERING_CONFLICT"; + case InferenceId::ARITH_NL_COVERING_EXCLUDED_INTERVAL: + return "ARITH_NL_COVERING_EXCLUDED_INTERVAL"; case InferenceId::ARITH_NL_ICP_CONFLICT: return "ARITH_NL_ICP_CONFLICT"; case InferenceId::ARITH_NL_ICP_PROPAGATION: return "ARITH_NL_ICP_PROPAGATION"; diff --git a/src/theory/inference_id.h b/src/theory/inference_id.h index a34b40661..76c330cae 100644 --- a/src/theory/inference_id.h +++ b/src/theory/inference_id.h @@ -158,11 +158,11 @@ enum class InferenceId ARITH_NL_POW2_MONOTONE_REFINE, // trivial refinements (Pow2Solver::checkFullRefine) ARITH_NL_POW2_TRIVIAL_CASE_REFINE, - //-------------------- nonlinear cad solver - // conflict / infeasible subset obtained from cad - ARITH_NL_CAD_CONFLICT, + //-------------------- nonlinear coverings solver + // conflict / infeasible subset obtained from coverings + ARITH_NL_COVERING_CONFLICT, // excludes an interval for a single variable - ARITH_NL_CAD_EXCLUDED_INTERVAL, + ARITH_NL_COVERING_EXCLUDED_INTERVAL, //-------------------- nonlinear icp solver // conflict obtained from icp ARITH_NL_ICP_CONFLICT, diff --git a/test/regress/regress0/nl/issue5726-downpolys.smt2 b/test/regress/regress0/nl/issue5726-downpolys.smt2 index b9b204198..4a663ebe0 100644 --- a/test/regress/regress0/nl/issue5726-downpolys.smt2 +++ b/test/regress/regress0/nl/issue5726-downpolys.smt2 @@ -1,4 +1,4 @@ -; COMMAND-LINE: --nl-ext=none --nl-cad +; COMMAND-LINE: --nl-ext=none --nl-cov ; REQUIRES: poly ; EXPECT: unsat (set-logic QF_NRA) diff --git a/test/regress/regress0/nl/issue5726-sqfactor.smt2 b/test/regress/regress0/nl/issue5726-sqfactor.smt2 index bfe8d31a6..07f1b7f6d 100644 --- a/test/regress/regress0/nl/issue5726-sqfactor.smt2 +++ b/test/regress/regress0/nl/issue5726-sqfactor.smt2 @@ -1,4 +1,4 @@ -; COMMAND-LINE: --nl-ext=none --nl-cad +; COMMAND-LINE: --nl-ext=none --nl-cov ; REQUIRES: poly ; EXPECT: sat (set-logic QF_NRA) diff --git a/test/unit/api/cpp/theory_arith_nl_black.cpp b/test/unit/api/cpp/theory_arith_nl_black.cpp index 0b9b9f119..de33a3f3d 100644 --- a/test/unit/api/cpp/theory_arith_nl_black.cpp +++ b/test/unit/api/cpp/theory_arith_nl_black.cpp @@ -55,8 +55,8 @@ TEST_F(TestTheoryBlackArithNl, cvc5Projects388Min) return; } Solver slv; - slv.setOption("nl-cad", "true"); - slv.setOption("nl-cad-var-elim", "true"); + slv.setOption("nl-cov", "true"); + slv.setOption("nl-cov-var-elim", "true"); slv.setOption("nl-ext", "none"); slv.setLogic("QF_NIRA"); Sort s = slv.getRealSort(); diff --git a/test/unit/theory/CMakeLists.txt b/test/unit/theory/CMakeLists.txt index 82e191d83..82cd3cb58 100644 --- a/test/unit/theory/CMakeLists.txt +++ b/test/unit/theory/CMakeLists.txt @@ -22,7 +22,7 @@ cvc5_add_unit_test_white(sequences_rewriter_white theory) cvc5_add_unit_test_white(strings_rewriter_white theory) cvc5_add_unit_test_white(theory_arith_pow2_white theory) cvc5_add_unit_test_white(theory_arith_white theory) -cvc5_add_unit_test_white(theory_arith_cad_white theory) +cvc5_add_unit_test_white(theory_arith_coverings_white theory) cvc5_add_unit_test_black(theory_arith_rewriter_black theory) cvc5_add_unit_test_white(theory_bags_normal_form_white theory) cvc5_add_unit_test_white(theory_bags_rewriter_white theory) diff --git a/test/unit/theory/theory_arith_cad_white.cpp b/test/unit/theory/theory_arith_cad_white.cpp deleted file mode 100644 index aef2189ba..000000000 --- a/test/unit/theory/theory_arith_cad_white.cpp +++ /dev/null @@ -1,486 +0,0 @@ -/****************************************************************************** - * Top contributors (to current version): - * Gereon Kremer - * - * This file is part of the cvc5 project. - * - * Copyright (c) 2009-2021 by the authors listed in the file AUTHORS - * in the top-level source directory and their institutional affiliations. - * All rights reserved. See the file COPYING in the top-level source - * directory for licensing information. - * **************************************************************************** - * - * Unit tests for the CAD module for nonlinear arithmetic. - */ - -#ifdef CVC5_USE_POLY - -#include - -#include -#include -#include - -#include "test_smt.h" -#include "options/options_handler.h" -#include "options/proof_options.h" -#include "options/smt_options.h" -#include "smt/proof_manager.h" -#include "theory/arith/nl/cad/cdcac.h" -#include "theory/arith/nl/cad/lazard_evaluation.h" -#include "theory/arith/nl/cad/projections.h" -#include "theory/arith/nl/cad_solver.h" -#include "theory/arith/nl/nl_lemma_utils.h" -#include "theory/arith/nl/poly_conversion.h" -#include "theory/arith/theory_arith.h" -#include "theory/rewriter.h" -#include "theory/theory.h" -#include "theory/theory_engine.h" -#include "util/poly_util.h" - -namespace cvc5::test { - -using namespace cvc5; -using namespace cvc5::kind; -using namespace cvc5::theory; -using namespace cvc5::theory::arith; -using namespace cvc5::theory::arith::nl; - -NodeManager* nodeManager; -class TestTheoryWhiteArithCAD : public TestSmt -{ - protected: - void SetUp() override - { - TestSmt::SetUp(); - d_realType.reset(new TypeNode(d_nodeManager->realType())); - d_intType.reset(new TypeNode(d_nodeManager->integerType())); - Trace.on("cad-check"); - nodeManager = d_nodeManager; - } - - void TearDown() override - { - d_dummyCache.clear(); - TestSmt::TearDown(); - } - - Node dummy(int i) - { - auto it = d_dummyCache.find(i); - if (it == d_dummyCache.end()) - { - it = d_dummyCache - .emplace(i, - d_nodeManager->mkBoundVar("c" + std::to_string(i), - d_nodeManager->booleanType())) - .first; - } - return it->second; - } - - Theory::Effort d_level = Theory::EFFORT_FULL; - std::unique_ptr d_realType; - std::unique_ptr d_intType; - const Rational d_zero = 0; - const Rational d_one = 1; - std::map d_dummyCache; -}; - -poly::AlgebraicNumber get_ran(std::initializer_list init, - int lower, - int upper) -{ - return poly::AlgebraicNumber(poly::UPolynomial(init), - poly::DyadicInterval(lower, upper)); -} - -Node operator==(const Node& a, const Node& b) -{ - return nodeManager->mkNode(Kind::EQUAL, a, b); -} -Node operator>=(const Node& a, const Node& b) -{ - return nodeManager->mkNode(Kind::GEQ, a, b); -} -Node operator+(const Node& a, const Node& b) -{ - return nodeManager->mkNode(Kind::ADD, a, b); -} -Node operator*(const Node& a, const Node& b) -{ - return nodeManager->mkNode(Kind::NONLINEAR_MULT, a, b); -} -Node operator!(const Node& a) { return nodeManager->mkNode(Kind::NOT, a); } -Node make_real_variable(const std::string& s) -{ - SkolemManager* sm = nodeManager->getSkolemManager(); - return sm->mkDummySkolem( - s, nodeManager->realType(), "", SkolemManager::SKOLEM_EXACT_NAME); -} -Node make_int_variable(const std::string& s) -{ - SkolemManager* sm = nodeManager->getSkolemManager(); - return sm->mkDummySkolem( - s, nodeManager->integerType(), "", SkolemManager::SKOLEM_EXACT_NAME); -} - -TEST_F(TestTheoryWhiteArithCAD, test_univariate_isolation) -{ - poly::UPolynomial poly({-2, 2, 3, -3, -1, 1}); - auto roots = poly::isolate_real_roots(poly); - - EXPECT_TRUE(roots.size() == 4); - EXPECT_TRUE(roots[0] == get_ran({-2, 0, 1}, -2, -1)); - EXPECT_TRUE(roots[1] == poly::Integer(-1)); - EXPECT_TRUE(roots[2] == poly::Integer(1)); - EXPECT_TRUE(roots[3] == get_ran({-2, 0, 1}, 1, 2)); -} - -TEST_F(TestTheoryWhiteArithCAD, test_multivariate_isolation) -{ - poly::Variable x("x"); - poly::Variable y("y"); - poly::Variable z("z"); - - poly::Assignment a; - a.set(x, get_ran({-2, 0, 1}, 1, 2)); - a.set(y, get_ran({-2, 0, 0, 0, 1}, 1, 2)); - - poly::Polynomial poly = (y * y + x) - z; - - auto roots = poly::isolate_real_roots(poly, a); - - EXPECT_TRUE(roots.size() == 1); - EXPECT_TRUE(roots[0] == get_ran({-8, 0, 1}, 2, 3)); -} - -TEST_F(TestTheoryWhiteArithCAD, test_univariate_factorization) -{ - poly::UPolynomial poly({-24, 44, -18, -1, 1, -3, 1}); - - auto factors = square_free_factors(poly); - std::sort(factors.begin(), factors.end()); - EXPECT_EQ(factors[0], poly::UPolynomial({-1, 1})); - EXPECT_EQ(factors[1], poly::UPolynomial({-24, -4, -2, -1, 1})); -} - -TEST_F(TestTheoryWhiteArithCAD, test_projection) -{ - // Gereon's thesis, Ex 5.1 - poly::Variable x("x"); - poly::Variable y("y"); - - poly::Polynomial p = (y + 1) * (y + 1) - x * x * x + 3 * x - 2; - poly::Polynomial q = (x + 1) * y - 3; - - auto res = cad::projectionMcCallum({p, q}); - std::sort(res.begin(), res.end()); - EXPECT_EQ(res[0], x - 1); - EXPECT_EQ(res[1], x + 1); - EXPECT_EQ(res[2], x + 2); - EXPECT_EQ(res[3], x * x * x - 3 * x + 1); - EXPECT_EQ(res[4], - x * x * x * x * x + 2 * x * x * x * x - 2 * x * x * x - 5 * x * x - - 7 * x - 14); -} - -poly::Polynomial up_to_poly(const poly::UPolynomial& p, poly::Variable var) -{ - poly::Polynomial res; - poly::Polynomial mult = 1; - for (const auto& coeff : coefficients(p)) - { - if (!is_zero(coeff)) - { - res += mult * coeff; - } - mult *= var; - } - return res; -} - -TEST_F(TestTheoryWhiteArithCAD, lazard_simp) -{ - Rewriter* rewriter = d_slvEngine->getRewriter(); - Node a = d_nodeManager->mkVar(*d_realType); - Node c = d_nodeManager->mkVar(*d_realType); - Node orig = d_nodeManager->mkAnd(std::vector{ - d_nodeManager->mkNode( - Kind::EQUAL, a, d_nodeManager->mkConst(CONST_RATIONAL, d_zero)), - d_nodeManager->mkNode( - Kind::EQUAL, - d_nodeManager->mkNode( - Kind::ADD, - d_nodeManager->mkNode(Kind::NONLINEAR_MULT, a, c), - d_nodeManager->mkConst(CONST_RATIONAL, d_one)), - d_nodeManager->mkConst(CONST_RATIONAL, d_zero))}); - - { - Node rewritten = rewriter->rewrite(orig); - EXPECT_NE(rewritten, d_nodeManager->mkConst(false)); - } - { - Node rewritten = rewriter->extendedRewrite(orig); - EXPECT_EQ(rewritten, d_nodeManager->mkConst(false)); - } -} - -#ifdef CVC5_USE_COCOA -TEST_F(TestTheoryWhiteArithCAD, lazard_eval) -{ - poly::Variable x("x"); - poly::Variable y("y"); - poly::Variable z("z"); - poly::Variable f("f"); - poly::AlgebraicNumber ax = get_ran({-2, 0, 1}, 1, 2); - poly::AlgebraicNumber ay = get_ran({-2, 0, 0, 0, 1}, 1, 2); - poly::AlgebraicNumber az = get_ran({-3, 0, 1}, 1, 2); - - cad::LazardEvaluation lazard; - lazard.add(x, ax); - lazard.add(y, ay); - lazard.add(z, az); - - poly::Polynomial q = (x * x - 2) * (y * y * y * y - 2) * z * f; - lazard.addFreeVariable(f); - auto qred = lazard.reducePolynomial(q); - EXPECT_EQ(qred, std::vector{f}); -} -#endif - -TEST_F(TestTheoryWhiteArithCAD, test_cdcac_1) -{ - Options opts; - Env env(NodeManager::currentNM(), &opts); - cad::CDCAC cac(env, {}); - poly::Variable x = cac.getConstraints().varMapper()(make_real_variable("x")); - poly::Variable y = cac.getConstraints().varMapper()(make_real_variable("y")); - - cac.getConstraints().addConstraint( - 4 * y - x * x + 4, poly::SignCondition::LT, dummy(1)); - cac.getConstraints().addConstraint( - 4 * y - 4 + (x - 1) * (x - 1), poly::SignCondition::GT, dummy(2)); - cac.getConstraints().addConstraint( - 4 * y - x - 2, poly::SignCondition::GT, dummy(3)); - - cac.computeVariableOrdering(); - - auto cover = cac.getUnsatCover(); - EXPECT_TRUE(cover.empty()); - std::cout << "SAT: " << cac.getModel() << std::endl; -} - -TEST_F(TestTheoryWhiteArithCAD, test_cdcac_2) -{ - Options opts; - Env env(NodeManager::currentNM(), &opts); - cad::CDCAC cac(env, {}); - poly::Variable x = cac.getConstraints().varMapper()(make_real_variable("x")); - poly::Variable y = cac.getConstraints().varMapper()(make_real_variable("y")); - - cac.getConstraints().addConstraint(y - pow(-x - 3, 11) + pow(-x - 3, 10) + 1, - poly::SignCondition::GT, - dummy(1)); - cac.getConstraints().addConstraint( - 2 * y - x + 2, poly::SignCondition::LT, dummy(2)); - cac.getConstraints().addConstraint( - 2 * y - 1 + x * x, poly::SignCondition::GT, dummy(3)); - cac.getConstraints().addConstraint( - 3 * y + x + 2, poly::SignCondition::LT, dummy(4)); - cac.getConstraints().addConstraint( - y * y * y - pow(x - 2, 11) + pow(x - 2, 10) + 1, - poly::SignCondition::GT, - dummy(5)); - - cac.computeVariableOrdering(); - - auto cover = cac.getUnsatCover(); - EXPECT_TRUE(!cover.empty()); - auto nodes = cad::collectConstraints(cover); - std::vector ref{dummy(1), dummy(2), dummy(3), dummy(4), dummy(5)}; - std::sort(nodes.begin(), nodes.end()); - std::sort(ref.begin(), ref.end()); - EXPECT_EQ(nodes, ref); -} - -TEST_F(TestTheoryWhiteArithCAD, test_cdcac_3) -{ - Options opts; - Env env(NodeManager::currentNM(), &opts); - cad::CDCAC cac(env, {}); - poly::Variable x = cac.getConstraints().varMapper()(make_real_variable("x")); - poly::Variable y = cac.getConstraints().varMapper()(make_real_variable("y")); - poly::Variable z = cac.getConstraints().varMapper()(make_real_variable("z")); - - cac.getConstraints().addConstraint( - x * x + y * y + z * z - 1, poly::SignCondition::LT, dummy(1)); - cac.getConstraints().addConstraint( - 4 * x * x + (2 * y - 3) * (2 * y - 3) + 4 * z * z - 4, - poly::SignCondition::LT, - dummy(2)); - - cac.computeVariableOrdering(); - - auto cover = cac.getUnsatCover(); - EXPECT_TRUE(cover.empty()); - std::cout << "SAT: " << cac.getModel() << std::endl; -} - -TEST_F(TestTheoryWhiteArithCAD, test_cdcac_4) -{ - Options opts; - Env env(NodeManager::currentNM(), &opts); - cad::CDCAC cac(env, {}); - poly::Variable x = cac.getConstraints().varMapper()(make_real_variable("x")); - poly::Variable y = cac.getConstraints().varMapper()(make_real_variable("y")); - poly::Variable z = cac.getConstraints().varMapper()(make_real_variable("z")); - - cac.getConstraints().addConstraint( - -z * z + y * y + x * x - 25, poly::SignCondition::GT, dummy(1)); - cac.getConstraints().addConstraint( - (y - x - 6) * z * z - 9 * y * y + x * x - 1, - poly::SignCondition::GT, - dummy(2)); - cac.getConstraints().addConstraint( - y * y - 100, poly::SignCondition::LT, dummy(3)); - - cac.computeVariableOrdering(); - - auto cover = cac.getUnsatCover(); - EXPECT_TRUE(cover.empty()); - std::cout << "SAT: " << cac.getModel() << std::endl; -} - -void test_delta(const std::vector& a) -{ - Options opts; - Env env(NodeManager::currentNM(), &opts); - cad::CDCAC cac(env, {}); - cac.reset(); - for (const Node& n : a) - { - cac.getConstraints().addConstraint(n); - } - cac.computeVariableOrdering(); - - // Do full theory check here - - auto covering = cac.getUnsatCover(); - if (covering.empty()) - { - std::cout << "SAT: " << cac.getModel() << std::endl; - } - else - { - auto mis = cad::collectConstraints(covering); - std::cout << "Collected MIS: " << mis << std::endl; - Assert(!mis.empty()) << "Infeasible subset can not be empty"; - Node lem = NodeManager::currentNM()->mkAnd(mis).negate(); - std::cout << "UNSAT with MIS: " << lem << std::endl; - } -} - -TEST_F(TestTheoryWhiteArithCAD, test_cdcac_proof_1) -{ - Options opts; - // enable proofs - opts.smt.proofMode = options::ProofMode::FULL; - opts.smt.produceProofs = true; - Env env(NodeManager::currentNM(), &opts); - opts.handler().setDefaultDagThresh("--dag-thresh", 0); - smt::PfManager pfm(env); - EXPECT_TRUE(env.isTheoryProofProducing()); - // register checkers that we need - builtin::BuiltinProofRuleChecker btchecker(env); - btchecker.registerTo(env.getProofNodeManager()->getChecker()); - cad::CADProofRuleChecker checker; - checker.registerTo(env.getProofNodeManager()->getChecker()); - // do the coverings problem - cad::CDCAC cac(env, {}); - EXPECT_TRUE(cac.getProof() != nullptr); - cac.startNewProof(); - poly::Variable x = cac.getConstraints().varMapper()(make_real_variable("x")); - poly::Variable y = cac.getConstraints().varMapper()(make_real_variable("y")); - - cac.getConstraints().addConstraint( - 4 * y - x * x + 4, poly::SignCondition::LT, dummy(1)); - cac.getConstraints().addConstraint( - 3 * y - 5 + (x - 2) * (x - 2), poly::SignCondition::GT, dummy(2)); - cac.getConstraints().addConstraint( - 4 * y - x - 2, poly::SignCondition::GT, dummy(3)); - cac.getConstraints().addConstraint( - x + 1, poly::SignCondition::GT, dummy(4)); - cac.getConstraints().addConstraint( - x - 2, poly::SignCondition::LT, dummy(5)); - - cac.computeVariableOrdering(); - - auto cover = cac.getUnsatCover(); - EXPECT_FALSE(cover.empty()); - - std::vector mis{dummy(1), dummy(3), dummy(4), dummy(5)}; - LazyTreeProofGenerator* pg = dynamic_cast(cac.closeProof(mis)); - EXPECT_TRUE(pg != nullptr); -} - -TEST_F(TestTheoryWhiteArithCAD, test_delta_one) -{ - std::vector a; - Node zero = d_nodeManager->mkConst(CONST_RATIONAL, Rational(0)); - Node one = d_nodeManager->mkConst(CONST_RATIONAL, Rational(1)); - Node mone = d_nodeManager->mkConst(CONST_RATIONAL, Rational(-1)); - Node fifth = d_nodeManager->mkConst(CONST_RATIONAL, Rational(1, 2)); - Node g = make_real_variable("g"); - Node l = make_real_variable("l"); - Node q = make_real_variable("q"); - Node s = make_real_variable("s"); - Node u = make_real_variable("u"); - - a.emplace_back(l == mone); - a.emplace_back(!(g * s == zero)); - a.emplace_back(q * s == zero); - a.emplace_back(u == zero); - a.emplace_back(q == (one + (fifth * g * s))); - a.emplace_back(l == u + q * s + (fifth * g * s * s)); - - test_delta(a); -} - -TEST_F(TestTheoryWhiteArithCAD, test_delta_two) -{ - std::vector a; - Node zero = d_nodeManager->mkConst(CONST_RATIONAL, Rational(0)); - Node one = d_nodeManager->mkConst(CONST_RATIONAL, Rational(1)); - Node mone = d_nodeManager->mkConst(CONST_RATIONAL, Rational(-1)); - Node fifth = d_nodeManager->mkConst(CONST_RATIONAL, Rational(1, 2)); - Node g = make_real_variable("g"); - Node l = make_real_variable("l"); - Node q = make_real_variable("q"); - Node s = make_real_variable("s"); - Node u = make_real_variable("u"); - - a.emplace_back(l == mone); - a.emplace_back(!(g * s == zero)); - a.emplace_back(u == zero); - a.emplace_back(q * s == zero); - a.emplace_back(q == (one + (fifth * g * s))); - a.emplace_back(l == u + q * s + (fifth * g * s * s)); - - test_delta(a); -} - -TEST_F(TestTheoryWhiteArithCAD, test_ran_conversion) -{ - RealAlgebraicNumber ran( - std::vector({-2, 0, 1}), Rational(1, 3), Rational(7, 3)); - { - Node x = make_real_variable("x"); - Node n = nl::ran_to_node(ran, x); - RealAlgebraicNumber back = nl::node_to_ran(n, x); - EXPECT_TRUE(ran == back); - } -} -} // namespace cvc5::test - -#endif diff --git a/test/unit/theory/theory_arith_coverings_white.cpp b/test/unit/theory/theory_arith_coverings_white.cpp new file mode 100644 index 000000000..864402eed --- /dev/null +++ b/test/unit/theory/theory_arith_coverings_white.cpp @@ -0,0 +1,485 @@ +/****************************************************************************** + * Top contributors (to current version): + * Gereon Kremer + * + * This file is part of the cvc5 project. + * + * Copyright (c) 2009-2021 by the authors listed in the file AUTHORS + * in the top-level source directory and their institutional affiliations. + * All rights reserved. See the file COPYING in the top-level source + * directory for licensing information. + * **************************************************************************** + * + * Unit tests for the coverings module for nonlinear arithmetic. + */ + +#ifdef CVC5_USE_POLY + +#include + +#include +#include +#include + +#include "test_smt.h" +#include "options/options_handler.h" +#include "options/proof_options.h" +#include "options/smt_options.h" +#include "smt/proof_manager.h" +#include "theory/arith/nl/coverings/cdcac.h" +#include "theory/arith/nl/coverings/lazard_evaluation.h" +#include "theory/arith/nl/coverings/projections.h" +#include "theory/arith/nl/coverings_solver.h" +#include "theory/arith/nl/nl_lemma_utils.h" +#include "theory/arith/nl/poly_conversion.h" +#include "theory/arith/theory_arith.h" +#include "theory/rewriter.h" +#include "theory/theory.h" +#include "theory/theory_engine.h" +#include "util/poly_util.h" + +namespace cvc5::test { + +using namespace cvc5; +using namespace cvc5::kind; +using namespace cvc5::theory; +using namespace cvc5::theory::arith; +using namespace cvc5::theory::arith::nl; + +NodeManager* nodeManager; +class TestTheoryWhiteArithCoverings : public TestSmt +{ + protected: + void SetUp() override + { + TestSmt::SetUp(); + d_realType.reset(new TypeNode(d_nodeManager->realType())); + d_intType.reset(new TypeNode(d_nodeManager->integerType())); + nodeManager = d_nodeManager; + } + + void TearDown() override + { + d_dummyCache.clear(); + TestSmt::TearDown(); + } + + Node dummy(int i) + { + auto it = d_dummyCache.find(i); + if (it == d_dummyCache.end()) + { + it = d_dummyCache + .emplace(i, + d_nodeManager->mkBoundVar("c" + std::to_string(i), + d_nodeManager->booleanType())) + .first; + } + return it->second; + } + + Theory::Effort d_level = Theory::EFFORT_FULL; + std::unique_ptr d_realType; + std::unique_ptr d_intType; + const Rational d_zero = 0; + const Rational d_one = 1; + std::map d_dummyCache; +}; + +poly::AlgebraicNumber get_ran(std::initializer_list init, + int lower, + int upper) +{ + return poly::AlgebraicNumber(poly::UPolynomial(init), + poly::DyadicInterval(lower, upper)); +} + +Node operator==(const Node& a, const Node& b) +{ + return nodeManager->mkNode(Kind::EQUAL, a, b); +} +Node operator>=(const Node& a, const Node& b) +{ + return nodeManager->mkNode(Kind::GEQ, a, b); +} +Node operator+(const Node& a, const Node& b) +{ + return nodeManager->mkNode(Kind::ADD, a, b); +} +Node operator*(const Node& a, const Node& b) +{ + return nodeManager->mkNode(Kind::NONLINEAR_MULT, a, b); +} +Node operator!(const Node& a) { return nodeManager->mkNode(Kind::NOT, a); } +Node make_real_variable(const std::string& s) +{ + SkolemManager* sm = nodeManager->getSkolemManager(); + return sm->mkDummySkolem( + s, nodeManager->realType(), "", SkolemManager::SKOLEM_EXACT_NAME); +} +Node make_int_variable(const std::string& s) +{ + SkolemManager* sm = nodeManager->getSkolemManager(); + return sm->mkDummySkolem( + s, nodeManager->integerType(), "", SkolemManager::SKOLEM_EXACT_NAME); +} + +TEST_F(TestTheoryWhiteArithCoverings, test_univariate_isolation) +{ + poly::UPolynomial poly({-2, 2, 3, -3, -1, 1}); + auto roots = poly::isolate_real_roots(poly); + + EXPECT_TRUE(roots.size() == 4); + EXPECT_TRUE(roots[0] == get_ran({-2, 0, 1}, -2, -1)); + EXPECT_TRUE(roots[1] == poly::Integer(-1)); + EXPECT_TRUE(roots[2] == poly::Integer(1)); + EXPECT_TRUE(roots[3] == get_ran({-2, 0, 1}, 1, 2)); +} + +TEST_F(TestTheoryWhiteArithCoverings, test_multivariate_isolation) +{ + poly::Variable x("x"); + poly::Variable y("y"); + poly::Variable z("z"); + + poly::Assignment a; + a.set(x, get_ran({-2, 0, 1}, 1, 2)); + a.set(y, get_ran({-2, 0, 0, 0, 1}, 1, 2)); + + poly::Polynomial poly = (y * y + x) - z; + + auto roots = poly::isolate_real_roots(poly, a); + + EXPECT_TRUE(roots.size() == 1); + EXPECT_TRUE(roots[0] == get_ran({-8, 0, 1}, 2, 3)); +} + +TEST_F(TestTheoryWhiteArithCoverings, test_univariate_factorization) +{ + poly::UPolynomial poly({-24, 44, -18, -1, 1, -3, 1}); + + auto factors = square_free_factors(poly); + std::sort(factors.begin(), factors.end()); + EXPECT_EQ(factors[0], poly::UPolynomial({-1, 1})); + EXPECT_EQ(factors[1], poly::UPolynomial({-24, -4, -2, -1, 1})); +} + +TEST_F(TestTheoryWhiteArithCoverings, test_projection) +{ + // Gereon's thesis, Ex 5.1 + poly::Variable x("x"); + poly::Variable y("y"); + + poly::Polynomial p = (y + 1) * (y + 1) - x * x * x + 3 * x - 2; + poly::Polynomial q = (x + 1) * y - 3; + + auto res = coverings::projectionMcCallum({p, q}); + std::sort(res.begin(), res.end()); + EXPECT_EQ(res[0], x - 1); + EXPECT_EQ(res[1], x + 1); + EXPECT_EQ(res[2], x + 2); + EXPECT_EQ(res[3], x * x * x - 3 * x + 1); + EXPECT_EQ(res[4], + x * x * x * x * x + 2 * x * x * x * x - 2 * x * x * x - 5 * x * x + - 7 * x - 14); +} + +poly::Polynomial up_to_poly(const poly::UPolynomial& p, poly::Variable var) +{ + poly::Polynomial res; + poly::Polynomial mult = 1; + for (const auto& coeff : coefficients(p)) + { + if (!is_zero(coeff)) + { + res += mult * coeff; + } + mult *= var; + } + return res; +} + +TEST_F(TestTheoryWhiteArithCoverings, lazard_simp) +{ + Rewriter* rewriter = d_slvEngine->getRewriter(); + Node a = d_nodeManager->mkVar(*d_realType); + Node c = d_nodeManager->mkVar(*d_realType); + Node orig = d_nodeManager->mkAnd(std::vector{ + d_nodeManager->mkNode( + Kind::EQUAL, a, d_nodeManager->mkConst(CONST_RATIONAL, d_zero)), + d_nodeManager->mkNode( + Kind::EQUAL, + d_nodeManager->mkNode( + Kind::ADD, + d_nodeManager->mkNode(Kind::NONLINEAR_MULT, a, c), + d_nodeManager->mkConst(CONST_RATIONAL, d_one)), + d_nodeManager->mkConst(CONST_RATIONAL, d_zero))}); + + { + Node rewritten = rewriter->rewrite(orig); + EXPECT_NE(rewritten, d_nodeManager->mkConst(false)); + } + { + Node rewritten = rewriter->extendedRewrite(orig); + EXPECT_EQ(rewritten, d_nodeManager->mkConst(false)); + } +} + +#ifdef CVC5_USE_COCOA +TEST_F(TestTheoryWhiteArithCoverings, lazard_eval) +{ + poly::Variable x("x"); + poly::Variable y("y"); + poly::Variable z("z"); + poly::Variable f("f"); + poly::AlgebraicNumber ax = get_ran({-2, 0, 1}, 1, 2); + poly::AlgebraicNumber ay = get_ran({-2, 0, 0, 0, 1}, 1, 2); + poly::AlgebraicNumber az = get_ran({-3, 0, 1}, 1, 2); + + coverings::LazardEvaluation lazard; + lazard.add(x, ax); + lazard.add(y, ay); + lazard.add(z, az); + + poly::Polynomial q = (x * x - 2) * (y * y * y * y - 2) * z * f; + lazard.addFreeVariable(f); + auto qred = lazard.reducePolynomial(q); + EXPECT_EQ(qred, std::vector{f}); +} +#endif + +TEST_F(TestTheoryWhiteArithCoverings, test_cdcac_1) +{ + Options opts; + Env env(NodeManager::currentNM(), &opts); + coverings::CDCAC cac(env, {}); + poly::Variable x = cac.getConstraints().varMapper()(make_real_variable("x")); + poly::Variable y = cac.getConstraints().varMapper()(make_real_variable("y")); + + cac.getConstraints().addConstraint( + 4 * y - x * x + 4, poly::SignCondition::LT, dummy(1)); + cac.getConstraints().addConstraint( + 4 * y - 4 + (x - 1) * (x - 1), poly::SignCondition::GT, dummy(2)); + cac.getConstraints().addConstraint( + 4 * y - x - 2, poly::SignCondition::GT, dummy(3)); + + cac.computeVariableOrdering(); + + auto cover = cac.getUnsatCover(); + EXPECT_TRUE(cover.empty()); + std::cout << "SAT: " << cac.getModel() << std::endl; +} + +TEST_F(TestTheoryWhiteArithCoverings, test_cdcac_2) +{ + Options opts; + Env env(NodeManager::currentNM(), &opts); + coverings::CDCAC cac(env, {}); + poly::Variable x = cac.getConstraints().varMapper()(make_real_variable("x")); + poly::Variable y = cac.getConstraints().varMapper()(make_real_variable("y")); + + cac.getConstraints().addConstraint(y - pow(-x - 3, 11) + pow(-x - 3, 10) + 1, + poly::SignCondition::GT, + dummy(1)); + cac.getConstraints().addConstraint( + 2 * y - x + 2, poly::SignCondition::LT, dummy(2)); + cac.getConstraints().addConstraint( + 2 * y - 1 + x * x, poly::SignCondition::GT, dummy(3)); + cac.getConstraints().addConstraint( + 3 * y + x + 2, poly::SignCondition::LT, dummy(4)); + cac.getConstraints().addConstraint( + y * y * y - pow(x - 2, 11) + pow(x - 2, 10) + 1, + poly::SignCondition::GT, + dummy(5)); + + cac.computeVariableOrdering(); + + auto cover = cac.getUnsatCover(); + EXPECT_TRUE(!cover.empty()); + auto nodes = coverings::collectConstraints(cover); + std::vector ref{dummy(1), dummy(2), dummy(3), dummy(4), dummy(5)}; + std::sort(nodes.begin(), nodes.end()); + std::sort(ref.begin(), ref.end()); + EXPECT_EQ(nodes, ref); +} + +TEST_F(TestTheoryWhiteArithCoverings, test_cdcac_3) +{ + Options opts; + Env env(NodeManager::currentNM(), &opts); + coverings::CDCAC cac(env, {}); + poly::Variable x = cac.getConstraints().varMapper()(make_real_variable("x")); + poly::Variable y = cac.getConstraints().varMapper()(make_real_variable("y")); + poly::Variable z = cac.getConstraints().varMapper()(make_real_variable("z")); + + cac.getConstraints().addConstraint( + x * x + y * y + z * z - 1, poly::SignCondition::LT, dummy(1)); + cac.getConstraints().addConstraint( + 4 * x * x + (2 * y - 3) * (2 * y - 3) + 4 * z * z - 4, + poly::SignCondition::LT, + dummy(2)); + + cac.computeVariableOrdering(); + + auto cover = cac.getUnsatCover(); + EXPECT_TRUE(cover.empty()); + std::cout << "SAT: " << cac.getModel() << std::endl; +} + +TEST_F(TestTheoryWhiteArithCoverings, test_cdcac_4) +{ + Options opts; + Env env(NodeManager::currentNM(), &opts); + coverings::CDCAC cac(env, {}); + poly::Variable x = cac.getConstraints().varMapper()(make_real_variable("x")); + poly::Variable y = cac.getConstraints().varMapper()(make_real_variable("y")); + poly::Variable z = cac.getConstraints().varMapper()(make_real_variable("z")); + + cac.getConstraints().addConstraint( + -z * z + y * y + x * x - 25, poly::SignCondition::GT, dummy(1)); + cac.getConstraints().addConstraint( + (y - x - 6) * z * z - 9 * y * y + x * x - 1, + poly::SignCondition::GT, + dummy(2)); + cac.getConstraints().addConstraint( + y * y - 100, poly::SignCondition::LT, dummy(3)); + + cac.computeVariableOrdering(); + + auto cover = cac.getUnsatCover(); + EXPECT_TRUE(cover.empty()); + std::cout << "SAT: " << cac.getModel() << std::endl; +} + +void test_delta(const std::vector& a) +{ + Options opts; + Env env(NodeManager::currentNM(), &opts); + coverings::CDCAC cac(env, {}); + cac.reset(); + for (const Node& n : a) + { + cac.getConstraints().addConstraint(n); + } + cac.computeVariableOrdering(); + + // Do full theory check here + + auto covering = cac.getUnsatCover(); + if (covering.empty()) + { + std::cout << "SAT: " << cac.getModel() << std::endl; + } + else + { + auto mis = coverings::collectConstraints(covering); + std::cout << "Collected MIS: " << mis << std::endl; + Assert(!mis.empty()) << "Infeasible subset can not be empty"; + Node lem = NodeManager::currentNM()->mkAnd(mis).negate(); + std::cout << "UNSAT with MIS: " << lem << std::endl; + } +} + +TEST_F(TestTheoryWhiteArithCoverings, test_cdcac_proof_1) +{ + Options opts; + // enable proofs + opts.smt.proofMode = options::ProofMode::FULL; + opts.smt.produceProofs = true; + Env env(NodeManager::currentNM(), &opts); + opts.handler().setDefaultDagThresh("--dag-thresh", 0); + smt::PfManager pfm(env); + EXPECT_TRUE(env.isTheoryProofProducing()); + // register checkers that we need + builtin::BuiltinProofRuleChecker btchecker(env); + btchecker.registerTo(env.getProofNodeManager()->getChecker()); + coverings::CoveringsProofRuleChecker checker; + checker.registerTo(env.getProofNodeManager()->getChecker()); + // do the coverings problem + coverings::CDCAC cac(env, {}); + EXPECT_TRUE(cac.getProof() != nullptr); + cac.startNewProof(); + poly::Variable x = cac.getConstraints().varMapper()(make_real_variable("x")); + poly::Variable y = cac.getConstraints().varMapper()(make_real_variable("y")); + + cac.getConstraints().addConstraint( + 4 * y - x * x + 4, poly::SignCondition::LT, dummy(1)); + cac.getConstraints().addConstraint( + 3 * y - 5 + (x - 2) * (x - 2), poly::SignCondition::GT, dummy(2)); + cac.getConstraints().addConstraint( + 4 * y - x - 2, poly::SignCondition::GT, dummy(3)); + cac.getConstraints().addConstraint( + x + 1, poly::SignCondition::GT, dummy(4)); + cac.getConstraints().addConstraint( + x - 2, poly::SignCondition::LT, dummy(5)); + + cac.computeVariableOrdering(); + + auto cover = cac.getUnsatCover(); + EXPECT_FALSE(cover.empty()); + + std::vector mis{dummy(1), dummy(3), dummy(4), dummy(5)}; + LazyTreeProofGenerator* pg = dynamic_cast(cac.closeProof(mis)); + EXPECT_TRUE(pg != nullptr); +} + +TEST_F(TestTheoryWhiteArithCoverings, test_delta_one) +{ + std::vector a; + Node zero = d_nodeManager->mkConst(CONST_RATIONAL, Rational(0)); + Node one = d_nodeManager->mkConst(CONST_RATIONAL, Rational(1)); + Node mone = d_nodeManager->mkConst(CONST_RATIONAL, Rational(-1)); + Node fifth = d_nodeManager->mkConst(CONST_RATIONAL, Rational(1, 2)); + Node g = make_real_variable("g"); + Node l = make_real_variable("l"); + Node q = make_real_variable("q"); + Node s = make_real_variable("s"); + Node u = make_real_variable("u"); + + a.emplace_back(l == mone); + a.emplace_back(!(g * s == zero)); + a.emplace_back(q * s == zero); + a.emplace_back(u == zero); + a.emplace_back(q == (one + (fifth * g * s))); + a.emplace_back(l == u + q * s + (fifth * g * s * s)); + + test_delta(a); +} + +TEST_F(TestTheoryWhiteArithCoverings, test_delta_two) +{ + std::vector a; + Node zero = d_nodeManager->mkConst(CONST_RATIONAL, Rational(0)); + Node one = d_nodeManager->mkConst(CONST_RATIONAL, Rational(1)); + Node mone = d_nodeManager->mkConst(CONST_RATIONAL, Rational(-1)); + Node fifth = d_nodeManager->mkConst(CONST_RATIONAL, Rational(1, 2)); + Node g = make_real_variable("g"); + Node l = make_real_variable("l"); + Node q = make_real_variable("q"); + Node s = make_real_variable("s"); + Node u = make_real_variable("u"); + + a.emplace_back(l == mone); + a.emplace_back(!(g * s == zero)); + a.emplace_back(u == zero); + a.emplace_back(q * s == zero); + a.emplace_back(q == (one + (fifth * g * s))); + a.emplace_back(l == u + q * s + (fifth * g * s * s)); + + test_delta(a); +} + +TEST_F(TestTheoryWhiteArithCoverings, test_ran_conversion) +{ + RealAlgebraicNumber ran( + std::vector({-2, 0, 1}), Rational(1, 3), Rational(7, 3)); + { + Node x = make_real_variable("x"); + Node n = nl::ran_to_node(ran, x); + RealAlgebraicNumber back = nl::node_to_ran(n, x); + EXPECT_TRUE(ran == back); + } +} +} // namespace cvc5::test + +#endif