GCC Code Coverage Report
Directory: . Exec Total Coverage
File: src/theory/uf/ho_extension.cpp Lines: 204 246 82.9 %
Date: 2021-08-14 Branches: 423 1030 41.1 %

Line Exec Source
1
/******************************************************************************
2
 * Top contributors (to current version):
3
 *   Andrew Reynolds, Gereon Kremer, Mathias Preiner
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 the higher-order extension of TheoryUF.
14
 */
15
16
#include "theory/uf/ho_extension.h"
17
18
#include "expr/node_algorithm.h"
19
#include "expr/skolem_manager.h"
20
#include "options/uf_options.h"
21
#include "theory/theory_model.h"
22
#include "theory/uf/theory_uf_rewriter.h"
23
24
using namespace std;
25
using namespace cvc5::kind;
26
27
namespace cvc5 {
28
namespace theory {
29
namespace uf {
30
31
187
HoExtension::HoExtension(TheoryState& state, TheoryInferenceManager& im)
32
    : d_state(state),
33
      d_im(im),
34
187
      d_extensionality(state.getUserContext()),
35
374
      d_uf_std_skolem(state.getUserContext())
36
{
37
187
  d_true = NodeManager::currentNM()->mkConst(true);
38
187
}
39
40
284
Node HoExtension::ppRewrite(Node node)
41
{
42
  // convert HO_APPLY to APPLY_UF if fully applied
43
284
  if (node[0].getType().getNumChildren() == 2)
44
  {
45
113
    Trace("uf-ho") << "uf-ho : expanding definition : " << node << std::endl;
46
226
    Node ret = getApplyUfForHoApply(node);
47
226
    Trace("uf-ho") << "uf-ho : ppRewrite : " << node << " to " << ret
48
113
                   << std::endl;
49
113
    return ret;
50
  }
51
171
  return node;
52
}
53
54
822
Node HoExtension::getExtensionalityDeq(TNode deq, bool isCached)
55
{
56
822
  Assert(deq.getKind() == NOT && deq[0].getKind() == EQUAL);
57
822
  Assert(deq[0][0].getType().isFunction());
58
822
  if (isCached)
59
  {
60
276
    std::map<Node, Node>::iterator it = d_extensionality_deq.find(deq);
61
276
    if (it != d_extensionality_deq.end())
62
    {
63
      return it->second;
64
    }
65
  }
66
1644
  TypeNode tn = deq[0][0].getType();
67
1644
  std::vector<TypeNode> argTypes = tn.getArgTypes();
68
1644
  std::vector<Node> skolems;
69
822
  NodeManager* nm = NodeManager::currentNM();
70
822
  SkolemManager* sm = nm->getSkolemManager();
71
1690
  for (unsigned i = 0, nargs = argTypes.size(); i < nargs; i++)
72
  {
73
    Node k = sm->mkDummySkolem(
74
1736
        "k", argTypes[i], "skolem created for extensionality.");
75
868
    skolems.push_back(k);
76
  }
77
1644
  Node t[2];
78
2466
  for (unsigned i = 0; i < 2; i++)
79
  {
80
3288
    std::vector<Node> children;
81
3288
    Node curr = deq[0][i];
82
2750
    while (curr.getKind() == HO_APPLY)
83
    {
84
553
      children.push_back(curr[1]);
85
553
      curr = curr[0];
86
    }
87
1644
    children.push_back(curr);
88
1644
    std::reverse(children.begin(), children.end());
89
1644
    children.insert(children.end(), skolems.begin(), skolems.end());
90
1644
    t[i] = nm->mkNode(APPLY_UF, children);
91
  }
92
1644
  Node conc = t[0].eqNode(t[1]).negate();
93
822
  if (isCached)
94
  {
95
276
    d_extensionality_deq[deq] = conc;
96
  }
97
822
  return conc;
98
}
99
100
3925
unsigned HoExtension::applyExtensionality(TNode deq)
101
{
102
3925
  Assert(deq.getKind() == NOT && deq[0].getKind() == EQUAL);
103
3925
  Assert(deq[0][0].getType().isFunction());
104
  // apply extensionality
105
3925
  if (d_extensionality.find(deq) == d_extensionality.end())
106
  {
107
276
    d_extensionality.insert(deq);
108
552
    Node conc = getExtensionalityDeq(deq);
109
552
    Node lem = NodeManager::currentNM()->mkNode(OR, deq[0], conc);
110
552
    Trace("uf-ho-lemma") << "uf-ho-lemma : extensionality : " << lem
111
276
                         << std::endl;
112
276
    d_im.lemma(lem, InferenceId::UF_HO_EXTENSIONALITY);
113
276
    return 1;
114
  }
115
3649
  return 0;
116
}
117
118
113
Node HoExtension::getApplyUfForHoApply(Node node)
119
{
120
113
  Assert(node[0].getType().getNumChildren() == 2);
121
226
  std::vector<TNode> args;
122
226
  Node f = TheoryUfRewriter::decomposeHoApply(node, args, true);
123
226
  Node new_f = f;
124
113
  NodeManager* nm = NodeManager::currentNM();
125
113
  SkolemManager* sm = nm->getSkolemManager();
126
113
  if (!TheoryUfRewriter::canUseAsApplyUfOperator(f))
127
  {
128
2
    NodeNodeMap::const_iterator itus = d_uf_std_skolem.find(f);
129
2
    if (itus == d_uf_std_skolem.end())
130
    {
131
4
      std::unordered_set<Node> fvs;
132
2
      expr::getFreeVariables(f, fvs);
133
4
      Node lem;
134
2
      if (!fvs.empty())
135
      {
136
        std::vector<TypeNode> newTypes;
137
        std::vector<Node> vs;
138
        std::vector<Node> nvs;
139
        for (const Node& v : fvs)
140
        {
141
          TypeNode vt = v.getType();
142
          newTypes.push_back(vt);
143
          Node nv = nm->mkBoundVar(vt);
144
          vs.push_back(v);
145
          nvs.push_back(nv);
146
        }
147
        TypeNode ft = f.getType();
148
        std::vector<TypeNode> argTypes = ft.getArgTypes();
149
        TypeNode rangeType = ft.getRangeType();
150
151
        newTypes.insert(newTypes.end(), argTypes.begin(), argTypes.end());
152
        TypeNode nft = nm->mkFunctionType(newTypes, rangeType);
153
        new_f = sm->mkDummySkolem("app_uf", nft);
154
        for (const Node& v : vs)
155
        {
156
          new_f = nm->mkNode(HO_APPLY, new_f, v);
157
        }
158
        Assert(new_f.getType() == f.getType());
159
        Node eq = new_f.eqNode(f);
160
        Node seq = eq.substitute(vs.begin(), vs.end(), nvs.begin(), nvs.end());
161
        lem = nm->mkNode(
162
            FORALL, nm->mkNode(BOUND_VAR_LIST, nvs), seq);
163
      }
164
      else
165
      {
166
        // introduce skolem to make a standard APPLY_UF
167
2
        new_f = sm->mkDummySkolem("app_uf", f.getType());
168
2
        lem = new_f.eqNode(f);
169
      }
170
4
      Trace("uf-ho-lemma")
171
2
          << "uf-ho-lemma : Skolem definition for apply-conversion : " << lem
172
2
          << std::endl;
173
2
      d_im.lemma(lem, InferenceId::UF_HO_APP_CONV_SKOLEM);
174
2
      d_uf_std_skolem[f] = new_f;
175
    }
176
    else
177
    {
178
      new_f = (*itus).second;
179
    }
180
    // unroll the HO_APPLY, adding to the first argument position
181
    // Note arguments in the vector args begin at position 1.
182
2
    while (new_f.getKind() == HO_APPLY)
183
    {
184
      args.insert(args.begin() + 1, new_f[1]);
185
      new_f = new_f[0];
186
    }
187
  }
188
113
  Assert(TheoryUfRewriter::canUseAsApplyUfOperator(new_f));
189
113
  args[0] = new_f;
190
113
  Node ret = nm->mkNode(APPLY_UF, args);
191
113
  Assert(ret.getType() == node.getType());
192
226
  return ret;
193
}
194
195
1307
unsigned HoExtension::checkExtensionality(TheoryModel* m)
196
{
197
1307
  eq::EqualityEngine* ee = d_state.getEqualityEngine();
198
1307
  NodeManager* nm = NodeManager::currentNM();
199
1307
  unsigned num_lemmas = 0;
200
1307
  bool isCollectModel = (m != nullptr);
201
2614
  Trace("uf-ho") << "HoExtension::checkExtensionality, collectModel="
202
1307
                 << isCollectModel << "..." << std::endl;
203
2614
  std::map<TypeNode, std::vector<Node> > func_eqcs;
204
1307
  eq::EqClassesIterator eqcs_i = eq::EqClassesIterator(ee);
205
1307
  bool hasFunctions = false;
206
40569
  while (!eqcs_i.isFinished())
207
  {
208
39262
    Node eqc = (*eqcs_i);
209
39262
    TypeNode tn = eqc.getType();
210
19631
    if (tn.isFunction())
211
    {
212
4374
      hasFunctions = true;
213
      // if during collect model, must have an infinite type
214
      // if not during collect model, must have a finite type
215
4374
      if (d_state.isFiniteType(tn) != isCollectModel)
216
      {
217
2076
        func_eqcs[tn].push_back(eqc);
218
4152
        Trace("uf-ho-debug")
219
2076
            << "  func eqc : " << tn << " : " << eqc << std::endl;
220
      }
221
    }
222
19631
    ++eqcs_i;
223
  }
224
1307
  if (!options::ufHoExt())
225
  {
226
    // we are not applying extensionality, thus we are incomplete if functions
227
    // are present
228
    if (hasFunctions)
229
    {
230
      d_im.setIncomplete(IncompleteId::UF_HO_EXT_DISABLED);
231
    }
232
    return 0;
233
  }
234
235
2549
  for (std::map<TypeNode, std::vector<Node> >::iterator itf = func_eqcs.begin();
236
2549
       itf != func_eqcs.end();
237
       ++itf)
238
  {
239
3318
    for (unsigned j = 0, sizej = itf->second.size(); j < sizej; j++)
240
    {
241
3646
      for (unsigned k = (j + 1), sizek = itf->second.size(); k < sizek; k++)
242
      {
243
        // if these equivalence classes are not explicitly disequal, do
244
        // extensionality to ensure distinctness
245
1570
        if (!ee->areDisequal(itf->second[j], itf->second[k], false))
246
        {
247
          Node deq =
248
1492
              Rewriter::rewrite(itf->second[j].eqNode(itf->second[k]).negate());
249
          // either add to model, or add lemma
250
746
          if (isCollectModel)
251
          {
252
            // Add extentionality disequality to the model.
253
            // It is important that we construct new (unconstrained) variables
254
            // k here, so that we do not generate any inconsistencies.
255
1092
            Node edeq = getExtensionalityDeq(deq, false);
256
546
            Assert(edeq.getKind() == NOT && edeq[0].getKind() == EQUAL);
257
            // introducing terms, must add required constraints, e.g. to
258
            // force equalities between APPLY_UF and HO_APPLY terms
259
1638
            for (unsigned r = 0; r < 2; r++)
260
            {
261
1092
              if (!collectModelInfoHoTerm(edeq[0][r], m))
262
              {
263
                return 1;
264
              }
265
            }
266
1092
            Trace("uf-ho-debug")
267
546
                << "Add extensionality deq to model : " << edeq << std::endl;
268
546
            if (!m->assertEquality(edeq[0][0], edeq[0][1], false))
269
            {
270
              Node eq = edeq[0][0].eqNode(edeq[0][1]);
271
              Node lem = nm->mkNode(OR, deq.negate(), eq);
272
              Trace("uf-ho") << "HoExtension: cmi extensionality lemma " << lem
273
                             << std::endl;
274
              d_im.lemma(lem, InferenceId::UF_HO_MODEL_EXTENSIONALITY);
275
              return 1;
276
            }
277
          }
278
          else
279
          {
280
            // apply extensionality lemma
281
200
            num_lemmas += applyExtensionality(deq);
282
          }
283
        }
284
      }
285
    }
286
  }
287
1307
  return num_lemmas;
288
}
289
290
682239
unsigned HoExtension::applyAppCompletion(TNode n)
291
{
292
682239
  Assert(n.getKind() == APPLY_UF);
293
294
682239
  eq::EqualityEngine* ee = d_state.getEqualityEngine();
295
  // must expand into APPLY_HO version if not there already
296
1364478
  Node ret = TheoryUfRewriter::getHoApplyForApplyUf(n);
297
682239
  if (!ee->hasTerm(ret) || !ee->areEqual(ret, n))
298
  {
299
25554
    Node eq = n.eqNode(ret);
300
25554
    Trace("uf-ho-lemma") << "uf-ho-lemma : infer, by apply-expand : " << eq
301
12777
                         << std::endl;
302
25554
    d_im.assertInternalFact(eq,
303
                            true,
304
                            InferenceId::UF_HO_APP_ENCODE,
305
                            PfRule::HO_APP_ENCODE,
306
                            {},
307
12777
                            {n});
308
12777
    return 1;
309
  }
310
1338924
  Trace("uf-ho-debug") << "    ...already have " << ret << " == " << n << "."
311
669462
                       << std::endl;
312
669462
  return 0;
313
}
314
315
13686
unsigned HoExtension::checkAppCompletion()
316
{
317
13686
  Trace("uf-ho") << "HoExtension::checkApplyCompletion..." << std::endl;
318
  // compute the operators that are relevant (those for which an HO_APPLY exist)
319
27372
  std::set<TNode> rlvOp;
320
13686
  eq::EqualityEngine* ee = d_state.getEqualityEngine();
321
13686
  eq::EqClassesIterator eqcs_i = eq::EqClassesIterator(ee);
322
27372
  std::map<TNode, std::vector<Node> > apply_uf;
323
76618
  while (!eqcs_i.isFinished())
324
  {
325
75709
    Node eqc = (*eqcs_i);
326
88486
    Trace("uf-ho-debug") << "  apply completion : visit eqc " << eqc
327
44243
                         << std::endl;
328
44243
    eq::EqClassIterator eqc_i = eq::EqClassIterator(eqc, ee);
329
5087659
    while (!eqc_i.isFinished())
330
    {
331
5056193
      Node n = *eqc_i;
332
2534485
      if (n.getKind() == APPLY_UF || n.getKind() == HO_APPLY)
333
      {
334
1583063
        int curr_sum = 0;
335
3153349
        std::map<TNode, bool> curr_rops;
336
1583063
        if (n.getKind() == APPLY_UF)
337
        {
338
1814021
          TNode rop = ee->getRepresentative(n.getOperator());
339
910908
          if (rlvOp.find(rop) != rlvOp.end())
340
          {
341
            // try if its operator is relevant
342
370924
            curr_sum = applyAppCompletion(n);
343
370924
            if (curr_sum > 0)
344
            {
345
7795
              return curr_sum;
346
            }
347
          }
348
          else
349
          {
350
            // add to pending list
351
539984
            apply_uf[rop].push_back(n);
352
          }
353
          // Arguments are also relevant operators.
354
          // It might be possible include fewer terms here, see #1115.
355
2702695
          for (unsigned k = 0; k < n.getNumChildren(); k++)
356
          {
357
1799582
            if (n[k].getType().isFunction())
358
            {
359
63742
              TNode rop2 = ee->getRepresentative(n[k]);
360
31871
              curr_rops[rop2] = true;
361
            }
362
          }
363
        }
364
        else
365
        {
366
672155
          Assert(n.getKind() == HO_APPLY);
367
1344310
          TNode rop = ee->getRepresentative(n[0]);
368
672155
          curr_rops[rop] = true;
369
        }
370
2274240
        for (std::map<TNode, bool>::iterator itc = curr_rops.begin();
371
2274240
             itc != curr_rops.end();
372
             ++itc)
373
        {
374
1402926
          TNode rop = itc->first;
375
703954
          if (rlvOp.find(rop) == rlvOp.end())
376
          {
377
47005
            rlvOp.insert(rop);
378
            // now, try each pending APPLY_UF for this operator
379
            std::map<TNode, std::vector<Node> >::iterator itu =
380
47005
                apply_uf.find(rop);
381
47005
            if (itu != apply_uf.end())
382
            {
383
327787
              for (unsigned j = 0, size = itu->second.size(); j < size; j++)
384
              {
385
311315
                curr_sum = applyAppCompletion(itu->second[j]);
386
311315
                if (curr_sum > 0)
387
                {
388
4982
                  return curr_sum;
389
                }
390
              }
391
            }
392
          }
393
        }
394
      }
395
2521708
      ++eqc_i;
396
    }
397
31466
    ++eqcs_i;
398
  }
399
909
  return 0;
400
}
401
402
1047
unsigned HoExtension::check()
403
{
404
1047
  Trace("uf-ho") << "HoExtension::checkHigherOrder..." << std::endl;
405
406
  // infer new facts based on apply completion until fixed point
407
  unsigned num_facts;
408
12639
  do
409
  {
410
13686
    num_facts = checkAppCompletion();
411
13686
    if (d_state.isInConflict())
412
    {
413
138
      Trace("uf-ho") << "...conflict during app-completion." << std::endl;
414
138
      return 1;
415
    }
416
13548
  } while (num_facts > 0);
417
418
909
  unsigned num_lemmas = 0;
419
420
909
  num_lemmas = checkExtensionality();
421
909
  if (num_lemmas > 0)
422
  {
423
74
    Trace("uf-ho") << "...extensionality returned " << num_lemmas << " lemmas."
424
37
                   << std::endl;
425
37
    return num_lemmas;
426
  }
427
428
872
  Trace("uf-ho") << "...finished check higher order." << std::endl;
429
430
872
  return 0;
431
}
432
433
398
bool HoExtension::collectModelInfoHo(TheoryModel* m,
434
                                     const std::set<Node>& termSet)
435
{
436
3581
  for (std::set<Node>::iterator it = termSet.begin(); it != termSet.end(); ++it)
437
  {
438
6366
    Node n = *it;
439
    // For model-building with ufHo, we require that APPLY_UF is always
440
    // expanded to HO_APPLY. That is, we always expand to a fully applicative
441
    // encoding during model construction.
442
3183
    if (!collectModelInfoHoTerm(n, m))
443
    {
444
      return false;
445
    }
446
  }
447
398
  int addedLemmas = checkExtensionality(m);
448
398
  return addedLemmas == 0;
449
}
450
451
4275
bool HoExtension::collectModelInfoHoTerm(Node n, TheoryModel* m)
452
{
453
4275
  if (n.getKind() == APPLY_UF)
454
  {
455
4612
    Node hn = TheoryUfRewriter::getHoApplyForApplyUf(n);
456
2306
    if (!m->assertEquality(n, hn, true))
457
    {
458
      Node eq = n.eqNode(hn);
459
      Trace("uf-ho") << "HoExtension: cmi app completion lemma " << eq
460
                     << std::endl;
461
      d_im.lemma(eq, InferenceId::UF_HO_MODEL_APP_ENCODE);
462
      return false;
463
    }
464
  }
465
4275
  return true;
466
}
467
468
}  // namespace uf
469
}  // namespace theory
470
29340
}  // namespace cvc5