GCC Code Coverage Report
Directory: . Exec Total Coverage
File: src/theory/arith/operator_elim.cpp Lines: 179 195 91.8 %
Date: 2021-09-29 Branches: 472 984 48.0 %

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