GCC Code Coverage Report
Directory: . Exec Total Coverage
File: src/theory/arith/nl/cad/lazard_evaluation.cpp Lines: 6 17 35.3 %
Date: 2021-09-07 Branches: 2 30 6.7 %

Line Exec Source
1
#include "theory/arith/nl/cad/lazard_evaluation.h"
2
3
#ifdef CVC5_POLY_IMP
4
5
#include "base/check.h"
6
#include "base/output.h"
7
#include "smt/smt_statistics_registry.h"
8
#include "util/statistics_stats.h"
9
10
#ifdef CVC5_USE_COCOA
11
12
#include <CoCoA/library.H>
13
14
#include <optional>
15
16
namespace cvc5::theory::arith::nl::cad {
17
18
struct LazardEvaluationStats
19
{
20
  IntStat d_directAssignments =
21
      smtStatisticsRegistry().registerInt("theory::arith::cad::lazard-direct");
22
  IntStat d_ranAssignments =
23
      smtStatisticsRegistry().registerInt("theory::arith::cad::lazard-rans");
24
  IntStat d_evaluations =
25
      smtStatisticsRegistry().registerInt("theory::arith::cad::lazard-evals");
26
  IntStat d_reductions =
27
      smtStatisticsRegistry().registerInt("theory::arith::cad::lazard-reduce");
28
};
29
30
struct LazardEvaluationState;
31
std::ostream& operator<<(std::ostream& os, const LazardEvaluationState& state);
32
33
/**
34
 * This class holds and implements all the technicalities required to map
35
 * polynomials from libpoly into CoCoALib, perform these computations properly
36
 * within CoCoALib and map the result back to libpoly.
37
 *
38
 * We need to be careful to perform all computations in the proper polynomial
39
 * rings, both to be correct and because CoCoALib explicitly requires it. As we
40
 * change the ring we are computing it all the time, we also need appropriate
41
 * ring homomorphisms to map polynomials from one into the other. We first give
42
 * a short overview of our approach, then describe the various polynomial rings
43
 * that are used, and then discuss which rings are used where.
44
 *
45
 * Inputs:
46
 * - (real) variables x_0, ..., x_n
47
 * - real algebraic numbers a_0, ..., a_{n-1} with
48
 * - defining polynomials p_0, ..., p_{n-1}; p_i from Q[x_i]
49
 * - a polynomial q over all variables x_0, ..., x_n
50
 *
51
 * We first iteratively build the field extensions Q(a_0), Q(a_0, a_2) ...
52
 * Instead of the extension field Q(a_0), we use the isomorphic quotient ring
53
 * Q[x_0]/<p_0> and recursively extend it with a_1, etc, in the same way. Doing
54
 * this recursive construction naively fails: (Q[x_0]/<p_0>)[x_1]/<p_1> is not
55
 * necessarily a proper field as p_1 (though a minimal polynomial in Q[x_1]) may
56
 * factor over Q[x_0]/<p_0>. Consider p_0 = x_0*x_0-2 and p_1 =
57
 * x_1*x_1*x_1*x_1-2 as an example, where p_1 factors into
58
 * (x_1*x_1-x_0)*(x_1*x_1+x_0) over Q[x_0]/<p_0>. We overcome this by explicitly
59
 * computing this factorization and using the factor that vanishes over {x_0 ->
60
 * a_0, x_1 -> a_1 } as the minimal polynomial of a_1 over Q[x_0]/<p_0>.
61
 *
62
 * After we have built the field extensions in that way, we iteratively push q
63
 * through the field extensions, each one extended to a polynomial ring over all
64
 * x_0, ..., x_n. When in the k'th field extension, we check whether the k'th
65
 * minimal polynomial divides q. If so, q would vanish in the next step and we
66
 * instead set q = q/p_{k}. Only then we map q into K_{k+1}.
67
 *
68
 * Eventually, we end up with q in Q(a_0, ..., a_{n-1})[x_n]. This polynomial is
69
 * univariate conceptually, and we want to compute its roots. However, it is not
70
 * technically univariate and we need to make it so. We can do this by computing
71
 * the Gröbner basis of the q and all minimal polynomials p_i with an
72
 * elimination order with x_n at the bottom over Q[x_0, ..., x_n].
73
 * We then collect the polynomials
74
 * that are univariate in x_n from the Gröbner basis. We can show that the roots
75
 * of these polynomials are a superset of the roots we are looking for.
76
 *
77
 *
78
 * To implement all that, we construct the following polynomial rings:
79
 * - K_i: K_0 = Q, K_{i+1} = K_{i}[x_i]/<p_i> (with p_i reduced w.r.t. K_i)
80
 * - R_i = K_i[x_i]
81
 * - J_i = K_i[x_i, ..., x_n] = R_i[x_{i+1}, ..., x_n]
82
 *
83
 * While p_i conceptually live in Q[x_i], we immediately convert them from
84
 * libpoly into R_i. We then factor it there, obtaining the actual minimal
85
 * polynomial p_i that we use to construct K_{i+1}. We do this to construct all
86
 * K_i and R_i. We then reduce q, initially in Q[x_0, ..., x_n] = J_0. We check
87
 * in J_i whether p_i divides q (and if so divide q by p_i). To do
88
 * this, we need to embed p_i into J_i. We then
89
 * map q from J_i to J_{i+1}. While obvious in theory, this is somewhat tricky
90
 * in practice as J_i and J_{i+1} have no direct relationship.
91
 * Finally, we need to push all p_i and the final q back into J_0 = Q[x_0, ...,
92
 * x_n] to compute the Gröbner basis.
93
 *
94
 * We thus furthermore store the following ring homomorphisms:
95
 * - phom_i: R_i -> J_i (canonical embedding)
96
 * - qhom_i: J_i -> J_{i+1} (hand-crafted homomorphism)
97
 *
98
 * We can sometimes avoid this construction for individual variables, i.e., if
99
 * the assignment for x_i already lives (algebraically) in K_i. This can be the
100
 * case if a_i is rational; in general, we check whether the vanishing factor
101
 * of p_i is linear. If so, it has the form x_i-r where is some term in lower
102
 * variables. We store r as the "direct assignment" in d_direct[i] and use it
103
 * to directly replace x_i when appropriate. Also, we have K_i = K_{i-1}.
104
 *
105
 */
106
struct LazardEvaluationState
107
{
108
  CoCoA::GlobalManager d_gm;
109
  static std::unique_ptr<LazardEvaluationStats> d_stats;
110
111
  /**
112
   * Maps libpoly variables to indets in J0. Used when constructing the input
113
   * polynomial q in the first polynomial ring J0.
114
   */
115
  std::map<poly::Variable, CoCoA::RingElem> d_varQ;
116
  /**
117
   * Maps CoCoA indets back to to libpoly variables.
118
   * Use when converting CoCoA RingElems to libpoly polynomials, either when
119
   * checking whether a factor vanishes or when returning the univariate
120
   * elements of the final Gröbner basis. The CoCoA indets are identified by the
121
   * pair of the ring id and the indet identifier. Hence, we can put all of them
122
   * in one map, no matter which ring they belong to.
123
   */
124
  std::map<std::pair<long, size_t>, poly::Variable> d_varCoCoA;
125
126
  /**
127
   * The minimal polynomials p_i used for constructing d_K.
128
   * If a variable x_i has a rational assignment, p_i holds no value (i.e.
129
   * d_p[i] == CoCoA::RingElem()).
130
   */
131
  std::vector<CoCoA::RingElem> d_p;
132
133
  /**
134
   * The sequence of extension fields.
135
   * K_0 = Q, K_{i+1} = K_i[x_i]/<p_i>
136
   * Every K_i is a field.
137
   */
138
  std::vector<CoCoA::ring> d_K = {CoCoA::RingQQ()};
139
  /**
140
   * R_i = K_i[x_i]
141
   * Every R_i is a univariate polynomial ring over the field K_i.
142
   */
143
  std::vector<CoCoA::ring> d_R;
144
  /**
145
   * J_i = K_i[x_i, ..., x_n]
146
   * All J_i are constructed with CoCoA::lex ordering, just to make sure that
147
   * the Gröbner basis of J_0 is computed as necessary.
148
   */
149
  std::vector<CoCoA::ring> d_J;
150
151
  /**
152
   * Custom homomorphism from R_i to J_i. PolyAlgebraHom with
153
   * Indets(R_i) = (x_i) --> (x_i)
154
   */
155
  std::vector<CoCoA::RingHom> d_phom;
156
  /**
157
   * Custom homomorphism from J_i to J_{i+1}
158
   * If assignment of x_i is rational a PolyAlgebraHom with
159
   * Indets(J_i) = (x_i,...,x_n) --> (a_i,x_{i+1},...,x_n)
160
   * Otherwise a PolyRingHom with:
161
   * - CoeffHom: K_{i-1} --> R_{i-1} --> K_i
162
   * - (x_i,...,x_n) --> (x_i,x_{i+1},...,x_n), x_i = Indet(R_{i-1})
163
   */
164
  std::vector<CoCoA::RingHom> d_qhom;
165
166
  /**
167
   * The base ideal for the Gröbner basis we compute in the end. Contains all
168
   * p_i pushed into J_0.
169
   */
170
  std::vector<CoCoA::RingElem> d_GBBaseIdeal;
171
172
  /**
173
   * The current assignment, used to identify the vanishing factor to construct
174
   * K_i.
175
   */
176
  poly::Assignment d_assignment;
177
  /**
178
   * The libpoly variables in proper order. Directly correspond to x_0,...,x_n.
179
   */
180
  std::vector<poly::Variable> d_variables;
181
  /**
182
   * Direct assignments for variables x_i as polynomials in lower variables.
183
   * If the assignment for x_i is no direct assignment, d_direct[i] holds no
184
   * value.
185
   */
186
  std::vector<std::optional<CoCoA::RingElem>> d_direct;
187
188
  LazardEvaluationState()
189
  {
190
    if (!d_stats)
191
    {
192
      d_stats = std::make_unique<LazardEvaluationStats>();
193
    }
194
  }
195
196
  /**
197
   * Converts a libpoly integer to a CoCoA::BigInt.
198
   */
199
  CoCoA::BigInt convert(const poly::Integer& i) const
200
  {
201
    return CoCoA::BigIntFromMPZ(poly::detail::cast_to_gmp(&i)->get_mpz_t());
202
  }
203
  /**
204
   * Converts a libpoly dyadic rational to a CoCoA::BigRat.
205
   */
206
  CoCoA::BigRat convert(const poly::DyadicRational& dr) const
207
  {
208
    return CoCoA::BigRat(convert(poly::numerator(dr)),
209
                         convert(poly::denominator(dr)));
210
  }
211
  /**
212
   * Converts a libpoly rational to a CoCoA::BigRat.
213
   */
214
  CoCoA::BigRat convert(const poly::Rational& r) const
215
  {
216
    return CoCoA::BigRatFromMPQ(poly::detail::cast_to_gmp(&r)->get_mpq_t());
217
  }
218
  /**
219
   * Converts a univariate libpoly polynomial p in variable var to CoCoA. It
220
   * assumes that p is a minimal polynomial p_i over variable x_i for the
221
   * highest variable x_i known yet. It thus directly constructs p_i in R_i.
222
   */
223
  CoCoA::RingElem convertMiPo(const poly::UPolynomial& p,
224
                              const poly::Variable& var) const
225
  {
226
    std::vector<poly::Integer> coeffs = poly::coefficients(p);
227
    CoCoA::RingElem res(d_R.back());
228
    CoCoA::RingElem v = CoCoA::indet(d_R.back(), 0);
229
    CoCoA::RingElem mult(d_R.back(), 1);
230
    for (const auto& c : coeffs)
231
    {
232
      if (!poly::is_zero(c))
233
      {
234
        res += convert(c) * mult;
235
      }
236
      mult *= v;
237
    }
238
    return res;
239
  }
240
241
  /**
242
   * Checks whether the given CoCoA polynomial evaluates to zero over the
243
   * current libpoly assignment. The polynomial should live over the current
244
   * R_i.
245
   */
246
  bool evaluatesToZero(const CoCoA::RingElem& cp) const
247
  {
248
    Assert(CoCoA::owner(cp) == d_R.back());
249
    poly::Polynomial pp = convert(cp);
250
    return poly::evaluate_constraint(pp, d_assignment, poly::SignCondition::EQ);
251
  }
252
253
  /**
254
   * Maps p from J_i to J_{i-1}. There can be no suitable homomorphism, and we
255
   * thus manually decompose p into its terms and reconstruct them in J_{i-1}.
256
   * If a_{i-1} is rational, we know that the coefficient rings of J_i and
257
   * J_{i-1} are identical (K_{i-1} and K_{i-2}, respectively). We can thus
258
   * immediately use coefficients from J_i as coefficients in J_{i-1}.
259
   * Otherwise, we map coefficients from K_{i-1} to their canonical
260
   * representation in R_{i-1} and then use d_phom[i-1] to map those into
261
   * J_{i-1}. Afterwards, we iterate over the power product of the term
262
   * reconstruct it in J_{i-1}.
263
   */
264
  CoCoA::RingElem pushDownJ(const CoCoA::RingElem& p, size_t i) const
265
  {
266
    Trace("cad::lazard") << "Push " << p << " from " << d_J[i] << " to "
267
                         << d_J[i - 1] << std::endl;
268
    Assert(CoCoA::owner(p) == d_J[i]);
269
    CoCoA::RingElem res(d_J[i - 1]);
270
    for (CoCoA::SparsePolyIter it = CoCoA::BeginIter(p); !CoCoA::IsEnded(it);
271
         ++it)
272
    {
273
      CoCoA::RingElem coeff = CoCoA::coeff(it);
274
      Assert(CoCoA::owner(coeff) == d_K[i]);
275
      if (d_direct[i - 1])
276
      {
277
        Assert(CoCoA::CoeffRing(d_J[i]) == CoCoA::CoeffRing(d_J[i - 1]));
278
        coeff = CoCoA::CoeffEmbeddingHom(d_J[i - 1])(coeff);
279
      }
280
      else
281
      {
282
        coeff = CoCoA::CanonicalRepr(coeff);
283
        Assert(CoCoA::owner(coeff) == d_R[i - 1]);
284
        coeff = d_phom[i - 1](coeff);
285
      }
286
      Assert(CoCoA::owner(coeff) == d_J[i - 1]);
287
      auto pp = CoCoA::PP(it);
288
      std::vector<long> indets = CoCoA::IndetsIn(pp);
289
      for (size_t k = 0; k < indets.size(); ++k)
290
      {
291
        long exp = CoCoA::exponent(pp, indets[k]);
292
        auto ind = CoCoA::indet(d_J[i - 1], indets[k] + 1);
293
        coeff *= CoCoA::power(ind, exp);
294
      }
295
      res += coeff;
296
    }
297
    return res;
298
  }
299
300
  /**
301
   * Uses pushDownJ repeatedly to map p from J_{i+1} to J_0.
302
   * Is used to map the minimal polynomials p_i and the reduced polynomial q
303
   * into J_0 to eventually compute the Gröbner basis.
304
   */
305
  CoCoA::RingElem pushDownJ0(const CoCoA::RingElem& p, size_t i) const
306
  {
307
    CoCoA::RingElem res = p;
308
    for (; i > 0; --i)
309
    {
310
      Trace("cad::lazard") << "Pushing " << p << " from J" << i << " to J"
311
                           << i - 1 << std::endl;
312
      res = pushDownJ(res, i);
313
    }
314
    return res;
315
  }
316
317
  /**
318
   * Add the next R_i:
319
   * - add variable x_i to d_variables
320
   * - extract the variable name
321
   * - construct R_i = K_i[x_i]
322
   * - add new variable to d_varCoCoA
323
   */
324
  void addR(const poly::Variable& var)
325
  {
326
    d_variables.emplace_back(var);
327
    if (Trace.isOn("cad::lazard"))
328
    {
329
      std::string vname = lp_variable_db_get_name(
330
          poly::Context::get_context().get_variable_db(), var.get_internal());
331
      d_R.emplace_back(CoCoA::NewPolyRing(d_K.back(), {CoCoA::symbol(vname)}));
332
    }
333
    else
334
    {
335
      d_R.emplace_back(CoCoA::NewPolyRing(d_K.back(), {CoCoA::NewSymbol()}));
336
    }
337
    Trace("cad::lazard") << "R" << d_R.size() - 1 << " = " << d_R.back()
338
                         << std::endl;
339
    d_varCoCoA.emplace(std::make_pair(CoCoA::RingID(d_R.back()), 0), var);
340
  }
341
342
  /**
343
   * Add the next K_{i+1} from a minimal polynomial:
344
   * - store dummy value in d_direct
345
   * - store the minimal polynomial p_i in d_p
346
   * - construct K_{i+1} = R_i/<p_i>
347
   */
348
  void addK(const poly::Variable& var, const CoCoA::RingElem& p)
349
  {
350
    d_direct.emplace_back();
351
    d_p.emplace_back(p);
352
    Trace("cad::lazard") << "p" << d_p.size() - 1 << " = " << d_p.back()
353
                         << std::endl;
354
    d_K.emplace_back(CoCoA::NewQuotientRing(d_R.back(), CoCoA::ideal(p)));
355
    Trace("cad::lazard") << "K" << d_K.size() - 1 << " = " << d_K.back()
356
                         << std::endl;
357
  }
358
359
  /**
360
   * Add the next K_{i+1} from a rational assignment:
361
   * - store assignment a_i in d_direct
362
   * - store a dummy minimal polynomial in d_p
363
   * - construct K_{i+1} as copy of K_i
364
   */
365
  void addKRational(const poly::Variable& var, const CoCoA::RingElem& r)
366
  {
367
    d_direct.emplace_back(r);
368
    d_p.emplace_back();
369
    Trace("cad::lazard") << "x" << d_p.size() - 1 << " = " << r << std::endl;
370
    d_K.emplace_back(d_K.back());
371
    Trace("cad::lazard") << "K" << d_K.size() - 1 << " = " << d_K.back()
372
                         << std::endl;
373
  }
374
375
  /**
376
   * Finish the whole construction by adding the free variable:
377
   * - add R_n by calling addR(var)
378
   * - construct all J_i
379
   * - construct all p homomorphisms (R_i --> J_i)
380
   * - construct all q homomorphisms (J_i --> J_{i+1})
381
   * - fill the mapping d_varQ (libpoly -> J_0)
382
   * - fill the mapping d_varCoCoA (J_n -> libpoly)
383
   * - fill d_GBBaseIdeal with p_i mapped to J_0
384
   */
385
  void addFreeVariable(const poly::Variable& var)
386
  {
387
    Trace("cad::lazard") << "Add free variable " << var << std::endl;
388
    addR(var);
389
    std::vector<CoCoA::symbol> symbols;
390
    for (size_t i = 0; i < d_R.size(); ++i)
391
    {
392
      symbols.emplace_back(CoCoA::symbols(d_R[i]).back());
393
    }
394
    for (size_t i = 0; i < d_R.size(); ++i)
395
    {
396
      d_J.emplace_back(CoCoA::NewPolyRing(d_K[i], symbols, CoCoA::lex));
397
      Trace("cad::lazard") << "J" << d_J.size() - 1 << " = " << d_J.back()
398
                           << std::endl;
399
      symbols.erase(symbols.begin());
400
      // R_i --> J_i
401
      d_phom.emplace_back(
402
          CoCoA::PolyAlgebraHom(d_R[i], d_J[i], {CoCoA::indet(d_J[i], 0)}));
403
      Trace("cad::lazard") << "R" << i << " --> J" << i << ": " << d_phom.back()
404
                           << std::endl;
405
      if (i > 0)
406
      {
407
        Trace("cad::lazard")
408
            << "Constructing J" << i - 1 << " --> J" << i << ": " << std::endl;
409
        Trace("cad::lazard") << "Constructing " << d_J[i - 1] << " --> "
410
                             << d_J[i] << ": " << std::endl;
411
        if (d_direct[i - 1])
412
        {
413
          Trace("cad::lazard") << "Using " << d_variables[i - 1] << " for "
414
                               << CoCoA::indet(d_J[i - 1], 0) << std::endl;
415
          Assert(CoCoA::CoeffRing(d_J[i]) == CoCoA::owner(*d_direct[i - 1]));
416
          std::vector<CoCoA::RingElem> indets = {
417
              CoCoA::RingElem(d_J[i], *d_direct[i - 1])};
418
          for (size_t j = 0; j < d_R.size() - i; ++j)
419
          {
420
            indets.push_back(CoCoA::indet(d_J[i], j));
421
          }
422
          d_qhom.emplace_back(
423
              CoCoA::PolyAlgebraHom(d_J[i - 1], d_J[i], indets));
424
        }
425
        else
426
        {
427
          // K_{i-1} --> R_{i-1}
428
          auto K2R = CoCoA::CoeffEmbeddingHom(d_R[i - 1]);
429
          Assert(CoCoA::domain(K2R) == d_K[i - 1]);
430
          Assert(CoCoA::codomain(K2R) == d_R[i - 1]);
431
          // R_{i-1} --> K_i
432
          auto R2K = CoCoA::QuotientingHom(d_K[i]);
433
          Assert(CoCoA::domain(R2K) == d_R[i - 1]);
434
          Assert(CoCoA::codomain(R2K) == d_K[i]);
435
          // K_i --> J_i
436
          auto K2J = CoCoA::CoeffEmbeddingHom(d_J[i]);
437
          Assert(CoCoA::domain(K2J) == d_K[i]);
438
          Assert(CoCoA::codomain(K2J) == d_J[i]);
439
          // J_{i-1} --> J_i, consisting of
440
          // - a homomorphism for the coefficients
441
          // - a mapping for the indets
442
          // Constructs [phom_i(x_i), x_i+1, ..., x_n]
443
          std::vector<CoCoA::RingElem> indets = {
444
              K2J(R2K(CoCoA::indet(d_R[i - 1], 0)))};
445
          for (size_t j = 0; j < d_R.size() - i; ++j)
446
          {
447
            indets.push_back(CoCoA::indet(d_J[i], j));
448
          }
449
          d_qhom.emplace_back(
450
              CoCoA::PolyRingHom(d_J[i - 1], d_J[i], R2K(K2R), indets));
451
        }
452
        Trace("cad::lazard") << "J" << i - 1 << " --> J" << i << ": "
453
                             << d_qhom.back() << std::endl;
454
      }
455
    }
456
    for (size_t i = 0; i < d_variables.size(); ++i)
457
    {
458
      d_varQ.emplace(d_variables[i], CoCoA::indet(d_J[0], i));
459
    }
460
    for (size_t i = 0; i < d_variables.size(); ++i)
461
    {
462
      d_varCoCoA.emplace(std::make_pair(CoCoA::RingID(d_J[0]), i),
463
                         d_variables[i]);
464
    }
465
466
    d_GBBaseIdeal.clear();
467
    for (size_t i = 0; i < d_p.size(); ++i)
468
    {
469
      if (d_direct[i]) continue;
470
      Trace("cad::lazard") << "Apply " << d_phom[i] << " to " << d_p[i]
471
                           << " from " << CoCoA::owner(d_p[i]) << std::endl;
472
      d_GBBaseIdeal.emplace_back(pushDownJ0(d_phom[i](d_p[i]), i));
473
    }
474
475
    Trace("cad::lazard") << "Finished construction" << std::endl
476
                         << *this << std::endl;
477
  }
478
479
  /**
480
   * Helper class for conversion from libpoly to CoCoA polynomials.
481
   * The lambda can not capture anything, as it needs to be of type
482
   * lp_polynomial_traverse_f.
483
   */
484
  struct CoCoAPolyConstructor
485
  {
486
    const LazardEvaluationState& d_state;
487
    CoCoA::RingElem d_result;
488
  };
489
490
  /**
491
   * Convert the polynomial q to CoCoA into J_0.
492
   */
493
  CoCoA::RingElem convertQ(const poly::Polynomial& q) const
494
  {
495
    CoCoAPolyConstructor cmd{*this};
496
    // Do the actual conversion
497
    cmd.d_result = CoCoA::RingElem(d_J[0]);
498
    lp_polynomial_traverse_f f = [](const lp_polynomial_context_t* ctx,
499
                                    lp_monomial_t* m,
500
                                    void* data) {
501
      CoCoAPolyConstructor* d = static_cast<CoCoAPolyConstructor*>(data);
502
      CoCoA::BigInt coeff = d->d_state.convert(*poly::detail::cast_from(&m->a));
503
      CoCoA::RingElem re(d->d_state.d_J[0], coeff);
504
      for (size_t i = 0; i < m->n; ++i)
505
      {
506
        // variable exponent pair
507
        CoCoA::RingElem var = d->d_state.d_varQ.at(m->p[i].x);
508
        re *= CoCoA::power(var, m->p[i].d);
509
      }
510
      d->d_result += re;
511
    };
512
    lp_polynomial_traverse(q.get_internal(), f, &cmd);
513
    return cmd.d_result;
514
  }
515
  /**
516
   * Actual (recursive) implementation of converting a CoCoA polynomial to a
517
   * libpoly polynomial. As libpoly polynomials only have integer coefficients,
518
   * we need to maintain an integer denominator to normalize all terms to the
519
   * same denominator.
520
   */
521
  poly::Polynomial convertImpl(const CoCoA::RingElem& p,
522
                               poly::Integer& denominator) const
523
  {
524
    Trace("cad::lazard") << "Converting " << p << std::endl;
525
    denominator = poly::Integer(1);
526
    poly::Polynomial res;
527
    for (CoCoA::SparsePolyIter i = CoCoA::BeginIter(p); !CoCoA::IsEnded(i); ++i)
528
    {
529
      poly::Polynomial coeff;
530
      poly::Integer denom(1);
531
      CoCoA::BigRat numcoeff;
532
      if (CoCoA::IsRational(numcoeff, CoCoA::coeff(i)))
533
      {
534
        poly::Rational rat(mpq_class(CoCoA::mpqref(numcoeff)));
535
        denom = poly::denominator(rat);
536
        coeff = poly::numerator(rat);
537
      }
538
      else
539
      {
540
        coeff = convertImpl(CoCoA::CanonicalRepr(CoCoA::coeff(i)), denom);
541
      }
542
      if (!CoCoA::IsOne(CoCoA::PP(i)))
543
      {
544
        std::vector<long> exponents;
545
        CoCoA::exponents(exponents, CoCoA::PP(i));
546
        for (size_t vid = 0; vid < exponents.size(); ++vid)
547
        {
548
          if (exponents[vid] == 0) continue;
549
          const auto& ring = CoCoA::owner(p);
550
          poly::Variable v =
551
              d_varCoCoA.at(std::make_pair(CoCoA::RingID(ring), vid));
552
          coeff *= poly::Polynomial(poly::Integer(1), v, exponents[vid]);
553
        }
554
      }
555
      if (denom != denominator)
556
      {
557
        poly::Integer g = gcd(denom, denominator);
558
        res = res * (denom / g) + coeff * (denominator / g);
559
        denominator *= (denom / g);
560
      }
561
      else
562
      {
563
        res += coeff;
564
      }
565
    }
566
    Trace("cad::lazard") << "-> " << res << std::endl;
567
    return res;
568
  }
569
  /**
570
   * Actually convert a CoCoA RingElem to a libpoly polynomial.
571
   * Requires d_varCoCoA to be filled appropriately.
572
   */
573
  poly::Polynomial convert(const CoCoA::RingElem& p) const
574
  {
575
    poly::Integer denom;
576
    return convertImpl(p, denom);
577
  }
578
579
  /**
580
   * Now reduce the polynomial qpoly:
581
   * - convert qpoly into J_0 and factor it
582
   * - for every factor q:
583
   *   - for every x_i:
584
   *     - if a_i is rational:
585
   *       - while q[x_i -> a_i] == 0
586
   *         - q = q / (x_i - a_i)
587
   *       - set q = q[x_i -> a_i]
588
   *     - otherwise
589
   *       - obtain tmp = phom_i(p_i)
590
   *       - while tmp divides q
591
   *         - q = q / tmp
592
   *     - embed q = qhom_i(q)
593
   * - compute (reduced) GBasis(p_0, ..., p_{n-i}, q)
594
   * - collect and convert basis elements univariate in the free variable
595
   */
596
  std::vector<poly::Polynomial> reduce(const poly::Polynomial& qpoly) const
597
  {
598
    d_stats->d_evaluations++;
599
    std::vector<poly::Polynomial> res;
600
    Trace("cad::lazard") << "Reducing " << qpoly << std::endl;
601
    auto input = convertQ(qpoly);
602
    Assert(CoCoA::owner(input) == d_J[0]);
603
    auto factorization = CoCoA::factor(input);
604
    for (const auto& f : factorization.myFactors())
605
    {
606
      Trace("cad::lazard") << "-> factor " << f << std::endl;
607
      CoCoA::RingElem q = f;
608
      for (size_t i = 0; i < d_J.size() - 1; ++i)
609
      {
610
        Trace("cad::lazard") << "i = " << i << std::endl;
611
        if (d_direct[i])
612
        {
613
          Trace("cad::lazard")
614
              << "Substitute " << d_variables[i] << " = " << *d_direct[i]
615
              << " into " << q << " from " << CoCoA::owner(q) << std::endl;
616
          auto indets = CoCoA::indets(d_J[i]);
617
          auto var = indets[0];
618
          Assert(CoCoA::CoeffRing(d_J[i]) == CoCoA::owner(*d_direct[i]));
619
          indets[0] = CoCoA::RingElem(d_J[i], *d_direct[i]);
620
          auto hom = CoCoA::PolyAlgebraHom(d_J[i], d_J[i], indets);
621
          while (CoCoA::IsZero(hom(q)))
622
          {
623
            q = q / (var - indets[0]);
624
            d_stats->d_reductions++;
625
          }
626
          // substitute x_i -> a_i
627
          q = hom(q);
628
          Trace("cad::lazard")
629
              << "-> " << q << " from " << CoCoA::owner(q) << std::endl;
630
        }
631
        else
632
        {
633
          auto tmp = d_phom[i](d_p[i]);
634
          while (CoCoA::IsDivisible(q, tmp))
635
          {
636
            q /= tmp;
637
            d_stats->d_reductions++;
638
          }
639
        }
640
        q = d_qhom[i](q);
641
      }
642
      Trace("cad::lazard") << "-> reduced to " << q << std::endl;
643
      Assert(CoCoA::owner(q) == d_J.back());
644
      std::vector<CoCoA::RingElem> ideal = d_GBBaseIdeal;
645
      ideal.emplace_back(pushDownJ0(q, d_J.size() - 1));
646
      Trace("cad::lazard") << "-> ideal " << ideal << std::endl;
647
      auto basis = CoCoA::ReducedGBasis(CoCoA::ideal(ideal));
648
      Trace("cad::lazard") << "-> basis " << basis << std::endl;
649
      for (const auto& belem : basis)
650
      {
651
        Trace("cad::lazard") << "-> retrieved " << belem << std::endl;
652
        auto pres = convert(belem);
653
        Trace("cad::lazard") << "-> converted " << pres << std::endl;
654
        // These checks are orthogonal!
655
        if (poly::is_univariate(pres)
656
            && poly::is_univariate_over_assignment(pres, d_assignment))
657
        {
658
          res.emplace_back(pres);
659
        }
660
      }
661
    }
662
    return res;
663
  }
664
};
665
666
std::ostream& operator<<(std::ostream& os, const LazardEvaluationState& state)
667
{
668
  for (size_t i = 0; i < state.d_K.size(); ++i)
669
  {
670
    os << "K" << i << " = " << state.d_K[i] << std::endl;
671
    os << "R" << i << " = " << state.d_R[i] << std::endl;
672
    os << "J" << i << " = " << state.d_J[i] << std::endl;
673
674
    os << "R" << i << " --> J" << i << ": " << state.d_phom[i] << std::endl;
675
    if (i > 0)
676
    {
677
      os << "J" << (i - 1) << " --> J" << i << ": " << state.d_qhom[i - 1]
678
         << std::endl;
679
    }
680
  }
681
  os << "GBBaseIdeal: " << state.d_GBBaseIdeal << std::endl;
682
  os << "Done" << std::endl;
683
  return os;
684
}
685
std::unique_ptr<LazardEvaluationStats> LazardEvaluationState::d_stats;
686
687
LazardEvaluation::LazardEvaluation()
688
    : d_state(std::make_unique<LazardEvaluationState>())
689
{
690
}
691
692
LazardEvaluation::~LazardEvaluation() {}
693
694
/**
695
 * Add a new variable with real algebraic number:
696
 * - add var = ran to the assignment
697
 * - add the next R_i by calling addR(var)
698
 * - if ran is actually rational:
699
 *   - obtain the rational and call addKRational()
700
 * - otherwise:
701
 *   - convert the minimal polynomial and identify vanishing factor
702
 *   - add the next K_i with the vanishing factor by valling addK()
703
 */
704
void LazardEvaluation::add(const poly::Variable& var, const poly::Value& val)
705
{
706
  Trace("cad::lazard") << "Adding " << var << " -> " << val << std::endl;
707
  try
708
  {
709
    d_state->d_assignment.set(var, val);
710
    d_state->addR(var);
711
712
    std::optional<CoCoA::BigRat> rational;
713
    poly::UPolynomial polymipo;
714
    if (poly::is_algebraic_number(val))
715
    {
716
      const poly::AlgebraicNumber& ran = poly::as_algebraic_number(val);
717
      const poly::DyadicInterval& di = poly::get_isolating_interval(ran);
718
      if (poly::is_point(di))
719
      {
720
        rational = d_state->convert(poly::get_point(di));
721
      }
722
      else
723
      {
724
        Trace("cad::lazard") << "\tis proper ran" << std::endl;
725
        polymipo = poly::get_defining_polynomial(ran);
726
      }
727
    }
728
    else
729
    {
730
      Assert(poly::is_dyadic_rational(val) || poly::is_integer(val)
731
             || poly::is_rational(val));
732
      if (poly::is_dyadic_rational(val))
733
      {
734
        rational = d_state->convert(poly::as_dyadic_rational(val));
735
      }
736
      else if (poly::is_integer(val))
737
      {
738
        rational = CoCoA::BigRat(d_state->convert(poly::as_integer(val)), 1);
739
      }
740
      else if (poly::is_rational(val))
741
      {
742
        rational = d_state->convert(poly::as_rational(val));
743
      }
744
    }
745
746
    if (rational)
747
    {
748
      d_state->addKRational(var,
749
                            CoCoA::RingElem(d_state->d_K.back(), *rational));
750
      d_state->d_stats->d_directAssignments++;
751
      return;
752
    }
753
    Trace("cad::lazard") << "Got mipo " << polymipo << std::endl;
754
    auto mipo = d_state->convertMiPo(polymipo, var);
755
    Trace("cad::lazard") << "Factoring " << mipo << " from "
756
                         << CoCoA::owner(mipo) << std::endl;
757
    auto factorization = CoCoA::factor(mipo);
758
    Trace("cad::lazard") << "-> " << factorization << std::endl;
759
    bool used_factor = false;
760
    for (const auto& f : factorization.myFactors())
761
    {
762
      if (d_state->evaluatesToZero(f))
763
      {
764
        Assert(CoCoA::deg(f) > 0 && CoCoA::NumTerms(f) <= 2);
765
        if (CoCoA::deg(f) == 1)
766
        {
767
          auto rat = -CoCoA::ConstantCoeff(f) / CoCoA::LC(f);
768
          Trace("cad::lazard") << "Using linear factor " << f << " -> " << var
769
                               << " = " << rat << std::endl;
770
          d_state->addKRational(var, rat);
771
          d_state->d_stats->d_directAssignments++;
772
        }
773
        else
774
        {
775
          Trace("cad::lazard") << "Using nonlinear factor " << f << std::endl;
776
          d_state->addK(var, f);
777
          d_state->d_stats->d_ranAssignments++;
778
        }
779
        used_factor = true;
780
        break;
781
      }
782
      else
783
      {
784
        Trace("cad::lazard") << "Skipping " << f << std::endl;
785
      }
786
    }
787
    Assert(used_factor);
788
  }
789
  catch (CoCoA::ErrorInfo& e)
790
  {
791
    e.myOutputSelf(std::cerr);
792
    throw;
793
  }
794
}
795
796
void LazardEvaluation::addFreeVariable(const poly::Variable& var)
797
{
798
  try
799
  {
800
    d_state->addFreeVariable(var);
801
  }
802
  catch (CoCoA::ErrorInfo& e)
803
  {
804
    e.myOutputSelf(std::cerr);
805
    throw;
806
  }
807
}
808
809
std::vector<poly::Polynomial> LazardEvaluation::reducePolynomial(
810
    const poly::Polynomial& p) const
811
{
812
  try
813
  {
814
    return d_state->reduce(p);
815
  }
816
  catch (CoCoA::ErrorInfo& e)
817
  {
818
    e.myOutputSelf(std::cerr);
819
    throw;
820
  }
821
  return {p};
822
}
823
824
/**
825
 * Compute the infeasible regions of the given polynomial according to a sign
826
 * condition. We first reduce the polynomial and isolate the real roots of every
827
 * resulting polynomial. We store all roots (except for -infty, +infty and none)
828
 * in a set. Then, we transform the set of roots into a list of infeasible
829
 * regions by generating intervals between -infty and the first root, in between
830
 * every two consecutive roots and between the last root and +infty. While doing
831
 * this, we only keep those intervals that are actually infeasible for the
832
 * original polynomial q over the partial assignment. Finally, we go over the
833
 * intervals and aggregate consecutive intervals that connect.
834
 */
835
std::vector<poly::Interval> LazardEvaluation::infeasibleRegions(
836
    const poly::Polynomial& q, poly::SignCondition sc) const
837
{
838
  poly::Assignment a;
839
  std::set<poly::Value> roots;
840
  // reduce q to a set of reduced polynomials p
841
  for (const auto& p : reducePolynomial(q))
842
  {
843
    // collect all real roots except for -infty, none, +infty
844
    Trace("cad::lazard") << "Isolating roots of " << p << std::endl;
845
    Assert(poly::is_univariate(p) && poly::is_univariate_over_assignment(p, a));
846
    std::vector<poly::Value> proots = poly::isolate_real_roots(p, a);
847
    for (const auto& r : proots)
848
    {
849
      if (poly::is_minus_infinity(r)) continue;
850
      if (poly::is_none(r)) continue;
851
      if (poly::is_plus_infinity(r)) continue;
852
      roots.insert(r);
853
    }
854
  }
855
856
  // generate all intervals
857
  // (-infty,root_0), [root_0], (root_0,root_1), ..., [root_m], (root_m,+infty)
858
  // if q is true over d_assignment x interval (represented by a sample)
859
  std::vector<poly::Interval> res;
860
  poly::Value last = poly::Value::minus_infty();
861
  for (const auto& r : roots)
862
  {
863
    poly::Value sample = poly::value_between(last, true, r, true);
864
    d_state->d_assignment.set(d_state->d_variables.back(), sample);
865
    if (!poly::evaluate_constraint(q, d_state->d_assignment, sc))
866
    {
867
      res.emplace_back(last, true, r, true);
868
    }
869
    d_state->d_assignment.set(d_state->d_variables.back(), r);
870
    if (!poly::evaluate_constraint(q, d_state->d_assignment, sc))
871
    {
872
      res.emplace_back(r);
873
    }
874
    last = r;
875
  }
876
  poly::Value sample =
877
      poly::value_between(last, true, poly::Value::plus_infty(), true);
878
  d_state->d_assignment.set(d_state->d_variables.back(), sample);
879
  if (!poly::evaluate_constraint(q, d_state->d_assignment, sc))
880
  {
881
    res.emplace_back(last, true, poly::Value::plus_infty(), true);
882
  }
883
  // clean up assignment
884
  d_state->d_assignment.unset(d_state->d_variables.back());
885
886
  Trace("cad::lazard") << "Shrinking:" << std::endl;
887
  for (const auto& i : res)
888
  {
889
    Trace("cad::lazard") << "-> " << i << std::endl;
890
  }
891
  std::vector<poly::Interval> combined;
892
  if (res.empty())
893
  {
894
    // nothing to do if there are no intervals to start with
895
    // return combined to simplify return value optimization
896
    return combined;
897
  }
898
  for (size_t i = 0; i < res.size() - 1; ++i)
899
  {
900
    // Invariant: the intervals do not overlap. Check for our own sanity.
901
    Assert(poly::get_upper(res[i]) <= poly::get_lower(res[i + 1]));
902
    if (poly::get_upper_open(res[i]) && poly::get_lower_open(res[i + 1]))
903
    {
904
      // does not connect, both are open
905
      combined.emplace_back(res[i]);
906
      continue;
907
    }
908
    if (poly::get_upper(res[i]) != poly::get_lower(res[i + 1]))
909
    {
910
      // does not connect, there is some space in between
911
      combined.emplace_back(res[i]);
912
      continue;
913
    }
914
    // combine them into res[i+1], do not copy res[i] over to combined
915
    Trace("cad::lazard") << "Combine " << res[i] << " and " << res[i + 1]
916
                         << std::endl;
917
    Assert(poly::get_lower(res[i]) <= poly::get_lower(res[i + 1]));
918
    res[i + 1].set_lower(poly::get_lower(res[i]), poly::get_lower_open(res[i]));
919
  }
920
921
  // always use the last one, it is never dropped
922
  combined.emplace_back(res.back());
923
  Trace("cad::lazard") << "To:" << std::endl;
924
  for (const auto& i : combined)
925
  {
926
    Trace("cad::lazard") << "-> " << i << std::endl;
927
  }
928
  return combined;
929
}
930
931
}  // namespace cvc5::theory::arith::nl::cad
932
933
#else
934
935
namespace cvc5::theory::arith::nl::cad {
936
937
/**
938
 * Do a very simple wrapper around the regular poly::infeasible_regions.
939
 * Warn the user about doing this.
940
 * This allows for a graceful fallback (albeit with a warning) if CoCoA is not
941
 * available.
942
 */
943
446
struct LazardEvaluationState
944
{
945
  poly::Assignment d_assignment;
946
};
947
223
LazardEvaluation::LazardEvaluation()
948
223
    : d_state(std::make_unique<LazardEvaluationState>())
949
{
950
223
}
951
223
LazardEvaluation::~LazardEvaluation() {}
952
953
void LazardEvaluation::add(const poly::Variable& var, const poly::Value& val)
954
{
955
  d_state->d_assignment.set(var, val);
956
}
957
958
void LazardEvaluation::addFreeVariable(const poly::Variable& var) {}
959
960
std::vector<poly::Polynomial> LazardEvaluation::reducePolynomial(
961
    const poly::Polynomial& p) const
962
{
963
  return {p};
964
}
965
std::vector<poly::Interval> LazardEvaluation::infeasibleRegions(
966
    const poly::Polynomial& q, poly::SignCondition sc) const
967
{
968
  WarningOnce()
969
      << "CAD::LazardEvaluation is disabled because CoCoA is not available. "
970
         "Falling back to regular calculation of infeasible regions."
971
      << std::endl;
972
  return poly::infeasible_regions(q, d_state->d_assignment, sc);
973
}
974
975
29502
}  // namespace cvc5::theory::arith::nl::cad
976
977
#endif
978
#endif