GCC Code Coverage Report
Directory: . Exec Total Coverage
File: src/theory/arith/operator_elim.cpp Lines: 183 195 93.8 %
Date: 2021-11-05 Branches: 488 986 49.5 %

Line Exec Source
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