1 |
|
/****************************************************************************** |
2 |
|
* Top contributors (to current version): |
3 |
|
* Andrew Reynolds, Tim King, Morgan Deters |
4 |
|
* |
5 |
|
* This file is part of the cvc5 project. |
6 |
|
* |
7 |
|
* Copyright (c) 2009-2021 by the authors listed in the file AUTHORS |
8 |
|
* in the top-level source directory and their institutional affiliations. |
9 |
|
* All rights reserved. See the file COPYING in the top-level source |
10 |
|
* directory for licensing information. |
11 |
|
* **************************************************************************** |
12 |
|
* |
13 |
|
* Implementation of operator elimination for arithmetic. |
14 |
|
*/ |
15 |
|
|
16 |
|
#include "theory/arith/operator_elim.h" |
17 |
|
|
18 |
|
#include <sstream> |
19 |
|
|
20 |
|
#include "expr/attribute.h" |
21 |
|
#include "expr/bound_var_manager.h" |
22 |
|
#include "options/arith_options.h" |
23 |
|
#include "proof/conv_proof_generator.h" |
24 |
|
#include "smt/env.h" |
25 |
|
#include "smt/logic_exception.h" |
26 |
|
#include "theory/arith/arith_utilities.h" |
27 |
|
#include "theory/rewriter.h" |
28 |
|
#include "theory/theory.h" |
29 |
|
|
30 |
|
using namespace cvc5::kind; |
31 |
|
|
32 |
|
namespace cvc5 { |
33 |
|
namespace theory { |
34 |
|
namespace arith { |
35 |
|
|
36 |
|
/** |
37 |
|
* Attribute used for constructing unique bound variables that are binders |
38 |
|
* for witness terms below. In other words, this attribute maps nodes to |
39 |
|
* the bound variable of a witness term for eliminating that node. |
40 |
|
* |
41 |
|
* Notice we use the same attribute for most bound variables below, since using |
42 |
|
* a node itself is a sufficient cache key for constructing a bound variable. |
43 |
|
* The exception is to_int / is_int, which share a skolem based on their |
44 |
|
* argument. |
45 |
|
*/ |
46 |
|
struct ArithWitnessVarAttributeId |
47 |
|
{ |
48 |
|
}; |
49 |
|
using ArithWitnessVarAttribute = expr::Attribute<ArithWitnessVarAttributeId, Node>; |
50 |
|
/** |
51 |
|
* Similar to above, shared for to_int and is_int. This is used for introducing |
52 |
|
* an integer bound variable used to construct the witness term for t in the |
53 |
|
* contexts (to_int t) and (is_int t). |
54 |
|
*/ |
55 |
|
struct ToIntWitnessVarAttributeId |
56 |
|
{ |
57 |
|
}; |
58 |
|
using ToIntWitnessVarAttribute |
59 |
|
= expr::Attribute<ToIntWitnessVarAttributeId, Node>; |
60 |
|
|
61 |
15271 |
OperatorElim::OperatorElim(Env& env) |
62 |
15271 |
: EnvObj(env), EagerProofGenerator(d_env.getProofNodeManager()) |
63 |
|
{ |
64 |
15271 |
} |
65 |
|
|
66 |
1019 |
void OperatorElim::checkNonLinearLogic(Node term) |
67 |
|
{ |
68 |
1019 |
if (d_env.getLogicInfo().isLinear()) |
69 |
|
{ |
70 |
12 |
Trace("arith-logic") << "ERROR: Non-linear term in linear logic: " << term |
71 |
6 |
<< std::endl; |
72 |
12 |
std::stringstream serr; |
73 |
6 |
serr << "A non-linear fact was asserted to arithmetic in a linear logic." |
74 |
6 |
<< std::endl; |
75 |
6 |
serr << "The fact in question: " << term << std::endl; |
76 |
6 |
throw LogicException(serr.str()); |
77 |
|
} |
78 |
1013 |
} |
79 |
|
|
80 |
828282 |
TrustNode OperatorElim::eliminate(Node n, |
81 |
|
std::vector<SkolemLemma>& lems, |
82 |
|
bool partialOnly) |
83 |
|
{ |
84 |
828282 |
TConvProofGenerator* tg = nullptr; |
85 |
1656558 |
Node nn = eliminateOperators(n, lems, tg, partialOnly); |
86 |
828276 |
if (nn != n) |
87 |
|
{ |
88 |
2330 |
return TrustNode::mkTrustRewrite(n, nn, nullptr); |
89 |
|
} |
90 |
825946 |
return TrustNode::null(); |
91 |
|
} |
92 |
|
|
93 |
828282 |
Node OperatorElim::eliminateOperators(Node node, |
94 |
|
std::vector<SkolemLemma>& lems, |
95 |
|
TConvProofGenerator* tg, |
96 |
|
bool partialOnly) |
97 |
|
{ |
98 |
828282 |
NodeManager* nm = NodeManager::currentNM(); |
99 |
828282 |
BoundVarManager* bvm = nm->getBoundVarManager(); |
100 |
828282 |
Kind k = node.getKind(); |
101 |
828282 |
switch (k) |
102 |
|
{ |
103 |
|
case TANGENT: |
104 |
|
case COSECANT: |
105 |
|
case SECANT: |
106 |
|
case COTANGENT: |
107 |
|
{ |
108 |
|
// these are eliminated by rewriting |
109 |
|
return Rewriter::rewrite(node); |
110 |
|
break; |
111 |
|
} |
112 |
107 |
case TO_INTEGER: |
113 |
|
case IS_INTEGER: |
114 |
|
{ |
115 |
107 |
if (partialOnly) |
116 |
|
{ |
117 |
|
// not eliminating total operators |
118 |
|
return node; |
119 |
|
} |
120 |
|
// node[0] - 1 < toIntSkolem <= node[0] |
121 |
|
// -1 < toIntSkolem - node[0] <= 0 |
122 |
|
// 0 <= node[0] - toIntSkolem < 1 |
123 |
|
Node v = |
124 |
214 |
bvm->mkBoundVar<ToIntWitnessVarAttribute>(node[0], nm->integerType()); |
125 |
214 |
Node one = mkRationalNode(1); |
126 |
214 |
Node zero = mkRationalNode(0); |
127 |
214 |
Node diff = nm->mkNode(MINUS, node[0], v); |
128 |
214 |
Node lem = mkInRange(diff, zero, one); |
129 |
|
Node toIntSkolem = |
130 |
|
mkWitnessTerm(v, |
131 |
|
lem, |
132 |
|
"toInt", |
133 |
|
"a conversion of a Real term to its Integer part", |
134 |
214 |
lems); |
135 |
107 |
if (k == IS_INTEGER) |
136 |
|
{ |
137 |
16 |
return nm->mkNode(EQUAL, node[0], toIntSkolem); |
138 |
|
} |
139 |
91 |
Assert(k == TO_INTEGER); |
140 |
91 |
return toIntSkolem; |
141 |
|
} |
142 |
|
|
143 |
1538 |
case INTS_DIVISION_TOTAL: |
144 |
|
case INTS_MODULUS_TOTAL: |
145 |
|
{ |
146 |
1538 |
if (partialOnly) |
147 |
|
{ |
148 |
|
// not eliminating total operators |
149 |
1568 |
return node; |
150 |
|
} |
151 |
1508 |
Node den = Rewriter::rewrite(node[1]); |
152 |
1508 |
Node num = Rewriter::rewrite(node[0]); |
153 |
1508 |
Node rw = nm->mkNode(k, num, den); |
154 |
1508 |
Node v = bvm->mkBoundVar<ArithWitnessVarAttribute>(rw, nm->integerType()); |
155 |
1508 |
Node lem; |
156 |
1508 |
Node leqNum = nm->mkNode(LEQ, nm->mkNode(MULT, den, v), num); |
157 |
1508 |
if (den.isConst()) |
158 |
|
{ |
159 |
1196 |
const Rational& rat = den.getConst<Rational>(); |
160 |
1196 |
if (num.isConst() || rat == 0) |
161 |
|
{ |
162 |
|
// just rewrite |
163 |
|
return Rewriter::rewrite(node); |
164 |
|
} |
165 |
1196 |
if (rat > 0) |
166 |
|
{ |
167 |
1196 |
lem = nm->mkNode( |
168 |
|
AND, |
169 |
|
leqNum, |
170 |
2392 |
nm->mkNode( |
171 |
|
LT, |
172 |
|
num, |
173 |
2392 |
nm->mkNode(MULT, |
174 |
|
den, |
175 |
2392 |
nm->mkNode(PLUS, v, nm->mkConst(Rational(1)))))); |
176 |
|
} |
177 |
|
else |
178 |
|
{ |
179 |
|
lem = nm->mkNode( |
180 |
|
AND, |
181 |
|
leqNum, |
182 |
|
nm->mkNode( |
183 |
|
LT, |
184 |
|
num, |
185 |
|
nm->mkNode(MULT, |
186 |
|
den, |
187 |
|
nm->mkNode(PLUS, v, nm->mkConst(Rational(-1)))))); |
188 |
|
} |
189 |
|
} |
190 |
|
else |
191 |
|
{ |
192 |
312 |
checkNonLinearLogic(node); |
193 |
936 |
lem = nm->mkNode( |
194 |
|
AND, |
195 |
1248 |
nm->mkNode( |
196 |
|
IMPLIES, |
197 |
624 |
nm->mkNode(GT, den, nm->mkConst(Rational(0))), |
198 |
624 |
nm->mkNode( |
199 |
|
AND, |
200 |
|
leqNum, |
201 |
624 |
nm->mkNode( |
202 |
|
LT, |
203 |
|
num, |
204 |
624 |
nm->mkNode( |
205 |
|
MULT, |
206 |
|
den, |
207 |
624 |
nm->mkNode(PLUS, v, nm->mkConst(Rational(1))))))), |
208 |
1248 |
nm->mkNode( |
209 |
|
IMPLIES, |
210 |
624 |
nm->mkNode(LT, den, nm->mkConst(Rational(0))), |
211 |
624 |
nm->mkNode( |
212 |
|
AND, |
213 |
|
leqNum, |
214 |
624 |
nm->mkNode( |
215 |
|
LT, |
216 |
|
num, |
217 |
624 |
nm->mkNode( |
218 |
|
MULT, |
219 |
|
den, |
220 |
624 |
nm->mkNode(PLUS, v, nm->mkConst(Rational(-1)))))))); |
221 |
|
} |
222 |
|
Node intVar = mkWitnessTerm( |
223 |
1508 |
v, lem, "linearIntDiv", "the result of an intdiv-by-k term", lems); |
224 |
1508 |
if (k == INTS_MODULUS_TOTAL) |
225 |
|
{ |
226 |
1676 |
Node nn = nm->mkNode(MINUS, num, nm->mkNode(MULT, den, intVar)); |
227 |
838 |
return nn; |
228 |
|
} |
229 |
|
else |
230 |
|
{ |
231 |
670 |
return intVar; |
232 |
|
} |
233 |
|
break; |
234 |
|
} |
235 |
161 |
case DIVISION_TOTAL: |
236 |
|
{ |
237 |
161 |
if (partialOnly) |
238 |
|
{ |
239 |
|
// not eliminating total operators |
240 |
2 |
return node; |
241 |
|
} |
242 |
318 |
Node num = Rewriter::rewrite(node[0]); |
243 |
318 |
Node den = Rewriter::rewrite(node[1]); |
244 |
159 |
if (den.isConst()) |
245 |
|
{ |
246 |
|
// No need to eliminate here, can eliminate via rewriting later. |
247 |
|
// Moreover, rewriting may change the type of this node from real to |
248 |
|
// int, which impacts certain issues with subtyping. |
249 |
|
return node; |
250 |
|
} |
251 |
159 |
checkNonLinearLogic(node); |
252 |
318 |
Node rw = nm->mkNode(k, num, den); |
253 |
318 |
Node v = bvm->mkBoundVar<ArithWitnessVarAttribute>(rw, nm->realType()); |
254 |
|
Node lem = nm->mkNode(IMPLIES, |
255 |
318 |
den.eqNode(nm->mkConst(Rational(0))).negate(), |
256 |
636 |
nm->mkNode(MULT, den, v).eqNode(num)); |
257 |
|
return mkWitnessTerm( |
258 |
159 |
v, lem, "nonlinearDiv", "the result of a non-linear div term", lems); |
259 |
|
break; |
260 |
|
} |
261 |
198 |
case DIVISION: |
262 |
|
{ |
263 |
396 |
Node num = Rewriter::rewrite(node[0]); |
264 |
396 |
Node den = Rewriter::rewrite(node[1]); |
265 |
396 |
Node ret = nm->mkNode(DIVISION_TOTAL, num, den); |
266 |
198 |
if (!den.isConst() || den.getConst<Rational>().sgn() == 0) |
267 |
|
{ |
268 |
201 |
checkNonLinearLogic(node); |
269 |
378 |
Node divByZeroNum = getArithSkolemApp(num, SkolemFunId::DIV_BY_ZERO); |
270 |
378 |
Node denEq0 = nm->mkNode(EQUAL, den, nm->mkConst(Rational(0))); |
271 |
189 |
ret = nm->mkNode(ITE, denEq0, divByZeroNum, ret); |
272 |
|
} |
273 |
192 |
return ret; |
274 |
|
break; |
275 |
|
} |
276 |
|
|
277 |
185 |
case INTS_DIVISION: |
278 |
|
{ |
279 |
|
// partial function: integer div |
280 |
370 |
Node num = Rewriter::rewrite(node[0]); |
281 |
370 |
Node den = Rewriter::rewrite(node[1]); |
282 |
370 |
Node ret = nm->mkNode(INTS_DIVISION_TOTAL, num, den); |
283 |
185 |
if (!den.isConst() || den.getConst<Rational>().sgn() == 0) |
284 |
|
{ |
285 |
185 |
checkNonLinearLogic(node); |
286 |
|
Node intDivByZeroNum = |
287 |
370 |
getArithSkolemApp(num, SkolemFunId::INT_DIV_BY_ZERO); |
288 |
370 |
Node denEq0 = nm->mkNode(EQUAL, den, nm->mkConst(Rational(0))); |
289 |
185 |
ret = nm->mkNode(ITE, denEq0, intDivByZeroNum, ret); |
290 |
|
} |
291 |
185 |
return ret; |
292 |
|
break; |
293 |
|
} |
294 |
|
|
295 |
112 |
case INTS_MODULUS: |
296 |
|
{ |
297 |
|
// partial function: mod |
298 |
224 |
Node num = Rewriter::rewrite(node[0]); |
299 |
224 |
Node den = Rewriter::rewrite(node[1]); |
300 |
224 |
Node ret = nm->mkNode(INTS_MODULUS_TOTAL, num, den); |
301 |
112 |
if (!den.isConst() || den.getConst<Rational>().sgn() == 0) |
302 |
|
{ |
303 |
112 |
checkNonLinearLogic(node); |
304 |
224 |
Node modZeroNum = getArithSkolemApp(num, SkolemFunId::MOD_BY_ZERO); |
305 |
224 |
Node denEq0 = nm->mkNode(EQUAL, den, nm->mkConst(Rational(0))); |
306 |
112 |
ret = nm->mkNode(ITE, denEq0, modZeroNum, ret); |
307 |
|
} |
308 |
112 |
return ret; |
309 |
|
break; |
310 |
|
} |
311 |
|
|
312 |
11 |
case ABS: |
313 |
|
{ |
314 |
|
return nm->mkNode(ITE, |
315 |
22 |
nm->mkNode(LT, node[0], nm->mkConst(Rational(0))), |
316 |
22 |
nm->mkNode(UMINUS, node[0]), |
317 |
55 |
node[0]); |
318 |
|
break; |
319 |
|
} |
320 |
58 |
case SQRT: |
321 |
|
case ARCSINE: |
322 |
|
case ARCCOSINE: |
323 |
|
case ARCTANGENT: |
324 |
|
case ARCCOSECANT: |
325 |
|
case ARCSECANT: |
326 |
|
case ARCCOTANGENT: |
327 |
|
{ |
328 |
58 |
if (partialOnly) |
329 |
|
{ |
330 |
|
// not eliminating total operators |
331 |
2 |
return node; |
332 |
|
} |
333 |
56 |
checkNonLinearLogic(node); |
334 |
|
// eliminate inverse functions here |
335 |
|
Node var = |
336 |
112 |
bvm->mkBoundVar<ArithWitnessVarAttribute>(node, nm->realType()); |
337 |
112 |
Node lem; |
338 |
56 |
if (k == SQRT) |
339 |
|
{ |
340 |
80 |
Node skolemApp = getArithSkolemApp(node[0], SkolemFunId::SQRT); |
341 |
80 |
Node uf = skolemApp.eqNode(var); |
342 |
|
Node nonNeg = |
343 |
80 |
nm->mkNode(AND, nm->mkNode(MULT, var, var).eqNode(node[0]), uf); |
344 |
|
|
345 |
|
// sqrt(x) reduces to: |
346 |
|
// witness y. ite(x >= 0.0, y * y = x ^ y = Uf(x), y = Uf(x)) |
347 |
|
// |
348 |
|
// Uf(x) makes sure that the reduction still behaves like a function, |
349 |
|
// otherwise the reduction of (x = 1) ^ (sqrt(x) != sqrt(1)) would be |
350 |
|
// satisfiable. On the original formula, this would require that we |
351 |
|
// simultaneously interpret sqrt(1) as 1 and -1, which is not a valid |
352 |
|
// model. |
353 |
120 |
lem = nm->mkNode(ITE, |
354 |
80 |
nm->mkNode(GEQ, node[0], nm->mkConst(Rational(0))), |
355 |
|
nonNeg, |
356 |
|
uf); |
357 |
|
} |
358 |
|
else |
359 |
|
{ |
360 |
32 |
Node pi = mkPi(); |
361 |
|
|
362 |
|
// range of the skolem |
363 |
32 |
Node rlem; |
364 |
16 |
if (k == ARCSINE || k == ARCTANGENT || k == ARCCOSECANT) |
365 |
|
{ |
366 |
24 |
Node half = nm->mkConst(Rational(1) / Rational(2)); |
367 |
24 |
Node pi2 = nm->mkNode(MULT, half, pi); |
368 |
24 |
Node npi2 = nm->mkNode(MULT, nm->mkConst(Rational(-1)), pi2); |
369 |
|
// -pi/2 < var <= pi/2 |
370 |
36 |
rlem = nm->mkNode( |
371 |
60 |
AND, nm->mkNode(LT, npi2, var), nm->mkNode(LEQ, var, pi2)); |
372 |
|
} |
373 |
|
else |
374 |
|
{ |
375 |
|
// 0 <= var < pi |
376 |
12 |
rlem = nm->mkNode(AND, |
377 |
8 |
nm->mkNode(LEQ, nm->mkConst(Rational(0)), var), |
378 |
8 |
nm->mkNode(LT, var, pi)); |
379 |
|
} |
380 |
|
|
381 |
16 |
Kind rk = |
382 |
|
k == ARCSINE |
383 |
28 |
? SINE |
384 |
|
: (k == ARCCOSINE |
385 |
20 |
? COSINE |
386 |
|
: (k == ARCTANGENT |
387 |
8 |
? TANGENT |
388 |
|
: (k == ARCCOSECANT |
389 |
|
? COSECANT |
390 |
|
: (k == ARCSECANT ? SECANT : COTANGENT)))); |
391 |
32 |
Node invTerm = nm->mkNode(rk, var); |
392 |
16 |
lem = nm->mkNode(AND, rlem, invTerm.eqNode(node[0])); |
393 |
|
} |
394 |
56 |
Assert(!lem.isNull()); |
395 |
|
return mkWitnessTerm( |
396 |
|
var, |
397 |
|
lem, |
398 |
|
"tfk", |
399 |
|
"Skolem to eliminate a non-standard transcendental function", |
400 |
56 |
lems); |
401 |
|
break; |
402 |
|
} |
403 |
|
|
404 |
825912 |
default: break; |
405 |
|
} |
406 |
825912 |
return node; |
407 |
|
} |
408 |
|
|
409 |
|
Node OperatorElim::getAxiomFor(Node n) { return Node::null(); } |
410 |
|
|
411 |
526 |
Node OperatorElim::getArithSkolem(SkolemFunId id) |
412 |
|
{ |
413 |
526 |
std::map<SkolemFunId, Node>::iterator it = d_arithSkolem.find(id); |
414 |
526 |
if (it == d_arithSkolem.end()) |
415 |
|
{ |
416 |
268 |
NodeManager* nm = NodeManager::currentNM(); |
417 |
536 |
TypeNode tn; |
418 |
268 |
if (id == SkolemFunId::DIV_BY_ZERO || id == SkolemFunId::SQRT) |
419 |
|
{ |
420 |
135 |
tn = nm->realType(); |
421 |
|
} |
422 |
|
else |
423 |
|
{ |
424 |
133 |
tn = nm->integerType(); |
425 |
|
} |
426 |
536 |
Node skolem; |
427 |
268 |
SkolemManager* sm = nm->getSkolemManager(); |
428 |
268 |
if (options().arith.arithNoPartialFun) |
429 |
|
{ |
430 |
|
// partial function: division, where we treat the skolem function as |
431 |
|
// a constant |
432 |
4 |
skolem = sm->mkSkolemFunction(id, tn); |
433 |
|
} |
434 |
|
else |
435 |
|
{ |
436 |
|
// partial function: division |
437 |
264 |
skolem = sm->mkSkolemFunction(id, nm->mkFunctionType(tn, tn)); |
438 |
|
} |
439 |
|
// cache it |
440 |
268 |
d_arithSkolem[id] = skolem; |
441 |
268 |
return skolem; |
442 |
|
} |
443 |
258 |
return it->second; |
444 |
|
} |
445 |
|
|
446 |
526 |
Node OperatorElim::getArithSkolemApp(Node n, SkolemFunId id) |
447 |
|
{ |
448 |
526 |
Node skolem = getArithSkolem(id); |
449 |
526 |
if (!options().arith.arithNoPartialFun) |
450 |
|
{ |
451 |
518 |
skolem = NodeManager::currentNM()->mkNode(APPLY_UF, skolem, n); |
452 |
|
} |
453 |
526 |
return skolem; |
454 |
|
} |
455 |
|
|
456 |
1830 |
Node OperatorElim::mkWitnessTerm(Node v, |
457 |
|
Node pred, |
458 |
|
const std::string& prefix, |
459 |
|
const std::string& comment, |
460 |
|
std::vector<SkolemLemma>& lems) |
461 |
|
{ |
462 |
1830 |
NodeManager* nm = NodeManager::currentNM(); |
463 |
1830 |
SkolemManager* sm = nm->getSkolemManager(); |
464 |
|
// we mark that we should send a lemma |
465 |
|
Node k = sm->mkSkolem( |
466 |
1830 |
v, pred, prefix, comment, SkolemManager::SKOLEM_DEFAULT, this); |
467 |
1830 |
if (d_pnm != nullptr) |
468 |
|
{ |
469 |
1658 |
Node lem = SkolemLemma::getSkolemLemmaFor(k); |
470 |
|
TrustNode tlem = |
471 |
1658 |
mkTrustNode(lem, PfRule::THEORY_PREPROCESS_LEMMA, {}, {lem}); |
472 |
829 |
lems.push_back(SkolemLemma(tlem, k)); |
473 |
|
} |
474 |
|
else |
475 |
|
{ |
476 |
1001 |
lems.push_back(SkolemLemma(k, nullptr)); |
477 |
|
} |
478 |
1830 |
return k; |
479 |
|
} |
480 |
|
|
481 |
|
} // namespace arith |
482 |
|
} // namespace theory |
483 |
31125 |
} // namespace cvc5 |