032565d3d8c975dd121954d1e95e01e1aa2e8c80
[cvc5.git] / src / theory / arith / nl / cad / lazard_evaluation.cpp
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 std::vector<poly::Value> LazardEvaluation::isolateRealRoots(
825 const poly::Polynomial& q) const
826 {
827 poly::Assignment a;
828 std::vector<poly::Value> roots;
829 // reduce q to a set of reduced polynomials p
830 for (const auto& p : reducePolynomial(q))
831 {
832 // collect all real roots except for -infty, none, +infty
833 Trace("cad::lazard") << "Isolating roots of " << p << std::endl;
834 Assert(poly::is_univariate(p) && poly::is_univariate_over_assignment(p, a));
835 std::vector<poly::Value> proots = poly::isolate_real_roots(p, a);
836 for (const auto& r : proots)
837 {
838 if (poly::is_minus_infinity(r)) continue;
839 if (poly::is_none(r)) continue;
840 if (poly::is_plus_infinity(r)) continue;
841 roots.emplace_back(r);
842 }
843 }
844 std::sort(roots.begin(), roots.end());
845 return roots;
846 }
847
848 /**
849 * Compute the infeasible regions of the given polynomial according to a sign
850 * condition. We first reduce the polynomial and isolate the real roots of every
851 * resulting polynomial. We store all roots (except for -infty, +infty and none)
852 * in a set. Then, we transform the set of roots into a list of infeasible
853 * regions by generating intervals between -infty and the first root, in between
854 * every two consecutive roots and between the last root and +infty. While doing
855 * this, we only keep those intervals that are actually infeasible for the
856 * original polynomial q over the partial assignment. Finally, we go over the
857 * intervals and aggregate consecutive intervals that connect.
858 */
859 std::vector<poly::Interval> LazardEvaluation::infeasibleRegions(
860 const poly::Polynomial& q, poly::SignCondition sc) const
861 {
862 std::vector<poly::Value> roots = isolateRealRoots(q);
863
864 // generate all intervals
865 // (-infty,root_0), [root_0], (root_0,root_1), ..., [root_m], (root_m,+infty)
866 // if q is true over d_assignment x interval (represented by a sample)
867 std::vector<poly::Interval> res;
868 poly::Value last = poly::Value::minus_infty();
869 for (const auto& r : roots)
870 {
871 poly::Value sample = poly::value_between(last, true, r, true);
872 d_state->d_assignment.set(d_state->d_variables.back(), sample);
873 if (!poly::evaluate_constraint(q, d_state->d_assignment, sc))
874 {
875 res.emplace_back(last, true, r, true);
876 }
877 d_state->d_assignment.set(d_state->d_variables.back(), r);
878 if (!poly::evaluate_constraint(q, d_state->d_assignment, sc))
879 {
880 res.emplace_back(r);
881 }
882 last = r;
883 }
884 poly::Value sample =
885 poly::value_between(last, true, poly::Value::plus_infty(), true);
886 d_state->d_assignment.set(d_state->d_variables.back(), sample);
887 if (!poly::evaluate_constraint(q, d_state->d_assignment, sc))
888 {
889 res.emplace_back(last, true, poly::Value::plus_infty(), true);
890 }
891 // clean up assignment
892 d_state->d_assignment.unset(d_state->d_variables.back());
893
894 Trace("cad::lazard") << "Shrinking:" << std::endl;
895 for (const auto& i : res)
896 {
897 Trace("cad::lazard") << "-> " << i << std::endl;
898 }
899 std::vector<poly::Interval> combined;
900 if (res.empty())
901 {
902 // nothing to do if there are no intervals to start with
903 // return combined to simplify return value optimization
904 return combined;
905 }
906 for (size_t i = 0; i < res.size() - 1; ++i)
907 {
908 // Invariant: the intervals do not overlap. Check for our own sanity.
909 Assert(poly::get_upper(res[i]) <= poly::get_lower(res[i + 1]));
910 if (poly::get_upper_open(res[i]) && poly::get_lower_open(res[i + 1]))
911 {
912 // does not connect, both are open
913 combined.emplace_back(res[i]);
914 continue;
915 }
916 if (poly::get_upper(res[i]) != poly::get_lower(res[i + 1]))
917 {
918 // does not connect, there is some space in between
919 combined.emplace_back(res[i]);
920 continue;
921 }
922 // combine them into res[i+1], do not copy res[i] over to combined
923 Trace("cad::lazard") << "Combine " << res[i] << " and " << res[i + 1]
924 << std::endl;
925 Assert(poly::get_lower(res[i]) <= poly::get_lower(res[i + 1]));
926 res[i + 1].set_lower(poly::get_lower(res[i]), poly::get_lower_open(res[i]));
927 }
928
929 // always use the last one, it is never dropped
930 combined.emplace_back(res.back());
931 Trace("cad::lazard") << "To:" << std::endl;
932 for (const auto& i : combined)
933 {
934 Trace("cad::lazard") << "-> " << i << std::endl;
935 }
936 return combined;
937 }
938
939 } // namespace cvc5::theory::arith::nl::cad
940
941 #else
942
943 namespace cvc5::theory::arith::nl::cad {
944
945 /**
946 * Do a very simple wrapper around the regular poly::infeasible_regions.
947 * Warn the user about doing this.
948 * This allows for a graceful fallback (albeit with a warning) if CoCoA is not
949 * available.
950 */
951 struct LazardEvaluationState
952 {
953 poly::Assignment d_assignment;
954 };
955 LazardEvaluation::LazardEvaluation()
956 : d_state(std::make_unique<LazardEvaluationState>())
957 {
958 }
959 LazardEvaluation::~LazardEvaluation() {}
960
961 void LazardEvaluation::add(const poly::Variable& var, const poly::Value& val)
962 {
963 d_state->d_assignment.set(var, val);
964 }
965
966 void LazardEvaluation::addFreeVariable(const poly::Variable& var) {}
967
968 std::vector<poly::Polynomial> LazardEvaluation::reducePolynomial(
969 const poly::Polynomial& p) const
970 {
971 return {p};
972 }
973
974 std::vector<poly::Value> LazardEvaluation::isolateRealRoots(
975 const poly::Polynomial& q) const
976 {
977 WarningOnce()
978 << "CAD::LazardEvaluation is disabled because CoCoA is not available. "
979 "Falling back to regular real root isolation."
980 << std::endl;
981 return poly::isolate_real_roots(q, d_state->d_assignment);
982 }
983 std::vector<poly::Interval> LazardEvaluation::infeasibleRegions(
984 const poly::Polynomial& q, poly::SignCondition sc) const
985 {
986 WarningOnce()
987 << "CAD::LazardEvaluation is disabled because CoCoA is not available. "
988 "Falling back to regular calculation of infeasible regions."
989 << std::endl;
990 return poly::infeasible_regions(q, d_state->d_assignment, sc);
991 }
992
993 } // namespace cvc5::theory::arith::nl::cad
994
995 #endif
996 #endif