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 |
434 |
struct LazardEvaluationState |
944 |
|
{ |
945 |
|
poly::Assignment d_assignment; |
946 |
|
}; |
947 |
217 |
LazardEvaluation::LazardEvaluation() |
948 |
217 |
: d_state(std::make_unique<LazardEvaluationState>()) |
949 |
|
{ |
950 |
217 |
} |
951 |
217 |
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 |
22755 |
} // namespace cvc5::theory::arith::nl::cad |
976 |
|
|
977 |
|
#endif |
978 |
|
#endif |