GCC Code Coverage Report
Directory: . Exec Total Coverage
File: src/proof/proof_node_manager.cpp Lines: 180 219 82.2 %
Date: 2021-11-05 Branches: 287 892 32.2 %

Line Exec Source
1
/******************************************************************************
2
 * Top contributors (to current version):
3
 *   Andrew Reynolds, Haniel Barbosa, Aina Niemetz
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 proof node manager.
14
 */
15
16
#include "proof/proof_node_manager.h"
17
18
#include <sstream>
19
20
#include "options/proof_options.h"
21
#include "proof/proof.h"
22
#include "proof/proof_checker.h"
23
#include "proof/proof_node.h"
24
#include "proof/proof_node_algorithm.h"
25
#include "theory/rewriter.h"
26
27
using namespace cvc5::kind;
28
29
namespace cvc5 {
30
31
7987
ProofNodeManager::ProofNodeManager(theory::Rewriter* rr, ProofChecker* pc)
32
7987
    : d_rewriter(rr), d_checker(pc)
33
{
34
7987
  d_true = NodeManager::currentNM()->mkConst(true);
35
  // we always allocate a proof checker, regardless of the proof checking mode
36
7987
  Assert(d_checker != nullptr);
37
7987
}
38
39
19019241
std::shared_ptr<ProofNode> ProofNodeManager::mkNode(
40
    PfRule id,
41
    const std::vector<std::shared_ptr<ProofNode>>& children,
42
    const std::vector<Node>& args,
43
    Node expected)
44
{
45
38038482
  Trace("pnm") << "ProofNodeManager::mkNode " << id << " {" << expected.getId()
46
19019241
               << "} " << expected << "\n";
47
19019241
  bool didCheck = false;
48
38038482
  Node res = checkInternal(id, children, args, expected, didCheck);
49
19019241
  if (res.isNull())
50
  {
51
    // if it was invalid, then we return the null node
52
    return nullptr;
53
  }
54
  // otherwise construct the proof node and set its proven field
55
  std::shared_ptr<ProofNode> pn =
56
38038482
      std::make_shared<ProofNode>(id, children, args);
57
19019241
  pn->d_proven = res;
58
19019241
  pn->d_provenChecked = didCheck;
59
19019241
  return pn;
60
}
61
62
9968200
std::shared_ptr<ProofNode> ProofNodeManager::mkAssume(Node fact)
63
{
64
9968200
  Assert(!fact.isNull());
65
9968200
  Assert(fact.getType().isBoolean());
66
9968200
  return mkNode(PfRule::ASSUME, {}, {fact}, fact);
67
}
68
69
329886
std::shared_ptr<ProofNode> ProofNodeManager::mkSymm(
70
    std::shared_ptr<ProofNode> child, Node expected)
71
{
72
329886
  if (child->getRule() == PfRule::SYMM)
73
  {
74
203
    Assert(expected.isNull()
75
           || child->getChildren()[0]->getResult() == expected);
76
203
    return child->getChildren()[0];
77
  }
78
329683
  return mkNode(PfRule::SYMM, {child}, {}, expected);
79
}
80
81
392
std::shared_ptr<ProofNode> ProofNodeManager::mkTrans(
82
    const std::vector<std::shared_ptr<ProofNode>>& children, Node expected)
83
{
84
392
  Assert(!children.empty());
85
392
  if (children.size() == 1)
86
  {
87
    Assert(expected.isNull() || children[0]->getResult() == expected);
88
    return children[0];
89
  }
90
392
  return mkNode(PfRule::TRANS, children, {}, expected);
91
}
92
93
191455
std::shared_ptr<ProofNode> ProofNodeManager::mkScope(
94
    std::shared_ptr<ProofNode> pf,
95
    std::vector<Node>& assumps,
96
    bool ensureClosed,
97
    bool doMinimize,
98
    Node expected)
99
{
100
191455
  if (!ensureClosed)
101
  {
102
57287
    return mkNode(PfRule::SCOPE, {pf}, assumps, expected);
103
  }
104
134168
  Trace("pnm-scope") << "ProofNodeManager::mkScope " << assumps << std::endl;
105
  // we first ensure the assumptions are flattened
106
268336
  std::unordered_set<Node> ac{assumps.begin(), assumps.end()};
107
  // map from the rewritten form of assumptions to the original. This is only
108
  // computed in the rare case when we need rewriting to match the
109
  // assumptions. An example of this is for Boolean constant equalities in
110
  // scoped proofs from the proof equality engine.
111
268336
  std::unordered_map<Node, Node> acr;
112
  // whether we have compute the map above
113
134168
  bool computedAcr = false;
114
115
  // The free assumptions of the proof
116
268336
  std::map<Node, std::vector<std::shared_ptr<ProofNode>>> famap;
117
134168
  expr::getFreeAssumptionsMap(pf, famap);
118
268336
  std::unordered_set<Node> acu;
119
565760
  for (const std::pair<const Node, std::vector<std::shared_ptr<ProofNode>>>&
120
134168
           fa : famap)
121
  {
122
1131520
    Node a = fa.first;
123
1008254
    if (ac.find(a) != ac.end())
124
    {
125
      // already covered by an assumption
126
442494
      acu.insert(a);
127
442494
      continue;
128
    }
129
    // trivial assumption
130
123266
    if (a == d_true)
131
    {
132
      Trace("pnm-scope") << "- justify trivial True assumption\n";
133
      for (std::shared_ptr<ProofNode> pfs : fa.second)
134
      {
135
        Assert(pfs->getResult() == a);
136
        updateNode(pfs.get(), PfRule::MACRO_SR_PRED_INTRO, {}, {a});
137
      }
138
      Trace("pnm-scope") << "...finished" << std::endl;
139
      acu.insert(a);
140
      continue;
141
    }
142
123266
    Trace("pnm-scope") << "- try matching free assumption " << a << "\n";
143
    // otherwise it may be due to symmetry?
144
246532
    Node aeqSym = CDProof::getSymmFact(a);
145
123266
    Trace("pnm-scope") << "  - try sym " << aeqSym << "\n";
146
246532
    Node aMatch;
147
123266
    if (!aeqSym.isNull() && ac.count(aeqSym))
148
    {
149
245298
      Trace("pnm-scope") << "- reorient assumption " << aeqSym << " via " << a
150
245298
                         << " for " << fa.second.size() << " proof nodes"
151
122649
                         << std::endl;
152
122649
      aMatch = aeqSym;
153
    }
154
    else
155
    {
156
      // Otherwise, may be derivable by rewriting. Note this is used in
157
      // ensuring that proofs from the proof equality engine that involve
158
      // equality with Boolean constants are closed.
159
617
      if (!computedAcr)
160
      {
161
243
        computedAcr = true;
162
3508
        for (const Node& acc : ac)
163
        {
164
6530
          Node accr = d_rewriter->rewrite(acc);
165
3265
          if (accr != acc)
166
          {
167
1262
            acr[accr] = acc;
168
          }
169
        }
170
      }
171
1234
      Node ar = d_rewriter->rewrite(a);
172
617
      std::unordered_map<Node, Node>::iterator itr = acr.find(ar);
173
617
      if (itr != acr.end())
174
      {
175
617
        aMatch = itr->second;
176
      }
177
    }
178
179
    // if we found a match either by symmetry or rewriting, then we connect
180
    // the assumption here.
181
123266
    if (!aMatch.isNull())
182
    {
183
246532
      std::shared_ptr<ProofNode> pfaa = mkAssume(aMatch);
184
      // must correct the orientation on this leaf
185
246532
      std::vector<std::shared_ptr<ProofNode>> children;
186
123266
      children.push_back(pfaa);
187
246532
      for (std::shared_ptr<ProofNode> pfs : fa.second)
188
      {
189
123266
        Assert(pfs->getResult() == a);
190
        // use SYMM if possible
191
123266
        if (aMatch == aeqSym)
192
        {
193
122649
          if (pfaa->getRule() == PfRule::SYMM)
194
          {
195
            updateNode(pfs.get(), pfaa->getChildren()[0].get());
196
          }
197
          else
198
          {
199
122649
            updateNode(pfs.get(), PfRule::SYMM, children, {});
200
          }
201
        }
202
        else
203
        {
204
617
          updateNode(pfs.get(), PfRule::MACRO_SR_PRED_TRANSFORM, children, {a});
205
        }
206
      }
207
123266
      Trace("pnm-scope") << "...finished" << std::endl;
208
123266
      acu.insert(aMatch);
209
123266
      continue;
210
    }
211
    // If we did not find a match, it is an error, since all free assumptions
212
    // should be arguments to SCOPE.
213
    std::stringstream ss;
214
215
    bool dumpProofTraceOn = Trace.isOn("dump-proof-error");
216
    if (dumpProofTraceOn)
217
    {
218
      ss << "The proof : " << *pf << std::endl;
219
    }
220
    ss << std::endl << "Free assumption: " << a << std::endl;
221
    for (const Node& aprint : ac)
222
    {
223
      ss << "- assumption: " << aprint << std::endl;
224
    }
225
    if (!dumpProofTraceOn)
226
    {
227
      ss << "Use -t dump-proof-error for details on proof" << std::endl;
228
    }
229
    Unreachable() << "Generated a proof that is not closed by the scope: "
230
                  << ss.str() << std::endl;
231
  }
232
134168
  if (acu.size() < ac.size())
233
  {
234
    // All assumptions should match a free assumption; if one does not, then
235
    // the explanation could have been smaller.
236
47418
    for (const Node& a : ac)
237
    {
238
43134
      if (acu.find(a) == acu.end())
239
      {
240
28735
        Notice() << "ProofNodeManager::mkScope: assumption " << a
241
                 << " does not match a free assumption in proof" << std::endl;
242
      }
243
    }
244
  }
245
134168
  if (doMinimize && acu.size() < ac.size())
246
  {
247
3240
    assumps.clear();
248
3240
    assumps.insert(assumps.end(), acu.begin(), acu.end());
249
  }
250
130928
  else if (ac.size() < assumps.size())
251
  {
252
    // remove duplicates to avoid redundant literals in clauses
253
715
    assumps.clear();
254
715
    assumps.insert(assumps.end(), ac.begin(), ac.end());
255
  }
256
268336
  Node minExpected;
257
134168
  NodeManager* nm = NodeManager::currentNM();
258
268336
  Node exp;
259
134168
  if (assumps.empty())
260
  {
261
    // SCOPE with no arguments is a no-op, just return original
262
2540
    return pf;
263
  }
264
263256
  Node conc = pf->getResult();
265
131628
  exp = assumps.size() == 1 ? assumps[0] : nm->mkNode(AND, assumps);
266
131628
  if (conc.isConst() && !conc.getConst<bool>())
267
  {
268
48625
    minExpected = exp.notNode();
269
  }
270
  else
271
  {
272
83003
    minExpected = nm->mkNode(IMPLIES, exp, conc);
273
  }
274
131628
  return mkNode(PfRule::SCOPE, {pf}, assumps, minExpected);
275
}
276
277
194045
bool ProofNodeManager::updateNode(
278
    ProofNode* pn,
279
    PfRule id,
280
    const std::vector<std::shared_ptr<ProofNode>>& children,
281
    const std::vector<Node>& args)
282
{
283
194045
  return updateNodeInternal(pn, id, children, args, true);
284
}
285
286
2265550
bool ProofNodeManager::updateNode(ProofNode* pn, ProofNode* pnr)
287
{
288
2265550
  Assert(pn != nullptr);
289
2265550
  Assert(pnr != nullptr);
290
2265550
  if (pn == pnr)
291
  {
292
    // same node, no update necessary
293
2187
    return true;
294
  }
295
2263363
  if (pn->getResult() != pnr->getResult())
296
  {
297
    return false;
298
  }
299
  // copy whether we did the check
300
2263363
  pn->d_provenChecked = pnr->d_provenChecked;
301
  // can shortcut re-check of rule
302
2263363
  return updateNodeInternal(
303
2263363
      pn, pnr->getRule(), pnr->getChildren(), pnr->getArguments(), false);
304
}
305
306
2533315
void ProofNodeManager::ensureChecked(ProofNode* pn)
307
{
308
2533315
  if (pn->d_provenChecked)
309
  {
310
    // already checked
311
114098
    return;
312
  }
313
  // directly call the proof checker
314
4838434
  Node res = d_checker->check(pn, pn->getResult());
315
2419217
  pn->d_provenChecked = true;
316
  // should have the correct result
317
2419217
  Assert(res == pn->d_proven);
318
}
319
320
19213286
Node ProofNodeManager::checkInternal(
321
    PfRule id,
322
    const std::vector<std::shared_ptr<ProofNode>>& children,
323
    const std::vector<Node>& args,
324
    Node expected,
325
    bool& didCheck)
326
{
327
  // if the user supplied an expected result, then we trust it if we are in
328
  // a proof checking mode that does not eagerly check rule applications
329
19213286
  if (!expected.isNull())
330
  {
331
36197076
    if (options::proofCheck() == options::ProofCheckMode::LAZY
332
18098538
        || options::proofCheck() == options::ProofCheckMode::NONE)
333
    {
334
18095950
      return expected;
335
    }
336
  }
337
  // check with the checker, which takes expected as argument
338
2234672
  Node res = d_checker->check(id, children, args, expected);
339
1117336
  didCheck = true;
340
1117336
  Assert(!res.isNull())
341
      << "ProofNodeManager::checkInternal: failed to check proof";
342
1117336
  return res;
343
}
344
345
11024014
ProofChecker* ProofNodeManager::getChecker() const { return d_checker; }
346
347
60736
std::shared_ptr<ProofNode> ProofNodeManager::clone(
348
    std::shared_ptr<ProofNode> pn) const
349
{
350
60736
  const ProofNode* orig = pn.get();
351
121472
  std::unordered_map<const ProofNode*, std::shared_ptr<ProofNode>> visited;
352
60736
  std::unordered_map<const ProofNode*, std::shared_ptr<ProofNode>>::iterator it;
353
121472
  std::vector<const ProofNode*> visit;
354
121472
  std::shared_ptr<ProofNode> cloned;
355
60736
  visit.push_back(orig);
356
  const ProofNode* cur;
357
3948056
  while (!visit.empty())
358
  {
359
1943660
    cur = visit.back();
360
1943660
    it = visited.find(cur);
361
1943660
    if (it == visited.end())
362
    {
363
905092
      visited[cur] = nullptr;
364
      const std::vector<std::shared_ptr<ProofNode>>& children =
365
905092
          cur->getChildren();
366
1882924
      for (const std::shared_ptr<ProofNode>& cp : children)
367
      {
368
977832
        visit.push_back(cp.get());
369
      }
370
905092
      continue;
371
    }
372
1038568
    visit.pop_back();
373
1038568
    if (it->second.get() == nullptr)
374
    {
375
1810184
      std::vector<std::shared_ptr<ProofNode>> cchildren;
376
      const std::vector<std::shared_ptr<ProofNode>>& children =
377
905092
          cur->getChildren();
378
1882924
      for (const std::shared_ptr<ProofNode>& cp : children)
379
      {
380
977832
        it = visited.find(cp.get());
381
977832
        Assert(it != visited.end());
382
        // if we encounter nullptr here, then this child is currently being
383
        // traversed at a higher level, hence this corresponds to a cyclic
384
        // proof.
385
977832
        if (it->second == nullptr)
386
        {
387
          Unreachable() << "Cyclic proof encountered when cloning a proof node";
388
        }
389
977832
        cchildren.push_back(it->second);
390
      }
391
1810184
      cloned = std::make_shared<ProofNode>(
392
1810184
          cur->getRule(), cchildren, cur->getArguments());
393
905092
      visited[cur] = cloned;
394
      // we trust the above cloning does not change what is proven
395
905092
      cloned->d_proven = cur->d_proven;
396
905092
      cloned->d_provenChecked = cur->d_provenChecked;
397
    }
398
  }
399
60736
  Assert(visited.find(orig) != visited.end());
400
121472
  return visited[orig];
401
}
402
403
268905
ProofNode* ProofNodeManager::cancelDoubleSymm(ProofNode* pn)
404
{
405
269993
  while (pn->getRule() == PfRule::SYMM)
406
  {
407
268905
    std::shared_ptr<ProofNode> pnc = pn->getChildren()[0];
408
267817
    if (pnc->getRule() == PfRule::SYMM)
409
    {
410
1088
      pn = pnc->getChildren()[0].get();
411
    }
412
    else
413
    {
414
266729
      break;
415
    }
416
  }
417
267817
  return pn;
418
}
419
420
2457408
bool ProofNodeManager::updateNodeInternal(
421
    ProofNode* pn,
422
    PfRule id,
423
    const std::vector<std::shared_ptr<ProofNode>>& children,
424
    const std::vector<Node>& args,
425
    bool needsCheck)
426
{
427
2457408
  Assert(pn != nullptr);
428
  // ---------------- check for cyclic
429
2457408
  if (options::proofCheck() == options::ProofCheckMode::EAGER)
430
  {
431
784
    std::unordered_set<const ProofNode*> visited;
432
494
    for (const std::shared_ptr<ProofNode>& cpc : children)
433
    {
434
102
      if (expr::containsSubproof(cpc.get(), pn, visited))
435
      {
436
        std::stringstream ss;
437
        ss << "ProofNodeManager::updateNode: attempting to make cyclic proof! "
438
           << id << " " << pn->getResult() << ", children = " << std::endl;
439
        for (const std::shared_ptr<ProofNode>& cp : children)
440
        {
441
          ss << "  " << cp->getRule() << " " << cp->getResult() << std::endl;
442
        }
443
        ss << "Full children:" << std::endl;
444
        for (const std::shared_ptr<ProofNode>& cp : children)
445
        {
446
          ss << "  - ";
447
          cp->printDebug(ss);
448
          ss << std::endl;
449
        }
450
        Unreachable() << ss.str();
451
      }
452
    }
453
  }
454
  // ---------------- end check for cyclic
455
456
  // should have already computed what is proven
457
2457408
  Assert(!pn->d_proven.isNull())
458
      << "ProofNodeManager::updateProofNode: invalid proof provided";
459
2457408
  if (needsCheck)
460
  {
461
    // We expect to prove the same thing as before
462
194045
    bool didCheck = false;
463
388090
    Node res = checkInternal(id, children, args, pn->d_proven, didCheck);
464
194045
    if (res.isNull())
465
    {
466
      // if it was invalid, then we do not update
467
      return false;
468
    }
469
    // proven field should already be the same as the result
470
194045
    Assert(res == pn->d_proven);
471
194045
    pn->d_provenChecked = didCheck;
472
  }
473
474
  // we update its value
475
2457408
  pn->setValue(id, children, args);
476
2457408
  return true;
477
}
478
479
31125
}  // namespace cvc5