Rename cad to coverings (#8187)
authorGereon Kremer <gereon.kremer@cs.rwth-aachen.de>
Tue, 1 Mar 2022 00:37:13 +0000 (01:37 +0100)
committerGitHub <noreply@github.com>
Tue, 1 Mar 2022 00:37:13 +0000 (00:37 +0000)
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.

56 files changed:
src/CMakeLists.txt
src/options/arith_options.toml
src/proof/lazy_tree_proof_generator.h
src/proof/proof_rule.cpp
src/proof/proof_rule.h
src/smt/set_defaults.cpp
src/theory/arith/nl/cad/cdcac.cpp [deleted file]
src/theory/arith/nl/cad/cdcac.h [deleted file]
src/theory/arith/nl/cad/cdcac_utils.cpp [deleted file]
src/theory/arith/nl/cad/cdcac_utils.h [deleted file]
src/theory/arith/nl/cad/constraints.cpp [deleted file]
src/theory/arith/nl/cad/constraints.h [deleted file]
src/theory/arith/nl/cad/lazard_evaluation.cpp [deleted file]
src/theory/arith/nl/cad/lazard_evaluation.h [deleted file]
src/theory/arith/nl/cad/projections.cpp [deleted file]
src/theory/arith/nl/cad/projections.h [deleted file]
src/theory/arith/nl/cad/proof_checker.cpp [deleted file]
src/theory/arith/nl/cad/proof_checker.h [deleted file]
src/theory/arith/nl/cad/proof_generator.cpp [deleted file]
src/theory/arith/nl/cad/proof_generator.h [deleted file]
src/theory/arith/nl/cad/variable_ordering.cpp [deleted file]
src/theory/arith/nl/cad/variable_ordering.h [deleted file]
src/theory/arith/nl/cad_solver.cpp [deleted file]
src/theory/arith/nl/cad_solver.h [deleted file]
src/theory/arith/nl/coverings/cdcac.cpp [new file with mode: 0644]
src/theory/arith/nl/coverings/cdcac.h [new file with mode: 0644]
src/theory/arith/nl/coverings/cdcac_utils.cpp [new file with mode: 0644]
src/theory/arith/nl/coverings/cdcac_utils.h [new file with mode: 0644]
src/theory/arith/nl/coverings/constraints.cpp [new file with mode: 0644]
src/theory/arith/nl/coverings/constraints.h [new file with mode: 0644]
src/theory/arith/nl/coverings/lazard_evaluation.cpp [new file with mode: 0644]
src/theory/arith/nl/coverings/lazard_evaluation.h [new file with mode: 0644]
src/theory/arith/nl/coverings/projections.cpp [new file with mode: 0644]
src/theory/arith/nl/coverings/projections.h [new file with mode: 0644]
src/theory/arith/nl/coverings/proof_checker.cpp [new file with mode: 0644]
src/theory/arith/nl/coverings/proof_checker.h [new file with mode: 0644]
src/theory/arith/nl/coverings/proof_generator.cpp [new file with mode: 0644]
src/theory/arith/nl/coverings/proof_generator.h [new file with mode: 0644]
src/theory/arith/nl/coverings/variable_ordering.cpp [new file with mode: 0644]
src/theory/arith/nl/coverings/variable_ordering.h [new file with mode: 0644]
src/theory/arith/nl/coverings_solver.cpp [new file with mode: 0644]
src/theory/arith/nl/coverings_solver.h [new file with mode: 0644]
src/theory/arith/nl/nonlinear_extension.cpp
src/theory/arith/nl/nonlinear_extension.h
src/theory/arith/nl/poly_conversion.cpp
src/theory/arith/nl/poly_conversion.h
src/theory/arith/nl/strategy.cpp
src/theory/arith/nl/strategy.h
src/theory/inference_id.cpp
src/theory/inference_id.h
test/regress/regress0/nl/issue5726-downpolys.smt2
test/regress/regress0/nl/issue5726-sqfactor.smt2
test/unit/api/cpp/theory_arith_nl_black.cpp
test/unit/theory/CMakeLists.txt
test/unit/theory/theory_arith_cad_white.cpp [deleted file]
test/unit/theory/theory_arith_coverings_white.cpp [new file with mode: 0644]

index ac8b642930ab695440560590e022d77c23aaf3e7..e29b234bacf38edfe57dd8145600aa3130ca604a 100644 (file)
@@ -410,24 +410,24 @@ libcvc5_add_sources(
   theory/arith/linear_equality.h
   theory/arith/matrix.cpp
   theory/arith/matrix.h
-  theory/arith/nl/cad_solver.cpp
-  theory/arith/nl/cad_solver.h
-  theory/arith/nl/cad/cdcac.cpp
-  theory/arith/nl/cad/cdcac.h
-  theory/arith/nl/cad/cdcac_utils.cpp
-  theory/arith/nl/cad/cdcac_utils.h
-  theory/arith/nl/cad/constraints.cpp
-  theory/arith/nl/cad/constraints.h
-  theory/arith/nl/cad/lazard_evaluation.cpp
-  theory/arith/nl/cad/lazard_evaluation.h
-  theory/arith/nl/cad/projections.cpp
-  theory/arith/nl/cad/projections.h
-  theory/arith/nl/cad/proof_checker.cpp
-  theory/arith/nl/cad/proof_checker.h
-  theory/arith/nl/cad/proof_generator.cpp
-  theory/arith/nl/cad/proof_generator.h
-  theory/arith/nl/cad/variable_ordering.cpp
-  theory/arith/nl/cad/variable_ordering.h
+  theory/arith/nl/coverings_solver.cpp
+  theory/arith/nl/coverings_solver.h
+  theory/arith/nl/coverings/cdcac.cpp
+  theory/arith/nl/coverings/cdcac.h
+  theory/arith/nl/coverings/cdcac_utils.cpp
+  theory/arith/nl/coverings/cdcac_utils.h
+  theory/arith/nl/coverings/constraints.cpp
+  theory/arith/nl/coverings/constraints.h
+  theory/arith/nl/coverings/lazard_evaluation.cpp
+  theory/arith/nl/coverings/lazard_evaluation.h
+  theory/arith/nl/coverings/projections.cpp
+  theory/arith/nl/coverings/projections.h
+  theory/arith/nl/coverings/proof_checker.cpp
+  theory/arith/nl/coverings/proof_checker.h
+  theory/arith/nl/coverings/proof_generator.cpp
+  theory/arith/nl/coverings/proof_generator.h
+  theory/arith/nl/coverings/variable_ordering.cpp
+  theory/arith/nl/coverings/variable_ordering.h
   theory/arith/nl/equality_substitution.cpp
   theory/arith/nl/equality_substitution.h
   theory/arith/nl/ext/constraint.cpp
index 5ec842f1a3384cc225a73c4647d3edbbd82badd4..d76f6c97368f52ee8942178014004a7a3b14f251 100644 (file)
@@ -492,34 +492,34 @@ name   = "Arithmetic Theory"
   help       = "whether to use simple rounding, similar to a unit-cube test, for integers"
 
 [[option]]
-  name       = "nlCad"
+  name       = "nlCov"
   category   = "regular"
-  long       = "nl-cad"
+  long       = "nl-cov"
   type       = "bool"
   default    = "false"
   help       = "whether to use the cylindrical algebraic coverings solver for non-linear arithmetic"
 
 [[option]]
-  name       = "nlCadVarElim"
+  name       = "nlCovVarElim"
   category   = "regular"
-  long       = "nl-cad-var-elim"
+  long       = "nl-cov-var-elim"
   type       = "bool"
   default    = "false"
   help       = "whether to eliminate variables using equalities before going into the cylindrical algebraic coverings solver"
 
 [[option]]
-  name       = "nlCadPrune"
+  name       = "nlCovPrune"
   category   = "regular"
-  long       = "nl-cad-prune"
+  long       = "nl-cov-prune"
   type       = "bool"
   default    = "false"
   help       = "whether to prune intervals more agressively"
 
 [[option]]
-  name       = "nlCadLinearModel"
+  name       = "nlCovLinearModel"
   category   = "regular"
-  long       = "nl-cad-linear-model=MODE"
-  type       = "NlCadLinearModelMode"
+  long       = "nl-cov-linear-model=MODE"
+  type       = "nlCovLinearModelMode"
   default    = "NONE"
   help       = "whether to use the linear model as initial guess for the cylindrical algebraic coverings solver"
   help_mode  = "Modes for the usage of the linear model in non-linear arithmetic."
@@ -534,13 +534,13 @@ name   = "Arithmetic Theory"
   help       = "Use linear model to seed nonlinear model whenever possible"
 
 [[option]]
-  name       = "nlCadProjection"
+  name       = "nlCovProjection"
   category   = "expert"
-  long       = "nl-cad-proj=MODE"
-  type       = "NlCadProjectionMode"
+  long       = "nl-cov-proj=MODE"
+  type       = "nlCovProjectionMode"
   default    = "MCCALLUM"
-  help       = "choose the CAD projection operator"
-  help_mode  = "Modes for the CAD projection operator in non-linear arithmetic."
+  help       = "choose the Coverings projection operator"
+  help_mode  = "Modes for the Coverings projection operator in non-linear arithmetic."
 [[option.mode.MCCALLUM]]
   name = "mccallum"
   help = "McCallum's projection operator."
@@ -552,13 +552,13 @@ name   = "Arithmetic Theory"
   help = "A modification of Lazard's projection operator."
 
 [[option]]
-  name       = "nlCadLifting"
+  name       = "nlCovLifting"
   category   = "expert"
-  long       = "nl-cad-lift=MODE"
-  type       = "NlCadLiftingMode"
+  long       = "nl-cov-lift=MODE"
+  type       = "nlCovLiftingMode"
   default    = "REGULAR"
-  help       = "choose the CAD lifting mode"
-  help_mode  = "Modes for the CAD lifting in non-linear arithmetic."
+  help       = "choose the Coverings lifting mode"
+  help_mode  = "Modes for the Coverings lifting in non-linear arithmetic."
 [[option.mode.REGULAR]]
   name = "regular"
   help = "Regular lifting."
index 1863c739fd50d4f72aa41aeef021c6ec44e5c5b9..4f603fc2d632e303bba9f16fa302ff0b7c60a2ad 100644 (file)
@@ -71,13 +71,13 @@ struct TreeProofNode
  *
  * Consider the example  x*x<1 and x>2  and the intended proof
  *  (SCOPE
- *    (ARITH_NL_CAD_SPLIT
+ *    (ARITH_NL_COVERING_RECURSIVE
  *      (SCOPE
- *        (ARITH_NL_CAD_DIRECT  (x<=2  and  x>2) ==> false)
+ *        (ARITH_NL_COVERING_DIRECT  (x<=2  and  x>2) ==> false)
  *        :args [x<=2]
  *      )
  *      (SCOPE
- *        (ARITH_NL_CAD_DIRECT  (x>=1  and  x*x<1) ==> false)
+ *        (ARITH_NL_COVERING_DIRECT  (x>=1  and  x*x<1) ==> false)
  *        :args [x>=1]
  *      )
  *    )
@@ -104,7 +104,7 @@ struct TreeProofNode
  *  openChild();
  *  setCurrent(SCOPE, {}, {}, false);
  *  openChild();
- *  setCurrent(CAD_DIRECT, {x>2}, {}, false);
+ *  setCurrent(ARITH_NL_COVERING_DIRECT, {x>2}, {}, false);
  *  closeChild();
  *  getCurrent().args = {x<=2};
  *  closeChild();
@@ -112,12 +112,12 @@ struct TreeProofNode
  *  openChild();
  *  setCurrent(SCOPE, {}, {}, false);
  *  openChild();
- *  setCurrent(CAD_DIRECT, {x*x<1}, {}, false);
+ *  setCurrent(ARITH_NL_COVERING_DIRECT, {x*x<1}, {}, false);
  *  closeChild();
  *  getCurrent().args = {x>=1};
  *  closeChild();
  *  // Finish split
- *  setCurrent(CAD_SPLIT, {}, {}, false);
+ *  setCurrent(ARITH_NL_COVERING_RECURSIVE, {}, {}, false);
  *  closeChild();
  *  closeChild();
  *
index 9bd7141611aaac973d25e215c913d1da61c8edfb..fd143350cfd059c65c37ee10a8847ca9bc112612 100644 (file)
@@ -200,8 +200,8 @@ const char* toString(PfRule id)
       return "ARITH_TRANS_SINE_APPROX_BELOW_NEG";
     case PfRule::ARITH_TRANS_SINE_APPROX_BELOW_POS:
       return "ARITH_TRANS_SINE_APPROX_BELOW_POS";
-    case PfRule::ARITH_NL_CAD_DIRECT: return "ARITH_NL_CAD_DIRECT";
-    case PfRule::ARITH_NL_CAD_RECURSIVE: return "ARITH_NL_CAD_RECURSIVE";
+    case PfRule::ARITH_NL_COVERING_DIRECT: return "ARITH_NL_COVERING_DIRECT";
+    case PfRule::ARITH_NL_COVERING_RECURSIVE: return "ARITH_NL_COVERING_RECURSIVE";
     //================================================= External rules
     case PfRule::LFSC_RULE: return "LFSC_RULE";
     case PfRule::ALETHE_RULE: return "ALETHE_RULE";
index eaa92c02c40cde85f4f4b5e81c9edefa74419843..5028cba1b3dbb60f922f3d107477f4fc21cb8393 100644 (file)
@@ -1379,7 +1379,7 @@ enum class PfRule : uint32_t
   // secant of p from l to u.
   ARITH_TRANS_SINE_APPROX_BELOW_POS,
 
-  // ================ CAD Lemmas
+  // ================ Coverings Lemmas
   // We use IRP for IndexedRootPredicate.
   //
   // A formula "Interval" describes that a variable (xn is none is given) is
@@ -1396,7 +1396,7 @@ enum class PfRule : uint32_t
   // A formula "Covering" is a set of Intervals, implying that xn can be in
   // neither of these intervals. To be a covering (of the real line), the union
   // of these intervals should be the real numbers.
-  // ======== CAD direct conflict
+  // ======== Coverings direct conflict
   // Children (Cell, A)
   // ---------------------
   // Conclusion: (false)
@@ -1404,15 +1404,15 @@ enum class PfRule : uint32_t
   // over a Cell (in variables x1...xn). It derives that A evaluates to false
   // over the Cell. In the actual algorithm, it means that xn can not be in the
   // topmost interval of the Cell.
-  ARITH_NL_CAD_DIRECT,
-  // ======== CAD recursive interval
+  ARITH_NL_COVERING_DIRECT,
+  // ======== Coverings recursive interval
   // Children (Cell, Covering)
   // ---------------------
   // Conclusion: (false)
   // A recursive interval is generated from a Covering (for xn) over a Cell
   // (in variables x1...xn-1). It generates the conclusion that no xn exists
   // that extends the Cell and satisfies all assumptions.
-  ARITH_NL_CAD_RECURSIVE,
+  ARITH_NL_COVERING_RECURSIVE,
 
   //================================================ Place holder for Lfsc rules
   // ======== Lfsc rule
index c3008f2ddd786a34b16c5e0046b9edfc24d98890..bc5ab5f08dcb368e5c94487a6bacef8b3c38f889 100644 (file)
@@ -798,10 +798,10 @@ void SetDefaults::setDefaultsPost(const LogicInfo& logic, Options& opts) const
 #ifdef CVC5_USE_POLY
   if (logic == LogicInfo("QF_UFNRA"))
   {
-    if (!opts.arith.nlCad && !opts.arith.nlCadWasSetByUser)
+    if (!opts.arith.nlCov && !opts.arith.nlCovWasSetByUser)
     {
-      opts.arith.nlCad = true;
-      opts.arith.nlCadVarElim = true;
+      opts.arith.nlCov = true;
+      opts.arith.nlCovVarElim = true;
       if (!opts.arith.nlExtWasSetByUser)
       {
         opts.arith.nlExt = options::NlExtMode::LIGHT;
@@ -815,10 +815,10 @@ void SetDefaults::setDefaultsPost(const LogicInfo& logic, Options& opts) const
   else if (logic.isQuantified() && logic.isTheoryEnabled(theory::THEORY_ARITH)
            && logic.areRealsUsed() && !logic.areIntegersUsed())
   {
-    if (!opts.arith.nlCad && !opts.arith.nlCadWasSetByUser)
+    if (!opts.arith.nlCov && !opts.arith.nlCovWasSetByUser)
     {
-      opts.arith.nlCad = true;
-      opts.arith.nlCadVarElim = true;
+      opts.arith.nlCov = true;
+      opts.arith.nlCovVarElim = true;
       if (!opts.arith.nlExtWasSetByUser)
       {
         opts.arith.nlExt = options::NlExtMode::LIGHT;
@@ -826,18 +826,18 @@ void SetDefaults::setDefaultsPost(const LogicInfo& logic, Options& opts) const
     }
   }
 #else
-  if (opts.arith.nlCad)
+  if (opts.arith.nlCov)
   {
-    if (opts.arith.nlCadWasSetByUser)
+    if (opts.arith.nlCovWasSetByUser)
     {
       throw OptionException(
-          "Cannot use --nl-cad without configuring with --poly.");
+          "Cannot use --nl-cov without configuring with --poly.");
     }
     else
     {
-      verbose(1) << "Cannot use --nl-cad without configuring with --poly."
+      verbose(1) << "Cannot use --nl-cov without configuring with --poly."
                  << std::endl;
-      opts.arith.nlCad = false;
+      opts.arith.nlCov = false;
       opts.arith.nlExt = options::NlExtMode::FULL;
     }
   }
diff --git a/src/theory/arith/nl/cad/cdcac.cpp b/src/theory/arith/nl/cad/cdcac.cpp
deleted file mode 100644 (file)
index 9891447..0000000
+++ /dev/null
@@ -1,768 +0,0 @@
-/******************************************************************************
- * Top contributors (to current version):
- *   Gereon Kremer, Aina Niemetz
- *
- * This file is part of the cvc5 project.
- *
- * Copyright (c) 2009-2021 by the authors listed in the file AUTHORS
- * in the top-level source directory and their institutional affiliations.
- * All rights reserved.  See the file COPYING in the top-level source
- * directory for licensing information.
- * ****************************************************************************
- *
- * Implements the CDCAC approach as described in
- * https://arxiv.org/pdf/2003.05633.pdf.
- */
-
-#include "theory/arith/nl/cad/cdcac.h"
-
-#ifdef CVC5_POLY_IMP
-
-#include "options/arith_options.h"
-#include "theory/arith/nl/cad/lazard_evaluation.h"
-#include "theory/arith/nl/cad/projections.h"
-#include "theory/arith/nl/cad/variable_ordering.h"
-#include "theory/arith/nl/nl_model.h"
-#include "theory/rewriter.h"
-
-using namespace cvc5::kind;
-
-namespace std {
-/** Generic streaming operator for std::vector. */
-template <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
diff --git a/src/theory/arith/nl/cad/cdcac.h b/src/theory/arith/nl/cad/cdcac.h
deleted file mode 100644 (file)
index 8317c08..0000000
+++ /dev/null
@@ -1,246 +0,0 @@
-/******************************************************************************
- * Top contributors (to current version):
- *   Gereon Kremer
- *
- * This file is part of the cvc5 project.
- *
- * Copyright (c) 2009-2021 by the authors listed in the file AUTHORS
- * in the top-level source directory and their institutional affiliations.
- * All rights reserved.  See the file COPYING in the top-level source
- * directory for licensing information.
- * ****************************************************************************
- *
- * Implements the CDCAC approach as described in
- * https://arxiv.org/pdf/2003.05633.pdf.
- */
-
-#include "cvc5_private.h"
-
-#ifndef CVC5__THEORY__ARITH__NL__CAD__CDCAC_H
-#define CVC5__THEORY__ARITH__NL__CAD__CDCAC_H
-
-#ifdef CVC5_POLY_IMP
-
-#include <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
diff --git a/src/theory/arith/nl/cad/cdcac_utils.cpp b/src/theory/arith/nl/cad/cdcac_utils.cpp
deleted file mode 100644 (file)
index f5723a6..0000000
+++ /dev/null
@@ -1,466 +0,0 @@
-/******************************************************************************
- * Top contributors (to current version):
- *   Gereon Kremer
- *
- * This file is part of the cvc5 project.
- *
- * Copyright (c) 2009-2021 by the authors listed in the file AUTHORS
- * in the top-level source directory and their institutional affiliations.
- * All rights reserved.  See the file COPYING in the top-level source
- * directory for licensing information.
- * ****************************************************************************
- *
- * Implements utilities for cdcac.
- */
-
-#include "theory/arith/nl/cad/cdcac_utils.h"
-
-#ifdef CVC5_POLY_IMP
-
-#include <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
diff --git a/src/theory/arith/nl/cad/cdcac_utils.h b/src/theory/arith/nl/cad/cdcac_utils.h
deleted file mode 100644 (file)
index c53e8fb..0000000
+++ /dev/null
@@ -1,115 +0,0 @@
-/******************************************************************************
- * Top contributors (to current version):
- *   Gereon Kremer
- *
- * This file is part of the cvc5 project.
- *
- * Copyright (c) 2009-2021 by the authors listed in the file AUTHORS
- * in the top-level source directory and their institutional affiliations.
- * All rights reserved.  See the file COPYING in the top-level source
- * directory for licensing information.
- * ****************************************************************************
- *
- * Implements utilities for cdcac.
- */
-
-#include "cvc5_private.h"
-
-#ifndef CVC5__THEORY__ARITH__NL__CAD__CDCAC_UTILS_H
-#define CVC5__THEORY__ARITH__NL__CAD__CDCAC_UTILS_H
-
-#ifdef CVC5_POLY_IMP
-
-#include <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
diff --git a/src/theory/arith/nl/cad/constraints.cpp b/src/theory/arith/nl/cad/constraints.cpp
deleted file mode 100644 (file)
index 628859a..0000000
+++ /dev/null
@@ -1,83 +0,0 @@
-/******************************************************************************
- * Top contributors (to current version):
- *   Gereon Kremer
- *
- * This file is part of the cvc5 project.
- *
- * Copyright (c) 2009-2021 by the authors listed in the file AUTHORS
- * in the top-level source directory and their institutional affiliations.
- * All rights reserved.  See the file COPYING in the top-level source
- * directory for licensing information.
- * ****************************************************************************
- *
- * Implements a container for CAD constraints.
- */
-
-#include "theory/arith/nl/cad/constraints.h"
-
-#ifdef CVC5_POLY_IMP
-
-#include <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
diff --git a/src/theory/arith/nl/cad/constraints.h b/src/theory/arith/nl/cad/constraints.h
deleted file mode 100644 (file)
index 4321800..0000000
+++ /dev/null
@@ -1,93 +0,0 @@
-/******************************************************************************
- * Top contributors (to current version):
- *   Gereon Kremer
- *
- * This file is part of the cvc5 project.
- *
- * Copyright (c) 2009-2021 by the authors listed in the file AUTHORS
- * in the top-level source directory and their institutional affiliations.
- * All rights reserved.  See the file COPYING in the top-level source
- * directory for licensing information.
- * ****************************************************************************
- *
- * Implements a container for CAD constraints.
- */
-
-#include "cvc5_private.h"
-
-#ifndef CVC5__THEORY__ARITH__NL__CAD__CONSTRAINTS_H
-#define CVC5__THEORY__ARITH__NL__CAD__CONSTRAINTS_H
-
-#ifdef CVC5_POLY_IMP
-
-#include <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
diff --git a/src/theory/arith/nl/cad/lazard_evaluation.cpp b/src/theory/arith/nl/cad/lazard_evaluation.cpp
deleted file mode 100644 (file)
index 91d775d..0000000
+++ /dev/null
@@ -1,995 +0,0 @@
-#include "theory/arith/nl/cad/lazard_evaluation.h"
-
-#ifdef CVC5_POLY_IMP
-
-#include "base/check.h"
-#include "base/output.h"
-#include "smt/smt_statistics_registry.h"
-#include "util/statistics_stats.h"
-
-#ifdef CVC5_USE_COCOA
-
-#include <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
diff --git a/src/theory/arith/nl/cad/lazard_evaluation.h b/src/theory/arith/nl/cad/lazard_evaluation.h
deleted file mode 100644 (file)
index 2afccb4..0000000
+++ /dev/null
@@ -1,117 +0,0 @@
-/******************************************************************************
- * Top contributors (to current version):
- *   Gereon Kremer
- *
- * This file is part of the cvc5 project.
- *
- * Copyright (c) 2009-2021 by the authors listed in the file AUTHORS
- * in the top-level source directory and their institutional affiliations.
- * All rights reserved.  See the file COPYING in the top-level source
- * directory for licensing information.
- * ****************************************************************************
- *
- * Implements the CDCAC approach as described in
- * https://arxiv.org/pdf/2003.05633.pdf.
- */
-
-#include "cvc5_private.h"
-
-#ifndef CVC5__THEORY__ARITH__NL__CAD__LAZARD_EVALUATION_H
-#define CVC5__THEORY__ARITH__NL__CAD__LAZARD_EVALUATION_H
-
-#ifdef CVC5_POLY_IMP
-
-#include <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
diff --git a/src/theory/arith/nl/cad/projections.cpp b/src/theory/arith/nl/cad/projections.cpp
deleted file mode 100644 (file)
index 896d8da..0000000
+++ /dev/null
@@ -1,110 +0,0 @@
-/******************************************************************************
- * Top contributors (to current version):
- *   Gereon Kremer
- *
- * This file is part of the cvc5 project.
- *
- * Copyright (c) 2009-2021 by the authors listed in the file AUTHORS
- * in the top-level source directory and their institutional affiliations.
- * All rights reserved.  See the file COPYING in the top-level source
- * directory for licensing information.
- * ****************************************************************************
- *
- * Implements utilities for CAD projection operators.
- */
-
-#include "theory/arith/nl/cad/projections.h"
-
-#ifdef CVC5_POLY_IMP
-
-#include "base/check.h"
-
-namespace cvc5 {
-namespace theory {
-namespace arith {
-namespace nl {
-namespace cad {
-
-using namespace poly;
-
-void PolyVector::add(const poly::Polynomial& poly, bool assertMain)
-{
-  for (const auto& p : poly::square_free_factors(poly))
-  {
-    if (poly::is_constant(p)) continue;
-    if (assertMain)
-    {
-      Assert(main_variable(poly) == main_variable(p));
-    }
-    std::vector<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
diff --git a/src/theory/arith/nl/cad/projections.h b/src/theory/arith/nl/cad/projections.h
deleted file mode 100644 (file)
index 129c351..0000000
+++ /dev/null
@@ -1,82 +0,0 @@
-/******************************************************************************
- * Top contributors (to current version):
- *   Gereon Kremer
- *
- * This file is part of the cvc5 project.
- *
- * Copyright (c) 2009-2021 by the authors listed in the file AUTHORS
- * in the top-level source directory and their institutional affiliations.
- * All rights reserved.  See the file COPYING in the top-level source
- * directory for licensing information.
- * ****************************************************************************
- *
- * Implements utilities for CAD projection operators.
- */
-
-#include "cvc5_private.h"
-
-#ifndef CVC5__THEORY__ARITH__NL__CAD_PROJECTIONS_H
-#define CVC5__THEORY__ARITH__NL__CAD_PROJECTIONS_H
-
-#ifdef CVC5_USE_POLY
-
-#include <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
diff --git a/src/theory/arith/nl/cad/proof_checker.cpp b/src/theory/arith/nl/cad/proof_checker.cpp
deleted file mode 100644 (file)
index 562b577..0000000
+++ /dev/null
@@ -1,62 +0,0 @@
-/******************************************************************************
- * Top contributors (to current version):
- *   Gereon Kremer, Aina Niemetz
- *
- * This file is part of the cvc5 project.
- *
- * Copyright (c) 2009-2021 by the authors listed in the file AUTHORS
- * in the top-level source directory and their institutional affiliations.
- * All rights reserved.  See the file COPYING in the top-level source
- * directory for licensing information.
- * ****************************************************************************
- *
- * Implementation of CAD proof checker.
- */
-
-#include "theory/arith/nl/cad/proof_checker.h"
-
-#include "expr/sequence.h"
-#include "theory/rewriter.h"
-
-using namespace cvc5::kind;
-
-namespace cvc5 {
-namespace theory {
-namespace arith {
-namespace nl {
-namespace cad {
-
-void CADProofRuleChecker::registerTo(ProofChecker* pc)
-{
-  // trusted rules
-  pc->registerTrustedChecker(PfRule::ARITH_NL_CAD_DIRECT, this, 2);
-  pc->registerTrustedChecker(PfRule::ARITH_NL_CAD_RECURSIVE, this, 2);
-}
-
-Node CADProofRuleChecker::checkInternal(PfRule id,
-                                        const std::vector<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
diff --git a/src/theory/arith/nl/cad/proof_checker.h b/src/theory/arith/nl/cad/proof_checker.h
deleted file mode 100644 (file)
index 8cc544f..0000000
+++ /dev/null
@@ -1,59 +0,0 @@
-/******************************************************************************
- * Top contributors (to current version):
- *   Gereon Kremer
- *
- * This file is part of the cvc5 project.
- *
- * Copyright (c) 2009-2021 by the authors listed in the file AUTHORS
- * in the top-level source directory and their institutional affiliations.
- * All rights reserved.  See the file COPYING in the top-level source
- * directory for licensing information.
- * ****************************************************************************
- *
- * CAD proof checker utility.
- */
-
-#include "cvc5_private.h"
-
-#ifndef CVC5__THEORY__ARITH__NL__CAD__PROOF_CHECKER_H
-#define CVC5__THEORY__ARITH__NL__CAD__PROOF_CHECKER_H
-
-#include "expr/node.h"
-#include "proof/proof_checker.h"
-
-namespace cvc5 {
-namespace theory {
-namespace arith {
-namespace nl {
-namespace cad {
-
-/**
- * A checker for CAD proofs
- *
- * This proof checker takes care of the two CAD proof rules ARITH_NL_CAD_DIRECT
- * and ARITH_NL_CAD_RECURSIVE. It does not do any actual proof checking yet, but
- * considers them to be trusted rules.
- */
-class CADProofRuleChecker : public ProofRuleChecker
-{
- public:
-  CADProofRuleChecker() {}
-  ~CADProofRuleChecker() {}
-
-  /** Register all rules owned by this rule checker in pc. */
-  void registerTo(ProofChecker* pc) override;
-
- protected:
-  /** Return the conclusion of the given proof step, or null if it is invalid */
-  Node checkInternal(PfRule id,
-                     const std::vector<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 */
diff --git a/src/theory/arith/nl/cad/proof_generator.cpp b/src/theory/arith/nl/cad/proof_generator.cpp
deleted file mode 100644 (file)
index 1e3c522..0000000
+++ /dev/null
@@ -1,247 +0,0 @@
-/******************************************************************************
- * Top contributors (to current version):
- *   Gereon Kremer
- *
- * This file is part of the cvc5 project.
- *
- * Copyright (c) 2009-2021 by the authors listed in the file AUTHORS
- * in the top-level source directory and their institutional affiliations.
- * All rights reserved.  See the file COPYING in the top-level source
- * directory for licensing information.
- * ****************************************************************************
- *
- * Implementation of CAD proof generator.
- */
-
-#include "theory/arith/nl/cad/proof_generator.h"
-
-#ifdef CVC5_POLY_IMP
-
-#include "proof/lazy_tree_proof_generator.h"
-#include "theory/arith/nl/poly_conversion.h"
-#include "util/indexed_root_predicate.h"
-
-using namespace cvc5::kind;
-
-namespace cvc5 {
-namespace theory {
-namespace arith {
-namespace nl {
-namespace cad {
-
-namespace {
-/**
- * Retrieves the root indices of the sign-invariant region of v.
- *
- * We assume that roots holds a sorted list of roots from one polynomial.
- * If v is equal to one of these roots, we return (id,id) where id is the index
- * of this root within roots. Otherwise, we return the id of the largest root
- * below v and the id of the smallest root above v. To make sure a smaller root
- * and a larger root always exist, we implicitly extend the roots by -infty and
- * infty.
- *
- * ATTENTION: if we return id, the corresponding root is:
- *   - id = 0: -infty
- *   - 0 < id <= roots.size(): roots[id-1]
- *   - id = roots.size() + 1: infty
- */
-std::pair<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
diff --git a/src/theory/arith/nl/cad/proof_generator.h b/src/theory/arith/nl/cad/proof_generator.h
deleted file mode 100644 (file)
index 521d5aa..0000000
+++ /dev/null
@@ -1,159 +0,0 @@
-/******************************************************************************
- * Top contributors (to current version):
- *   Gereon Kremer
- *
- * This file is part of the cvc5 project.
- *
- * Copyright (c) 2009-2021 by the authors listed in the file AUTHORS
- * in the top-level source directory and their institutional affiliations.
- * All rights reserved.  See the file COPYING in the top-level source
- * directory for licensing information.
- * ****************************************************************************
- *
- * Implements the proof generator for CAD.
- */
-
-#include "cvc5_private.h"
-
-#ifndef CVC5__THEORY__ARITH__NL__CAD__PROOF_GENERATOR_H
-#define CVC5__THEORY__ARITH__NL__CAD__PROOF_GENERATOR_H
-
-#ifdef CVC5_POLY_IMP
-
-#include <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
diff --git a/src/theory/arith/nl/cad/variable_ordering.cpp b/src/theory/arith/nl/cad/variable_ordering.cpp
deleted file mode 100644 (file)
index daf3f48..0000000
+++ /dev/null
@@ -1,136 +0,0 @@
-/******************************************************************************
- * Top contributors (to current version):
- *   Gereon Kremer
- *
- * This file is part of the cvc5 project.
- *
- * Copyright (c) 2009-2021 by the authors listed in the file AUTHORS
- * in the top-level source directory and their institutional affiliations.
- * All rights reserved.  See the file COPYING in the top-level source
- * directory for licensing information.
- * ****************************************************************************
- *
- * Implements variable orderings tailored to CAD.
- */
-
-#include "theory/arith/nl/cad/variable_ordering.h"
-
-#ifdef CVC5_POLY_IMP
-
-#include "util/poly_util.h"
-
-namespace cvc5 {
-namespace theory {
-namespace arith {
-namespace nl {
-namespace cad {
-
-std::vector<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
diff --git a/src/theory/arith/nl/cad/variable_ordering.h b/src/theory/arith/nl/cad/variable_ordering.h
deleted file mode 100644 (file)
index 2de2620..0000000
+++ /dev/null
@@ -1,71 +0,0 @@
-/******************************************************************************
- * Top contributors (to current version):
- *   Gereon Kremer
- *
- * This file is part of the cvc5 project.
- *
- * Copyright (c) 2009-2021 by the authors listed in the file AUTHORS
- * in the top-level source directory and their institutional affiliations.
- * All rights reserved.  See the file COPYING in the top-level source
- * directory for licensing information.
- * ****************************************************************************
- *
- * Implements variable orderings tailored to CAD.
- */
-
-#include "cvc5_private.h"
-
-#ifndef CVC5__THEORY__ARITH__NL__CAD__VARIABLE_ORDERING_H
-#define CVC5__THEORY__ARITH__NL__CAD__VARIABLE_ORDERING_H
-
-#ifdef CVC5_POLY_IMP
-
-#include <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
diff --git a/src/theory/arith/nl/cad_solver.cpp b/src/theory/arith/nl/cad_solver.cpp
deleted file mode 100644 (file)
index 57ea8c8..0000000
+++ /dev/null
@@ -1,255 +0,0 @@
-/******************************************************************************
- * Top contributors (to current version):
- *   Gereon Kremer, Andrew Reynolds, Andres Noetzli
- *
- * This file is part of the cvc5 project.
- *
- * Copyright (c) 2009-2021 by the authors listed in the file AUTHORS
- * in the top-level source directory and their institutional affiliations.
- * All rights reserved.  See the file COPYING in the top-level source
- * directory for licensing information.
- * ****************************************************************************
- *
- * Implementation of new non-linear solver.
- */
-
-#include "theory/arith/nl/cad_solver.h"
-
-#include "expr/skolem_manager.h"
-#include "options/arith_options.h"
-#include "smt/env.h"
-#include "theory/arith/inference_manager.h"
-#include "theory/arith/nl/cad/cdcac.h"
-#include "theory/arith/nl/nl_model.h"
-#include "theory/arith/nl/poly_conversion.h"
-#include "theory/inference_id.h"
-#include "theory/theory.h"
-
-namespace cvc5 {
-namespace theory {
-namespace arith {
-namespace nl {
-
-CadSolver::CadSolver(Env& env, InferenceManager& im, NlModel& model)
-    :
-      EnvObj(env),
-#ifdef CVC5_POLY_IMP
-      d_CAC(env),
-#endif
-      d_foundSatisfiability(false),
-      d_im(im),
-      d_model(model),
-      d_eqsubs(env)
-{
-  NodeManager* nm = NodeManager::currentNM();
-  SkolemManager* sm = nm->getSkolemManager();
-  d_ranVariable = sm->mkDummySkolem("__z", nm->realType(), "");
-#ifdef CVC5_POLY_IMP
-  if (env.isTheoryProofProducing())
-  {
-    ProofChecker* pc = env.getProofNodeManager()->getChecker();
-    // add checkers
-    d_proofChecker.registerTo(pc);
-  }
-#endif
-}
-
-CadSolver::~CadSolver() {}
-
-void CadSolver::initLastCall(const std::vector<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
diff --git a/src/theory/arith/nl/cad_solver.h b/src/theory/arith/nl/cad_solver.h
deleted file mode 100644 (file)
index d72c92a..0000000
+++ /dev/null
@@ -1,124 +0,0 @@
-/******************************************************************************
- * Top contributors (to current version):
- *   Gereon Kremer
- *
- * This file is part of the cvc5 project.
- *
- * Copyright (c) 2009-2021 by the authors listed in the file AUTHORS
- * in the top-level source directory and their institutional affiliations.
- * All rights reserved.  See the file COPYING in the top-level source
- * directory for licensing information.
- * ****************************************************************************
- *
- * CAD-based solver based on https://arxiv.org/pdf/2003.05633.pdf.
- */
-
-#ifndef CVC5__THEORY__ARITH__CAD_SOLVER_H
-#define CVC5__THEORY__ARITH__CAD_SOLVER_H
-
-#include <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 */
diff --git a/src/theory/arith/nl/coverings/cdcac.cpp b/src/theory/arith/nl/coverings/cdcac.cpp
new file mode 100644 (file)
index 0000000..8091964
--- /dev/null
@@ -0,0 +1,768 @@
+/******************************************************************************
+ * Top contributors (to current version):
+ *   Gereon Kremer, Aina Niemetz
+ *
+ * This file is part of the cvc5 project.
+ *
+ * Copyright (c) 2009-2021 by the authors listed in the file AUTHORS
+ * in the top-level source directory and their institutional affiliations.
+ * All rights reserved.  See the file COPYING in the top-level source
+ * directory for licensing information.
+ * ****************************************************************************
+ *
+ * Implements the CDCAC approach as described in
+ * https://arxiv.org/pdf/2003.05633.pdf.
+ */
+
+#include "theory/arith/nl/coverings/cdcac.h"
+
+#ifdef CVC5_POLY_IMP
+
+#include "options/arith_options.h"
+#include "theory/arith/nl/coverings/lazard_evaluation.h"
+#include "theory/arith/nl/coverings/projections.h"
+#include "theory/arith/nl/coverings/variable_ordering.h"
+#include "theory/arith/nl/nl_model.h"
+#include "theory/rewriter.h"
+
+using namespace cvc5::kind;
+
+namespace std {
+/** Generic streaming operator for std::vector. */
+template <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
diff --git a/src/theory/arith/nl/coverings/cdcac.h b/src/theory/arith/nl/coverings/cdcac.h
new file mode 100644 (file)
index 0000000..e7f5709
--- /dev/null
@@ -0,0 +1,246 @@
+/******************************************************************************
+ * Top contributors (to current version):
+ *   Gereon Kremer
+ *
+ * This file is part of the cvc5 project.
+ *
+ * Copyright (c) 2009-2021 by the authors listed in the file AUTHORS
+ * in the top-level source directory and their institutional affiliations.
+ * All rights reserved.  See the file COPYING in the top-level source
+ * directory for licensing information.
+ * ****************************************************************************
+ *
+ * Implements the CDCAC approach as described in
+ * https://arxiv.org/pdf/2003.05633.pdf.
+ */
+
+#include "cvc5_private.h"
+
+#ifndef CVC5__THEORY__ARITH__NL__COVERINGS__CDCAC_H
+#define CVC5__THEORY__ARITH__NL__COVERINGS__CDCAC_H
+
+#ifdef CVC5_POLY_IMP
+
+#include <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
diff --git a/src/theory/arith/nl/coverings/cdcac_utils.cpp b/src/theory/arith/nl/coverings/cdcac_utils.cpp
new file mode 100644 (file)
index 0000000..ae1abe9
--- /dev/null
@@ -0,0 +1,466 @@
+/******************************************************************************
+ * Top contributors (to current version):
+ *   Gereon Kremer
+ *
+ * This file is part of the cvc5 project.
+ *
+ * Copyright (c) 2009-2021 by the authors listed in the file AUTHORS
+ * in the top-level source directory and their institutional affiliations.
+ * All rights reserved.  See the file COPYING in the top-level source
+ * directory for licensing information.
+ * ****************************************************************************
+ *
+ * Implements utilities for cdcac.
+ */
+
+#include "theory/arith/nl/coverings/cdcac_utils.h"
+
+#ifdef CVC5_POLY_IMP
+
+#include <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
diff --git a/src/theory/arith/nl/coverings/cdcac_utils.h b/src/theory/arith/nl/coverings/cdcac_utils.h
new file mode 100644 (file)
index 0000000..16c01fe
--- /dev/null
@@ -0,0 +1,115 @@
+/******************************************************************************
+ * Top contributors (to current version):
+ *   Gereon Kremer
+ *
+ * This file is part of the cvc5 project.
+ *
+ * Copyright (c) 2009-2021 by the authors listed in the file AUTHORS
+ * in the top-level source directory and their institutional affiliations.
+ * All rights reserved.  See the file COPYING in the top-level source
+ * directory for licensing information.
+ * ****************************************************************************
+ *
+ * Implements utilities for cdcac.
+ */
+
+#include "cvc5_private.h"
+
+#ifndef CVC5__THEORY__ARITH__NL__COVERINGS__CDCAC_UTILS_H
+#define CVC5__THEORY__ARITH__NL__COVERINGS__CDCAC_UTILS_H
+
+#ifdef CVC5_POLY_IMP
+
+#include <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
diff --git a/src/theory/arith/nl/coverings/constraints.cpp b/src/theory/arith/nl/coverings/constraints.cpp
new file mode 100644 (file)
index 0000000..d857113
--- /dev/null
@@ -0,0 +1,83 @@
+/******************************************************************************
+ * Top contributors (to current version):
+ *   Gereon Kremer
+ *
+ * This file is part of the cvc5 project.
+ *
+ * Copyright (c) 2009-2021 by the authors listed in the file AUTHORS
+ * in the top-level source directory and their institutional affiliations.
+ * All rights reserved.  See the file COPYING in the top-level source
+ * directory for licensing information.
+ * ****************************************************************************
+ *
+ * Implements a container for coverings constraints.
+ */
+
+#include "theory/arith/nl/coverings/constraints.h"
+
+#ifdef CVC5_POLY_IMP
+
+#include <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
diff --git a/src/theory/arith/nl/coverings/constraints.h b/src/theory/arith/nl/coverings/constraints.h
new file mode 100644 (file)
index 0000000..7fe521a
--- /dev/null
@@ -0,0 +1,93 @@
+/******************************************************************************
+ * Top contributors (to current version):
+ *   Gereon Kremer
+ *
+ * This file is part of the cvc5 project.
+ *
+ * Copyright (c) 2009-2021 by the authors listed in the file AUTHORS
+ * in the top-level source directory and their institutional affiliations.
+ * All rights reserved.  See the file COPYING in the top-level source
+ * directory for licensing information.
+ * ****************************************************************************
+ *
+ * Implements a container for coverings constraints.
+ */
+
+#include "cvc5_private.h"
+
+#ifndef CVC5__THEORY__ARITH__NL__COVERINGS__CONSTRAINTS_H
+#define CVC5__THEORY__ARITH__NL__COVERINGS__CONSTRAINTS_H
+
+#ifdef CVC5_POLY_IMP
+
+#include <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
diff --git a/src/theory/arith/nl/coverings/lazard_evaluation.cpp b/src/theory/arith/nl/coverings/lazard_evaluation.cpp
new file mode 100644 (file)
index 0000000..3d60e86
--- /dev/null
@@ -0,0 +1,995 @@
+#include "theory/arith/nl/coverings/lazard_evaluation.h"
+
+#ifdef CVC5_POLY_IMP
+
+#include "base/check.h"
+#include "base/output.h"
+#include "smt/smt_statistics_registry.h"
+#include "util/statistics_stats.h"
+
+#ifdef CVC5_USE_COCOA
+
+#include <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
diff --git a/src/theory/arith/nl/coverings/lazard_evaluation.h b/src/theory/arith/nl/coverings/lazard_evaluation.h
new file mode 100644 (file)
index 0000000..a03630d
--- /dev/null
@@ -0,0 +1,117 @@
+/******************************************************************************
+ * Top contributors (to current version):
+ *   Gereon Kremer
+ *
+ * This file is part of the cvc5 project.
+ *
+ * Copyright (c) 2009-2021 by the authors listed in the file AUTHORS
+ * in the top-level source directory and their institutional affiliations.
+ * All rights reserved.  See the file COPYING in the top-level source
+ * directory for licensing information.
+ * ****************************************************************************
+ *
+ * Implements the CDCAC approach as described in
+ * https://arxiv.org/pdf/2003.05633.pdf.
+ */
+
+#include "cvc5_private.h"
+
+#ifndef CVC5__THEORY__ARITH__NL__COVERINGS__LAZARD_EVALUATION_H
+#define CVC5__THEORY__ARITH__NL__COVERINGS__LAZARD_EVALUATION_H
+
+#ifdef CVC5_POLY_IMP
+
+#include <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
diff --git a/src/theory/arith/nl/coverings/projections.cpp b/src/theory/arith/nl/coverings/projections.cpp
new file mode 100644 (file)
index 0000000..4340f99
--- /dev/null
@@ -0,0 +1,110 @@
+/******************************************************************************
+ * Top contributors (to current version):
+ *   Gereon Kremer
+ *
+ * This file is part of the cvc5 project.
+ *
+ * Copyright (c) 2009-2021 by the authors listed in the file AUTHORS
+ * in the top-level source directory and their institutional affiliations.
+ * All rights reserved.  See the file COPYING in the top-level source
+ * directory for licensing information.
+ * ****************************************************************************
+ *
+ * Implements utilities for coverings projection operators.
+ */
+
+#include "theory/arith/nl/coverings/projections.h"
+
+#ifdef CVC5_POLY_IMP
+
+#include "base/check.h"
+
+namespace cvc5 {
+namespace theory {
+namespace arith {
+namespace nl {
+namespace coverings {
+
+using namespace poly;
+
+void PolyVector::add(const poly::Polynomial& poly, bool assertMain)
+{
+  for (const auto& p : poly::square_free_factors(poly))
+  {
+    if (poly::is_constant(p)) continue;
+    if (assertMain)
+    {
+      Assert(main_variable(poly) == main_variable(p));
+    }
+    std::vector<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
diff --git a/src/theory/arith/nl/coverings/projections.h b/src/theory/arith/nl/coverings/projections.h
new file mode 100644 (file)
index 0000000..7df36a6
--- /dev/null
@@ -0,0 +1,82 @@
+/******************************************************************************
+ * Top contributors (to current version):
+ *   Gereon Kremer
+ *
+ * This file is part of the cvc5 project.
+ *
+ * Copyright (c) 2009-2021 by the authors listed in the file AUTHORS
+ * in the top-level source directory and their institutional affiliations.
+ * All rights reserved.  See the file COPYING in the top-level source
+ * directory for licensing information.
+ * ****************************************************************************
+ *
+ * Implements utilities for coverings projection operators.
+ */
+
+#include "cvc5_private.h"
+
+#ifndef CVC5__THEORY__ARITH__NL__COVERINGS_PROJECTIONS_H
+#define CVC5__THEORY__ARITH__NL__COVERINGS_PROJECTIONS_H
+
+#ifdef CVC5_USE_POLY
+
+#include <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
diff --git a/src/theory/arith/nl/coverings/proof_checker.cpp b/src/theory/arith/nl/coverings/proof_checker.cpp
new file mode 100644 (file)
index 0000000..d4f8c6f
--- /dev/null
@@ -0,0 +1,62 @@
+/******************************************************************************
+ * Top contributors (to current version):
+ *   Gereon Kremer, Aina Niemetz
+ *
+ * This file is part of the cvc5 project.
+ *
+ * Copyright (c) 2009-2021 by the authors listed in the file AUTHORS
+ * in the top-level source directory and their institutional affiliations.
+ * All rights reserved.  See the file COPYING in the top-level source
+ * directory for licensing information.
+ * ****************************************************************************
+ *
+ * Implementation of CAD proof checker.
+ */
+
+#include "theory/arith/nl/coverings/proof_checker.h"
+
+#include "expr/sequence.h"
+#include "theory/rewriter.h"
+
+using namespace cvc5::kind;
+
+namespace cvc5 {
+namespace theory {
+namespace arith {
+namespace nl {
+namespace coverings {
+
+void CoveringsProofRuleChecker::registerTo(ProofChecker* pc)
+{
+  // trusted rules
+  pc->registerTrustedChecker(PfRule::ARITH_NL_COVERING_DIRECT, this, 2);
+  pc->registerTrustedChecker(PfRule::ARITH_NL_COVERING_RECURSIVE, this, 2);
+}
+
+Node CoveringsProofRuleChecker::checkInternal(PfRule id,
+                                        const std::vector<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
diff --git a/src/theory/arith/nl/coverings/proof_checker.h b/src/theory/arith/nl/coverings/proof_checker.h
new file mode 100644 (file)
index 0000000..cc33787
--- /dev/null
@@ -0,0 +1,59 @@
+/******************************************************************************
+ * Top contributors (to current version):
+ *   Gereon Kremer
+ *
+ * This file is part of the cvc5 project.
+ *
+ * Copyright (c) 2009-2021 by the authors listed in the file AUTHORS
+ * in the top-level source directory and their institutional affiliations.
+ * All rights reserved.  See the file COPYING in the top-level source
+ * directory for licensing information.
+ * ****************************************************************************
+ *
+ * Coverings proof checker utility.
+ */
+
+#include "cvc5_private.h"
+
+#ifndef CVC5__THEORY__ARITH__NL__COVERINGS__PROOF_CHECKER_H
+#define CVC5__THEORY__ARITH__NL__COVERINGS__PROOF_CHECKER_H
+
+#include "expr/node.h"
+#include "proof/proof_checker.h"
+
+namespace cvc5 {
+namespace theory {
+namespace arith {
+namespace nl {
+namespace coverings {
+
+/**
+ * A checker for coverings proofs
+ *
+ * This proof checker takes care of the two coverings proof rules ARITH_NL_COVERING_DIRECT
+ * and ARITH_NL_COVERING_RECURSIVE. It does not do any actual proof checking yet, but
+ * considers them to be trusted rules.
+ */
+class CoveringsProofRuleChecker : public ProofRuleChecker
+{
+ public:
+  CoveringsProofRuleChecker() {}
+  ~CoveringsProofRuleChecker() {}
+
+  /** Register all rules owned by this rule checker in pc. */
+  void registerTo(ProofChecker* pc) override;
+
+ protected:
+  /** Return the conclusion of the given proof step, or null if it is invalid */
+  Node checkInternal(PfRule id,
+                     const std::vector<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 */
diff --git a/src/theory/arith/nl/coverings/proof_generator.cpp b/src/theory/arith/nl/coverings/proof_generator.cpp
new file mode 100644 (file)
index 0000000..b91af55
--- /dev/null
@@ -0,0 +1,247 @@
+/******************************************************************************
+ * Top contributors (to current version):
+ *   Gereon Kremer
+ *
+ * This file is part of the cvc5 project.
+ *
+ * Copyright (c) 2009-2021 by the authors listed in the file AUTHORS
+ * in the top-level source directory and their institutional affiliations.
+ * All rights reserved.  See the file COPYING in the top-level source
+ * directory for licensing information.
+ * ****************************************************************************
+ *
+ * Implementation of coverings proof generator.
+ */
+
+#include "theory/arith/nl/coverings/proof_generator.h"
+
+#ifdef CVC5_POLY_IMP
+
+#include "proof/lazy_tree_proof_generator.h"
+#include "theory/arith/nl/poly_conversion.h"
+#include "util/indexed_root_predicate.h"
+
+using namespace cvc5::kind;
+
+namespace cvc5 {
+namespace theory {
+namespace arith {
+namespace nl {
+namespace coverings {
+
+namespace {
+/**
+ * Retrieves the root indices of the sign-invariant region of v.
+ *
+ * We assume that roots holds a sorted list of roots from one polynomial.
+ * If v is equal to one of these roots, we return (id,id) where id is the index
+ * of this root within roots. Otherwise, we return the id of the largest root
+ * below v and the id of the smallest root above v. To make sure a smaller root
+ * and a larger root always exist, we implicitly extend the roots by -infty and
+ * infty.
+ *
+ * ATTENTION: if we return id, the corresponding root is:
+ *   - id = 0: -infty
+ *   - 0 < id <= roots.size(): roots[id-1]
+ *   - id = roots.size() + 1: infty
+ */
+std::pair<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
diff --git a/src/theory/arith/nl/coverings/proof_generator.h b/src/theory/arith/nl/coverings/proof_generator.h
new file mode 100644 (file)
index 0000000..46d3843
--- /dev/null
@@ -0,0 +1,159 @@
+/******************************************************************************
+ * Top contributors (to current version):
+ *   Gereon Kremer
+ *
+ * This file is part of the cvc5 project.
+ *
+ * Copyright (c) 2009-2021 by the authors listed in the file AUTHORS
+ * in the top-level source directory and their institutional affiliations.
+ * All rights reserved.  See the file COPYING in the top-level source
+ * directory for licensing information.
+ * ****************************************************************************
+ *
+ * Implements the proof generator for coverings.
+ */
+
+#include "cvc5_private.h"
+
+#ifndef CVC5__THEORY__ARITH__NL__COVERINGS__PROOF_GENERATOR_H
+#define CVC5__THEORY__ARITH__NL__COVERINGS__PROOF_GENERATOR_H
+
+#ifdef CVC5_POLY_IMP
+
+#include <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
diff --git a/src/theory/arith/nl/coverings/variable_ordering.cpp b/src/theory/arith/nl/coverings/variable_ordering.cpp
new file mode 100644 (file)
index 0000000..58a42d1
--- /dev/null
@@ -0,0 +1,136 @@
+/******************************************************************************
+ * Top contributors (to current version):
+ *   Gereon Kremer
+ *
+ * This file is part of the cvc5 project.
+ *
+ * Copyright (c) 2009-2021 by the authors listed in the file AUTHORS
+ * in the top-level source directory and their institutional affiliations.
+ * All rights reserved.  See the file COPYING in the top-level source
+ * directory for licensing information.
+ * ****************************************************************************
+ *
+ * Implements variable orderings tailored to coverings.
+ */
+
+#include "theory/arith/nl/coverings/variable_ordering.h"
+
+#ifdef CVC5_POLY_IMP
+
+#include "util/poly_util.h"
+
+namespace cvc5 {
+namespace theory {
+namespace arith {
+namespace nl {
+namespace coverings {
+
+std::vector<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
diff --git a/src/theory/arith/nl/coverings/variable_ordering.h b/src/theory/arith/nl/coverings/variable_ordering.h
new file mode 100644 (file)
index 0000000..e3f287f
--- /dev/null
@@ -0,0 +1,71 @@
+/******************************************************************************
+ * Top contributors (to current version):
+ *   Gereon Kremer
+ *
+ * This file is part of the cvc5 project.
+ *
+ * Copyright (c) 2009-2021 by the authors listed in the file AUTHORS
+ * in the top-level source directory and their institutional affiliations.
+ * All rights reserved.  See the file COPYING in the top-level source
+ * directory for licensing information.
+ * ****************************************************************************
+ *
+ * Implements variable orderings tailored to coverings.
+ */
+
+#include "cvc5_private.h"
+
+#ifndef CVC5__THEORY__ARITH__NL__COVERINGS__VARIABLE_ORDERING_H
+#define CVC5__THEORY__ARITH__NL__COVERINGS__VARIABLE_ORDERING_H
+
+#ifdef CVC5_POLY_IMP
+
+#include <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
diff --git a/src/theory/arith/nl/coverings_solver.cpp b/src/theory/arith/nl/coverings_solver.cpp
new file mode 100644 (file)
index 0000000..2ec366d
--- /dev/null
@@ -0,0 +1,255 @@
+/******************************************************************************
+ * Top contributors (to current version):
+ *   Gereon Kremer, Andrew Reynolds, Andres Noetzli
+ *
+ * This file is part of the cvc5 project.
+ *
+ * Copyright (c) 2009-2021 by the authors listed in the file AUTHORS
+ * in the top-level source directory and their institutional affiliations.
+ * All rights reserved.  See the file COPYING in the top-level source
+ * directory for licensing information.
+ * ****************************************************************************
+ *
+ * Implementation of new non-linear solver.
+ */
+
+#include "theory/arith/nl/coverings_solver.h"
+
+#include "expr/skolem_manager.h"
+#include "options/arith_options.h"
+#include "smt/env.h"
+#include "theory/arith/inference_manager.h"
+#include "theory/arith/nl/coverings/cdcac.h"
+#include "theory/arith/nl/nl_model.h"
+#include "theory/arith/nl/poly_conversion.h"
+#include "theory/inference_id.h"
+#include "theory/theory.h"
+
+namespace cvc5 {
+namespace theory {
+namespace arith {
+namespace nl {
+
+CoveringsSolver::CoveringsSolver(Env& env, InferenceManager& im, NlModel& model)
+    :
+      EnvObj(env),
+#ifdef CVC5_POLY_IMP
+      d_CAC(env),
+#endif
+      d_foundSatisfiability(false),
+      d_im(im),
+      d_model(model),
+      d_eqsubs(env)
+{
+  NodeManager* nm = NodeManager::currentNM();
+  SkolemManager* sm = nm->getSkolemManager();
+  d_ranVariable = sm->mkDummySkolem("__z", nm->realType(), "");
+#ifdef CVC5_POLY_IMP
+  if (env.isTheoryProofProducing())
+  {
+    ProofChecker* pc = env.getProofNodeManager()->getChecker();
+    // add checkers
+    d_proofChecker.registerTo(pc);
+  }
+#endif
+}
+
+CoveringsSolver::~CoveringsSolver() {}
+
+void CoveringsSolver::initLastCall(const std::vector<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
diff --git a/src/theory/arith/nl/coverings_solver.h b/src/theory/arith/nl/coverings_solver.h
new file mode 100644 (file)
index 0000000..73ba352
--- /dev/null
@@ -0,0 +1,124 @@
+/******************************************************************************
+ * Top contributors (to current version):
+ *   Gereon Kremer
+ *
+ * This file is part of the cvc5 project.
+ *
+ * Copyright (c) 2009-2021 by the authors listed in the file AUTHORS
+ * in the top-level source directory and their institutional affiliations.
+ * All rights reserved.  See the file COPYING in the top-level source
+ * directory for licensing information.
+ * ****************************************************************************
+ *
+ * CAD-based solver based on https://arxiv.org/pdf/2003.05633.pdf.
+ */
+
+#ifndef CVC5__THEORY__ARITH__COVERINGS_SOLVER_H
+#define CVC5__THEORY__ARITH__COVERINGS_SOLVER_H
+
+#include <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 */
index a26dbf173d59cd93b737d08ea73e743eb4f7be31..43004c21ef88f71b30d3f030843aed5dc4df20b1 100644 (file)
@@ -56,7 +56,7 @@ NonlinearExtension::NonlinearExtension(Env& env,
       d_monomialSlv(d_env, &d_extState),
       d_splitZeroSlv(d_env, &d_extState),
       d_tangentPlaneSlv(d_env, &d_extState),
-      d_cadSlv(d_env, d_im, d_model),
+      d_covSlv(d_env, d_im, d_model),
       d_icpSlv(d_env, d_im),
       d_iandSlv(env, d_im, state, d_model),
       d_pow2Slv(env, d_im, state, d_model)
@@ -223,9 +223,9 @@ bool NonlinearExtension::checkModel(const std::vector<Node>& assertions)
       return false;
     }
   }
-  if (options().arith.nlCad)
+  if (options().arith.nlCov)
   {
-    d_cadSlv.constructModelIfAvailable(passertions);
+    d_covSlv.constructModelIfAvailable(passertions);
   }
 
   Trace("nl-ext-cm") << "-----" << std::endl;
@@ -442,8 +442,8 @@ void NonlinearExtension::runStrategy(Theory::Effort effort,
     {
       case InferStep::BREAK: stop = d_im.hasPendingLemma(); break;
       case InferStep::FLUSH_WAITING_LEMMAS: d_im.flushWaitingLemmas(); break;
-      case InferStep::CAD_FULL: d_cadSlv.checkFull(); break;
-      case InferStep::CAD_INIT: d_cadSlv.initLastCall(assertions); break;
+      case InferStep::COVERINGS_FULL: d_covSlv.checkFull(); break;
+      case InferStep::COVERINGS_INIT: d_covSlv.initLastCall(assertions); break;
       case InferStep::NL_FACTORING:
         d_factoringSlv.check(assertions, false_asserts);
         break;
index 0c94bbc08d18c2227ab4f43c9665f5dedf6b5c1a..0080f894825f62da99504b947a3af69cd13aa65c 100644 (file)
@@ -22,7 +22,7 @@
 
 #include "expr/node.h"
 #include "smt/env_obj.h"
-#include "theory/arith/nl/cad_solver.h"
+#include "theory/arith/nl/coverings_solver.h"
 #include "theory/arith/nl/ext/ext_state.h"
 #include "theory/arith/nl/ext/factoring_check.h"
 #include "theory/arith/nl/ext/monomial_bounds_check.h"
@@ -242,8 +242,8 @@ class NonlinearExtension : EnvObj
   SplitZeroCheck d_splitZeroSlv;
   /** Solver for tangent plane lemmas. */
   TangentPlaneCheck d_tangentPlaneSlv;
-  /** The CAD-based solver */
-  CadSolver d_cadSlv;
+  /** The coverings-based solver */
+  CoveringsSolver d_covSlv;
   /** The ICP-based solver */
   icp::ICPSolver d_icpSlv;
   /** The integer and solver
index 43c3d152ded103c35acf1b9c7d25d7e9e1e6dc96..0327af22c71d2a1d8f7547f06d1541c5c3aaf952 100644 (file)
@@ -567,7 +567,7 @@ Node excluding_interval_to_lemma(const Node& variable,
       const poly::AlgebraicNumber& alg = as_algebraic_number(lv);
       if (poly::is_rational(alg))
       {
-        Trace("nl-cad") << "Rational point interval: " << interval << std::endl;
+        Trace("nl-cov") << "Rational point interval: " << interval << std::endl;
         return nm->mkNode(
             Kind::DISTINCT,
             variable,
@@ -575,7 +575,7 @@ Node excluding_interval_to_lemma(const Node& variable,
                 CONST_RATIONAL,
                 poly_utils::toRational(poly::to_rational_approximation(alg))));
       }
-      Trace("nl-cad") << "Algebraic point interval: " << interval << std::endl;
+      Trace("nl-cov") << "Algebraic point interval: " << interval << std::endl;
       // p(x) != 0 or x <= lb or ub <= x
       if (allowNonlinearLemma)
       {
@@ -597,7 +597,7 @@ Node excluding_interval_to_lemma(const Node& variable,
     }
     else
     {
-      Trace("nl-cad") << "Rational point interval: " << interval << std::endl;
+      Trace("nl-cov") << "Rational point interval: " << interval << std::endl;
       return nm->mkNode(
           Kind::DISTINCT,
           variable,
@@ -606,17 +606,17 @@ Node excluding_interval_to_lemma(const Node& variable,
   }
   if (li)
   {
-    Trace("nl-cad") << "Only upper bound: " << interval << std::endl;
+    Trace("nl-cov") << "Only upper bound: " << interval << std::endl;
     return upper_bound_as_node(
         variable, uv, poly::get_upper_open(interval), allowNonlinearLemma);
   }
   if (ui)
   {
-    Trace("nl-cad") << "Only lower bound: " << interval << std::endl;
+    Trace("nl-cov") << "Only lower bound: " << interval << std::endl;
     return lower_bound_as_node(
         variable, lv, poly::get_lower_open(interval), allowNonlinearLemma);
   }
-  Trace("nl-cad") << "Proper interval: " << interval << std::endl;
+  Trace("nl-cov") << "Proper interval: " << interval << std::endl;
   Node lb = lower_bound_as_node(
       variable, lv, poly::get_lower_open(interval), allowNonlinearLemma);
   Node ub = upper_bound_as_node(
index dddde3c0fca7ad803800b574885179779b6882f9..f9c82fa1f7d56e200649a1812e3e8a45737e48aa 100644 (file)
@@ -68,7 +68,7 @@ poly::UPolynomial as_poly_upolynomial(const cvc5::Node& n,
  * Once the polynomial has been fully constructed, we can oftentimes ignore the
  * denominator (except for its sign, which is always positive, though).
  * This is the case if we are solely interested in the roots of the polynomials
- * (like in the context of CAD). If we need the actual polynomial (for example
+ * (like in the context of coverings). If we need the actual polynomial (for example
  * in the context of ICP) the second overload provides the denominator in the
  * third argument.
  */
index a14841f67efc3edf71f59b8991bebeffc7b7061c..8ea46099cc9cd4fdf222f42ef14c12bf6ba1b249 100644 (file)
@@ -31,8 +31,8 @@ std::ostream& operator<<(std::ostream& os, InferStep step)
   {
     case InferStep::BREAK: return os << "BREAK";
     case InferStep::FLUSH_WAITING_LEMMAS: return os << "FLUSH_WAITING_LEMMAS";
-    case InferStep::CAD_INIT: return os << "CAD_INIT";
-    case InferStep::CAD_FULL: return os << "CAD_FULL";
+    case InferStep::COVERINGS_INIT: return os << "COVERINGS_INIT";
+    case InferStep::COVERINGS_FULL: return os << "COVERINGS_FULL";
     case InferStep::NL_FACTORING: return os << "NL_FACTORING";
     case InferStep::IAND_INIT: return os << "IAND_INIT";
     case InferStep::IAND_FULL: return os << "IAND_FULL";
@@ -170,10 +170,10 @@ void Strategy::initializeStrategy(const Options& options)
   }
   one << InferStep::IAND_FULL << InferStep::BREAK;
   one << InferStep::POW2_FULL << InferStep::BREAK;
-  if (options.arith.nlCad)
+  if (options.arith.nlCov)
   {
-    one << InferStep::CAD_INIT << InferStep::BREAK;
-    one << InferStep::CAD_FULL << InferStep::BREAK;
+    one << InferStep::COVERINGS_INIT << InferStep::BREAK;
+    one << InferStep::COVERINGS_FULL << InferStep::BREAK;
   }
 
   d_interleaving.add(one);
index d5010885139d0d01fc75e124d5ea1258398c8fac..a0d257d2a9ec592131d358a8acca8bf368b9267e 100644 (file)
@@ -34,10 +34,10 @@ enum class InferStep
   /** Flush waiting lemmas to be pending */
   FLUSH_WAITING_LEMMAS,
 
-  /** Initialize the CAD solver */
-  CAD_INIT,
-  /** A full CAD check */
-  CAD_FULL,
+  /** Initialize the coverings solver */
+  COVERINGS_INIT,
+  /** A full coverings check */
+  COVERINGS_FULL,
 
   /** Initialize the IAND solver */
   IAND_INIT,
index b0d218e814b3193e98b186f4a6af92d84d7e9a48..5968a2ff2315b941ab3fddd0049199356f256b46 100644 (file)
@@ -100,9 +100,9 @@ const char* toString(InferenceId i)
       return "ARITH_NL_POW2_MONOTONE_REFINE";
     case InferenceId::ARITH_NL_POW2_TRIVIAL_CASE_REFINE:
       return "ARITH_NL_POW2_TRIVIAL_CASE_REFINE";
-    case InferenceId::ARITH_NL_CAD_CONFLICT: return "ARITH_NL_CAD_CONFLICT";
-    case InferenceId::ARITH_NL_CAD_EXCLUDED_INTERVAL:
-      return "ARITH_NL_CAD_EXCLUDED_INTERVAL";
+    case InferenceId::ARITH_NL_COVERING_CONFLICT: return "ARITH_NL_COVERING_CONFLICT";
+    case InferenceId::ARITH_NL_COVERING_EXCLUDED_INTERVAL:
+      return "ARITH_NL_COVERING_EXCLUDED_INTERVAL";
     case InferenceId::ARITH_NL_ICP_CONFLICT: return "ARITH_NL_ICP_CONFLICT";
     case InferenceId::ARITH_NL_ICP_PROPAGATION:
       return "ARITH_NL_ICP_PROPAGATION";
index a34b4066114d38f4a5eb91c09cc2f3ffc14d3b15..76c330cae8758fc5e4644f418c41913e0835664b 100644 (file)
@@ -158,11 +158,11 @@ enum class InferenceId
   ARITH_NL_POW2_MONOTONE_REFINE,
   // trivial refinements (Pow2Solver::checkFullRefine)
   ARITH_NL_POW2_TRIVIAL_CASE_REFINE,
-  //-------------------- nonlinear cad solver
-  // conflict / infeasible subset obtained from cad
-  ARITH_NL_CAD_CONFLICT,
+  //-------------------- nonlinear coverings solver
+  // conflict / infeasible subset obtained from coverings
+  ARITH_NL_COVERING_CONFLICT,
   // excludes an interval for a single variable
-  ARITH_NL_CAD_EXCLUDED_INTERVAL,
+  ARITH_NL_COVERING_EXCLUDED_INTERVAL,
   //-------------------- nonlinear icp solver
   // conflict obtained from icp
   ARITH_NL_ICP_CONFLICT,
index b9b204198abd220a3f1ee1bc064c65fa58d3ecf2..4a663ebe08c18af0d8eadae62addc9b485a02897 100644 (file)
@@ -1,4 +1,4 @@
-; COMMAND-LINE: --nl-ext=none --nl-cad
+; COMMAND-LINE: --nl-ext=none --nl-cov
 ; REQUIRES: poly
 ; EXPECT: unsat
 (set-logic QF_NRA)
index bfe8d31a688acf22492aaff9fa72191333bc079f..07f1b7f6d49e01a7dfac0ba71b7a382c8a79b55e 100644 (file)
@@ -1,4 +1,4 @@
-; COMMAND-LINE: --nl-ext=none --nl-cad
+; COMMAND-LINE: --nl-ext=none --nl-cov
 ; REQUIRES: poly
 ; EXPECT: sat
 (set-logic QF_NRA)
index 0b9b9f119a6f4f050c9ee8ec8f9d81eeeab3d8ab..de33a3f3d24f36503db72812dc0db97ba17a87df 100644 (file)
@@ -55,8 +55,8 @@ TEST_F(TestTheoryBlackArithNl, cvc5Projects388Min)
     return;
   }
   Solver slv;
-  slv.setOption("nl-cad", "true");
-  slv.setOption("nl-cad-var-elim", "true");
+  slv.setOption("nl-cov", "true");
+  slv.setOption("nl-cov-var-elim", "true");
   slv.setOption("nl-ext", "none");
   slv.setLogic("QF_NIRA");
   Sort s = slv.getRealSort();
index 82e191d83a5d456a876298101bd55097c76b4d5c..82cd3cb58a664fcbae04cd9cfeb48b52e1d9629a 100644 (file)
@@ -22,7 +22,7 @@ cvc5_add_unit_test_white(sequences_rewriter_white theory)
 cvc5_add_unit_test_white(strings_rewriter_white theory)
 cvc5_add_unit_test_white(theory_arith_pow2_white theory)
 cvc5_add_unit_test_white(theory_arith_white theory)
-cvc5_add_unit_test_white(theory_arith_cad_white theory)
+cvc5_add_unit_test_white(theory_arith_coverings_white theory)
 cvc5_add_unit_test_black(theory_arith_rewriter_black theory)
 cvc5_add_unit_test_white(theory_bags_normal_form_white theory)
 cvc5_add_unit_test_white(theory_bags_rewriter_white theory)
diff --git a/test/unit/theory/theory_arith_cad_white.cpp b/test/unit/theory/theory_arith_cad_white.cpp
deleted file mode 100644 (file)
index aef2189..0000000
+++ /dev/null
@@ -1,486 +0,0 @@
-/******************************************************************************
- * Top contributors (to current version):
- *   Gereon Kremer
- *
- * This file is part of the cvc5 project.
- *
- * Copyright (c) 2009-2021 by the authors listed in the file AUTHORS
- * in the top-level source directory and their institutional affiliations.
- * All rights reserved.  See the file COPYING in the top-level source
- * directory for licensing information.
- * ****************************************************************************
- *
- * Unit tests for the CAD module for nonlinear arithmetic.
- */
-
-#ifdef CVC5_USE_POLY
-
-#include <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
diff --git a/test/unit/theory/theory_arith_coverings_white.cpp b/test/unit/theory/theory_arith_coverings_white.cpp
new file mode 100644 (file)
index 0000000..864402e
--- /dev/null
@@ -0,0 +1,485 @@
+/******************************************************************************
+ * Top contributors (to current version):
+ *   Gereon Kremer
+ *
+ * This file is part of the cvc5 project.
+ *
+ * Copyright (c) 2009-2021 by the authors listed in the file AUTHORS
+ * in the top-level source directory and their institutional affiliations.
+ * All rights reserved.  See the file COPYING in the top-level source
+ * directory for licensing information.
+ * ****************************************************************************
+ *
+ * Unit tests for the coverings module for nonlinear arithmetic.
+ */
+
+#ifdef CVC5_USE_POLY
+
+#include <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