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.
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
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."
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."
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."
*
* 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]
* )
* )
* 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();
* 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();
*
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";
// 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
// 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)
// 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
#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;
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;
}
}
#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;
}
}
+++ /dev/null
-/******************************************************************************
- * 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 <typename T>
-std::ostream& operator<<(std::ostream& os, const std::vector<T>& 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<poly::Variable>& 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<poly::Variable>& CDCAC::getVariableOrdering() const
-{
- return d_variableOrdering;
-}
-
-std::vector<CACInterval> CDCAC::getUnsatIntervals(std::size_t cur_variable)
-{
- std::vector<CACInterval> 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<poly::Interval> 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<CACInterval>& 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<Node> 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<bool>());
- 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<CACInterval>& 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<poly::Value> 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<CACInterval> CDCAC::getUnsatCoverImpl(std::size_t curVariable,
- bool returnFirstInterval)
-{
- Trace("cdcac") << "Looking for unsat cover for "
- << d_variableOrdering[curVariable] << std::endl;
- std::vector<CACInterval> 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<CACInterval> 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<Node>& 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<CACInterval>& 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<poly::Value> 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
+++ /dev/null
-/******************************************************************************
- * 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 <poly/polyxx.h>
-
-#include <vector>
-
-#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<poly::Variable>& 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<poly::Variable>& 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<CACInterval> 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<CACInterval>& 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<CACInterval>& 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<CACInterval> 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<CACInterval> 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<Node>& 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<CACInterval>& 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<poly::Value> 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<poly::Variable> d_variableOrdering;
-
- /** The object computing the variable ordering. */
- VariableOrdering d_varOrder;
-
- /** The linear assignment used as an initial guess. */
- std::vector<poly::Value> d_initialAssignment;
-
- /** The proof generator */
- std::unique_ptr<CADProofGenerator> d_proof;
-
- /** The next interval id */
- size_t d_nextIntervalId = 1;
-};
-
-} // namespace cad
-} // namespace nl
-} // namespace arith
-} // namespace theory
-} // namespace cvc5
-
-#endif
-
-#endif
+++ /dev/null
-/******************************************************************************
- * 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 <optional>
-
-#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<bool> 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<CACInterval>& 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<CACInterval>& 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<Node> collectConstraints(const std::vector<CACInterval>& intervals)
-{
- std::vector<Node> 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<CACInterval>& 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<poly::Polynomial, poly::Polynomial> 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
+++ /dev/null
-/******************************************************************************
- * 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 <poly/polyxx.h>
-
-#include <vector>
-
-#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<Node> 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<CACInterval>& 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<CACInterval>& intervals);
-
-/**
- * Collect all origins from the list of intervals to construct the origins for a
- * whole covering.
- */
-std::vector<Node> collectConstraints(const std::vector<CACInterval>& 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<CACInterval>& 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
+++ /dev/null
-/******************************************************************************
- * 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 <algorithm>
-
-#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<poly::Polynomial, poly::SignCondition, Node>;
- 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
+++ /dev/null
-/******************************************************************************
- * 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 <poly/polyxx.h>
-
-#include <tuple>
-#include <vector>
-
-#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<poly::Polynomial, poly::SignCondition, Node>;
- using ConstraintVector = std::vector<Constraint>;
-
- 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
+++ /dev/null
-#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 <CoCoA/library.H>
-
-#include <optional>
-
-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]/<p_0> and recursively extend it with a_1, etc, in the same way. Doing
- * this recursive construction naively fails: (Q[x_0]/<p_0>)[x_1]/<p_1> is not
- * necessarily a proper field as p_1 (though a minimal polynomial in Q[x_1]) may
- * factor over Q[x_0]/<p_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]/<p_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]/<p_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]/<p_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<poly::Variable, CoCoA::RingElem> 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<std::pair<long, size_t>, 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<CoCoA::RingElem> d_p;
-
- /**
- * The sequence of extension fields.
- * K_0 = Q, K_{i+1} = K_i[x_i]/<p_i>
- * Every K_i is a field.
- */
- std::vector<CoCoA::ring> 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<CoCoA::ring> 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<CoCoA::ring> d_J;
-
- /**
- * Custom homomorphism from R_i to J_i. PolyAlgebraHom with
- * Indets(R_i) = (x_i) --> (x_i)
- */
- std::vector<CoCoA::RingHom> 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<CoCoA::RingHom> 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<CoCoA::RingElem> 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<poly::Variable> 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<std::optional<CoCoA::RingElem>> 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<poly::Integer> 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<long> 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/<p_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<CoCoA::symbol> 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<CoCoA::RingElem> 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<CoCoA::RingElem> 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<CoCoAPolyConstructor*>(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<long> 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<poly::Polynomial> reduce(const poly::Polynomial& qpoly) const
- {
- d_stats.d_evaluations++;
- std::vector<poly::Polynomial> 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<CoCoA::RingElem> 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<LazardEvaluationState>())
-{
-}
-
-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<CoCoA::BigRat> 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<poly::Polynomial> 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<poly::Value> LazardEvaluation::isolateRealRoots(
- const poly::Polynomial& q) const
-{
- poly::Assignment a;
- std::vector<poly::Value> 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<poly::Value> 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<poly::Interval> LazardEvaluation::infeasibleRegions(
- const poly::Polynomial& q, poly::SignCondition sc) const
-{
- std::vector<poly::Value> 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<poly::Interval> 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<poly::Interval> 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<LazardEvaluationState>())
-{
-}
-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<poly::Polynomial> LazardEvaluation::reducePolynomial(
- const poly::Polynomial& p) const
-{
- return {p};
-}
-
-std::vector<poly::Value> 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<poly::Interval> 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
+++ /dev/null
-/******************************************************************************
- * 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 <poly/polyxx.h>
-
-#include <memory>
-
-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]/<p_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<poly::Polynomial> reducePolynomial(
- const poly::Polynomial& q) const;
-
- /**
- * Isolates the real roots of the given polynomials.
- */
- std::vector<poly::Value> 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<poly::Interval> infeasibleRegions(const poly::Polynomial& q,
- poly::SignCondition sc) const;
-
- private:
- std::unique_ptr<LazardEvaluationState> d_state;
-};
-
-} // namespace cvc5::theory::arith::nl::cad
-
-#endif
-#endif
+++ /dev/null
-/******************************************************************************
- * 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<poly::Polynomial>::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<Polynomial>& 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
+++ /dev/null
-/******************************************************************************
- * 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 <poly/polyxx.h>
-
-#include <vector>
-
-namespace cvc5 {
-namespace theory {
-namespace arith {
-namespace nl {
-namespace cad {
-
-/**
- * A simple wrapper around std::vector<poly::Polynomial> that ensures that all
- * polynomials are properly factorized and pruned when added to the list.
- */
-class PolyVector : public std::vector<poly::Polynomial>
-{
- 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<poly::Polynomial> 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<poly::Polynomial>& polys);
-
-} // namespace cad
-} // namespace nl
-} // namespace arith
-} // namespace theory
-} // namespace cvc5
-
-#endif
-
-#endif
+++ /dev/null
-/******************************************************************************
- * 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<Node>& children,
- const std::vector<Node>& 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
+++ /dev/null
-/******************************************************************************
- * 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<Node>& children,
- const std::vector<Node>& args) override;
-};
-
-} // namespace cad
-} // namespace nl
-} // namespace arith
-} // namespace theory
-} // namespace cvc5
-
-#endif /* CVC5__THEORY__STRINGS__PROOF_CHECKER_H */
+++ /dev/null
-/******************************************************************************
- * 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<std::size_t, std::size_t> getRootIDs(
- const std::vector<poly::Value>& 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>(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<bool>(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<Node>& 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<Node> 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<Node> 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<Node> 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
+++ /dev/null
-/******************************************************************************
- * 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 <poly/polyxx.h>
-
-#include <vector>
-
-#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<Node>& 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 <typename F>
- 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<Node> 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<LazyTreeProofGenerator> 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
+++ /dev/null
-/******************************************************************************
- * 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<poly_utils::VariableInformation> collectInformation(
- const Constraints::ConstraintVector& polys, bool with_totals)
-{
- poly::VariableCollector vc;
- for (const auto& c : polys)
- {
- vc(std::get<0>(c));
- }
- std::vector<poly_utils::VariableInformation> 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<poly::Variable> getVariables(
- const std::vector<poly_utils::VariableInformation>& vi)
-{
- std::vector<poly::Variable> res;
- for (const auto& v : vi)
- {
- res.emplace_back(v.var);
- }
- return res;
-}
-
-std::vector<poly::Variable> 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<poly::Variable> 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<poly::Variable> 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<poly::Variable> 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
+++ /dev/null
-/******************************************************************************
- * 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 <poly/polyxx.h>
-
-#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<poly::Variable> 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<poly_utils::VariableInformation> collectInformation(
- const Constraints::ConstraintVector& polys, bool with_totals = false);
-
-} // namespace cad
-} // namespace nl
-} // namespace arith
-} // namespace theory
-} // namespace cvc5
-
-#endif
-
-#endif
+++ /dev/null
-/******************************************************************************
- * 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<Node>& 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<Node> 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<Node>& 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
+++ /dev/null
-/******************************************************************************
- * 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 <vector>
-
-#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<Node>& 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<Node>& 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 */
--- /dev/null
+/******************************************************************************
+ * 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 <typename T>
+std::ostream& operator<<(std::ostream& os, const std::vector<T>& 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<poly::Variable>& 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<poly::Variable>& CDCAC::getVariableOrdering() const
+{
+ return d_variableOrdering;
+}
+
+std::vector<CACInterval> CDCAC::getUnsatIntervals(std::size_t cur_variable)
+{
+ std::vector<CACInterval> 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<poly::Interval> 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<CACInterval>& 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<Node> 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<bool>());
+ 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<CACInterval>& 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<poly::Value> 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<CACInterval> CDCAC::getUnsatCoverImpl(std::size_t curVariable,
+ bool returnFirstInterval)
+{
+ Trace("cdcac") << "Looking for unsat cover for "
+ << d_variableOrdering[curVariable] << std::endl;
+ std::vector<CACInterval> 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<CACInterval> 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<Node>& 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<CACInterval>& 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<poly::Value> 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
--- /dev/null
+/******************************************************************************
+ * 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 <poly/polyxx.h>
+
+#include <vector>
+
+#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<poly::Variable>& 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<poly::Variable>& 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<CACInterval> 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<CACInterval>& 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<CACInterval>& 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<CACInterval> 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<CACInterval> 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<Node>& 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<CACInterval>& 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<poly::Value> 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<poly::Variable> d_variableOrdering;
+
+ /** The object computing the variable ordering. */
+ VariableOrdering d_varOrder;
+
+ /** The linear assignment used as an initial guess. */
+ std::vector<poly::Value> d_initialAssignment;
+
+ /** The proof generator */
+ std::unique_ptr<CoveringsProofGenerator> d_proof;
+
+ /** The next interval id */
+ size_t d_nextIntervalId = 1;
+};
+
+} // namespace coverings
+} // namespace nl
+} // namespace arith
+} // namespace theory
+} // namespace cvc5
+
+#endif
+
+#endif
--- /dev/null
+/******************************************************************************
+ * 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 <optional>
+
+#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<bool> 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<CACInterval>& 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<CACInterval>& 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<Node> collectConstraints(const std::vector<CACInterval>& intervals)
+{
+ std::vector<Node> 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<CACInterval>& 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<poly::Polynomial, poly::Polynomial> 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
--- /dev/null
+/******************************************************************************
+ * 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 <poly/polyxx.h>
+
+#include <vector>
+
+#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<Node> 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<CACInterval>& 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<CACInterval>& intervals);
+
+/**
+ * Collect all origins from the list of intervals to construct the origins for a
+ * whole covering.
+ */
+std::vector<Node> collectConstraints(const std::vector<CACInterval>& 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<CACInterval>& 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
--- /dev/null
+/******************************************************************************
+ * 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 <algorithm>
+
+#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<poly::Polynomial, poly::SignCondition, Node>;
+ 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
--- /dev/null
+/******************************************************************************
+ * 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 <poly/polyxx.h>
+
+#include <tuple>
+#include <vector>
+
+#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<poly::Polynomial, poly::SignCondition, Node>;
+ using ConstraintVector = std::vector<Constraint>;
+
+ 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
--- /dev/null
+#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 <CoCoA/library.H>
+
+#include <optional>
+
+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]/<p_0> and recursively extend it with a_1, etc, in the same way. Doing
+ * this recursive construction naively fails: (Q[x_0]/<p_0>)[x_1]/<p_1> is not
+ * necessarily a proper field as p_1 (though a minimal polynomial in Q[x_1]) may
+ * factor over Q[x_0]/<p_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]/<p_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]/<p_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]/<p_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<poly::Variable, CoCoA::RingElem> 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<std::pair<long, size_t>, 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<CoCoA::RingElem> d_p;
+
+ /**
+ * The sequence of extension fields.
+ * K_0 = Q, K_{i+1} = K_i[x_i]/<p_i>
+ * Every K_i is a field.
+ */
+ std::vector<CoCoA::ring> 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<CoCoA::ring> 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<CoCoA::ring> d_J;
+
+ /**
+ * Custom homomorphism from R_i to J_i. PolyAlgebraHom with
+ * Indets(R_i) = (x_i) --> (x_i)
+ */
+ std::vector<CoCoA::RingHom> 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<CoCoA::RingHom> 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<CoCoA::RingElem> 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<poly::Variable> 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<std::optional<CoCoA::RingElem>> 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<poly::Integer> 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<long> 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/<p_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<CoCoA::symbol> 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<CoCoA::RingElem> 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<CoCoA::RingElem> 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<CoCoAPolyConstructor*>(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<long> 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<poly::Polynomial> reduce(const poly::Polynomial& qpoly) const
+ {
+ d_stats.d_evaluations++;
+ std::vector<poly::Polynomial> 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<CoCoA::RingElem> 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<LazardEvaluationState>())
+{
+}
+
+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<CoCoA::BigRat> 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<poly::Polynomial> 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<poly::Value> LazardEvaluation::isolateRealRoots(
+ const poly::Polynomial& q) const
+{
+ poly::Assignment a;
+ std::vector<poly::Value> 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<poly::Value> 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<poly::Interval> LazardEvaluation::infeasibleRegions(
+ const poly::Polynomial& q, poly::SignCondition sc) const
+{
+ std::vector<poly::Value> 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<poly::Interval> 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<poly::Interval> 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<LazardEvaluationState>())
+{
+}
+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<poly::Polynomial> LazardEvaluation::reducePolynomial(
+ const poly::Polynomial& p) const
+{
+ return {p};
+}
+
+std::vector<poly::Value> 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<poly::Interval> 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
--- /dev/null
+/******************************************************************************
+ * 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 <poly/polyxx.h>
+
+#include <memory>
+
+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]/<p_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<poly::Polynomial> reducePolynomial(
+ const poly::Polynomial& q) const;
+
+ /**
+ * Isolates the real roots of the given polynomials.
+ */
+ std::vector<poly::Value> 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<poly::Interval> infeasibleRegions(const poly::Polynomial& q,
+ poly::SignCondition sc) const;
+
+ private:
+ std::unique_ptr<LazardEvaluationState> d_state;
+};
+
+} // namespace cvc5::theory::arith::nl::coverings
+
+#endif
+#endif
--- /dev/null
+/******************************************************************************
+ * 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<poly::Polynomial>::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<Polynomial>& 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
--- /dev/null
+/******************************************************************************
+ * 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 <poly/polyxx.h>
+
+#include <vector>
+
+namespace cvc5 {
+namespace theory {
+namespace arith {
+namespace nl {
+namespace coverings {
+
+/**
+ * A simple wrapper around std::vector<poly::Polynomial> that ensures that all
+ * polynomials are properly factorized and pruned when added to the list.
+ */
+class PolyVector : public std::vector<poly::Polynomial>
+{
+ 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<poly::Polynomial> 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<poly::Polynomial>& polys);
+
+} // namespace coverings
+} // namespace nl
+} // namespace arith
+} // namespace theory
+} // namespace cvc5
+
+#endif
+
+#endif
--- /dev/null
+/******************************************************************************
+ * 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<Node>& children,
+ const std::vector<Node>& 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
--- /dev/null
+/******************************************************************************
+ * 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<Node>& children,
+ const std::vector<Node>& args) override;
+};
+
+} // namespace coverings
+} // namespace nl
+} // namespace arith
+} // namespace theory
+} // namespace cvc5
+
+#endif /* CVC5__THEORY__STRINGS__PROOF_CHECKER_H */
--- /dev/null
+/******************************************************************************
+ * 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<std::size_t, std::size_t> getRootIDs(
+ const std::vector<poly::Value>& 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>(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<bool>(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<Node>& 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<Node> 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<Node> 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<Node> 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
--- /dev/null
+/******************************************************************************
+ * 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 <poly/polyxx.h>
+
+#include <vector>
+
+#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<Node>& 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 <typename F>
+ 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<Node> 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<LazyTreeProofGenerator> 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
--- /dev/null
+/******************************************************************************
+ * 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<poly_utils::VariableInformation> collectInformation(
+ const Constraints::ConstraintVector& polys, bool with_totals)
+{
+ poly::VariableCollector vc;
+ for (const auto& c : polys)
+ {
+ vc(std::get<0>(c));
+ }
+ std::vector<poly_utils::VariableInformation> 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<poly::Variable> getVariables(
+ const std::vector<poly_utils::VariableInformation>& vi)
+{
+ std::vector<poly::Variable> res;
+ for (const auto& v : vi)
+ {
+ res.emplace_back(v.var);
+ }
+ return res;
+}
+
+std::vector<poly::Variable> 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<poly::Variable> 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<poly::Variable> 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<poly::Variable> 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
--- /dev/null
+/******************************************************************************
+ * 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 <poly/polyxx.h>
+
+#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<poly::Variable> 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<poly_utils::VariableInformation> collectInformation(
+ const Constraints::ConstraintVector& polys, bool with_totals = false);
+
+} // namespace coverings
+} // namespace nl
+} // namespace arith
+} // namespace theory
+} // namespace cvc5
+
+#endif
+
+#endif
--- /dev/null
+/******************************************************************************
+ * 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<Node>& 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<Node> 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<Node>& 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
--- /dev/null
+/******************************************************************************
+ * 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 <vector>
+
+#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<Node>& 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<Node>& 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 */
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)
return false;
}
}
- if (options().arith.nlCad)
+ if (options().arith.nlCov)
{
- d_cadSlv.constructModelIfAvailable(passertions);
+ d_covSlv.constructModelIfAvailable(passertions);
}
Trace("nl-ext-cm") << "-----" << std::endl;
{
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;
#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"
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
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,
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)
{
}
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,
}
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(
* 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.
*/
{
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";
}
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);
/** 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,
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";
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,
-; COMMAND-LINE: --nl-ext=none --nl-cad
+; COMMAND-LINE: --nl-ext=none --nl-cov
; REQUIRES: poly
; EXPECT: unsat
(set-logic QF_NRA)
-; COMMAND-LINE: --nl-ext=none --nl-cad
+; COMMAND-LINE: --nl-ext=none --nl-cov
; REQUIRES: poly
; EXPECT: sat
(set-logic QF_NRA)
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();
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)
+++ /dev/null
-/******************************************************************************
- * 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 <poly/polyxx.h>
-
-#include <iostream>
-#include <memory>
-#include <vector>
-
-#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<TypeNode> d_realType;
- std::unique_ptr<TypeNode> d_intType;
- const Rational d_zero = 0;
- const Rational d_one = 1;
- std::map<int, Node> d_dummyCache;
-};
-
-poly::AlgebraicNumber get_ran(std::initializer_list<long> 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<Node>{
- 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<poly::Polynomial>{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<Node> 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<Node>& 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<Node> mis{dummy(1), dummy(3), dummy(4), dummy(5)};
- LazyTreeProofGenerator* pg = dynamic_cast<LazyTreeProofGenerator*>(cac.closeProof(mis));
- EXPECT_TRUE(pg != nullptr);
-}
-
-TEST_F(TestTheoryWhiteArithCAD, test_delta_one)
-{
- std::vector<Node> 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<Node> 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<Rational>({-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
--- /dev/null
+/******************************************************************************
+ * 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 <poly/polyxx.h>
+
+#include <iostream>
+#include <memory>
+#include <vector>
+
+#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<TypeNode> d_realType;
+ std::unique_ptr<TypeNode> d_intType;
+ const Rational d_zero = 0;
+ const Rational d_one = 1;
+ std::map<int, Node> d_dummyCache;
+};
+
+poly::AlgebraicNumber get_ran(std::initializer_list<long> 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<Node>{
+ 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<poly::Polynomial>{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<Node> 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<Node>& 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<Node> mis{dummy(1), dummy(3), dummy(4), dummy(5)};
+ LazyTreeProofGenerator* pg = dynamic_cast<LazyTreeProofGenerator*>(cac.closeProof(mis));
+ EXPECT_TRUE(pg != nullptr);
+}
+
+TEST_F(TestTheoryWhiteArithCoverings, test_delta_one)
+{
+ std::vector<Node> 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<Node> 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<Rational>({-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