GCC Code Coverage Report
Directory: . Exec Total Coverage
File: src/proof/proof_node_manager.cpp Lines: 152 188 80.9 %
Date: 2021-08-01 Branches: 258 810 31.9 %

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
3756
ProofNodeManager::ProofNodeManager(ProofChecker* pc) : d_checker(pc)
32
{
33
3756
  d_true = NodeManager::currentNM()->mkConst(true);
34
3756
}
35
36
19773925
std::shared_ptr<ProofNode> ProofNodeManager::mkNode(
37
    PfRule id,
38
    const std::vector<std::shared_ptr<ProofNode>>& children,
39
    const std::vector<Node>& args,
40
    Node expected)
41
{
42
39547850
  Trace("pnm") << "ProofNodeManager::mkNode " << id << " {" << expected.getId()
43
19773925
               << "} " << expected << "\n";
44
39547850
  Node res = checkInternal(id, children, args, expected);
45
19773925
  if (res.isNull())
46
  {
47
    // if it was invalid, then we return the null node
48
    return nullptr;
49
  }
50
  // otherwise construct the proof node and set its proven field
51
  std::shared_ptr<ProofNode> pn =
52
39547850
      std::make_shared<ProofNode>(id, children, args);
53
19773925
  pn->d_proven = res;
54
19773925
  return pn;
55
}
56
57
9799756
std::shared_ptr<ProofNode> ProofNodeManager::mkAssume(Node fact)
58
{
59
9799756
  Assert(!fact.isNull());
60
9799756
  Assert(fact.getType().isBoolean());
61
9799756
  return mkNode(PfRule::ASSUME, {}, {fact}, fact);
62
}
63
64
363
std::shared_ptr<ProofNode> ProofNodeManager::mkTrans(
65
    const std::vector<std::shared_ptr<ProofNode>>& children, Node expected)
66
{
67
363
  Assert(!children.empty());
68
363
  if (children.size() == 1)
69
  {
70
    Assert(expected.isNull() || children[0]->getResult() == expected);
71
    return children[0];
72
  }
73
363
  return mkNode(PfRule::TRANS, children, {}, expected);
74
}
75
76
167778
std::shared_ptr<ProofNode> ProofNodeManager::mkScope(
77
    std::shared_ptr<ProofNode> pf,
78
    std::vector<Node>& assumps,
79
    bool ensureClosed,
80
    bool doMinimize,
81
    Node expected)
82
{
83
167778
  if (!ensureClosed)
84
  {
85
51202
    return mkNode(PfRule::SCOPE, {pf}, assumps, expected);
86
  }
87
116576
  Trace("pnm-scope") << "ProofNodeManager::mkScope " << assumps << std::endl;
88
  // we first ensure the assumptions are flattened
89
233152
  std::unordered_set<Node> ac{assumps.begin(), assumps.end()};
90
  // map from the rewritten form of assumptions to the original. This is only
91
  // computed in the rare case when we need rewriting to match the
92
  // assumptions. An example of this is for Boolean constant equalities in
93
  // scoped proofs from the proof equality engine.
94
233152
  std::unordered_map<Node, Node> acr;
95
  // whether we have compute the map above
96
116576
  bool computedAcr = false;
97
98
  // The free assumptions of the proof
99
233152
  std::map<Node, std::vector<std::shared_ptr<ProofNode>>> famap;
100
116576
  expr::getFreeAssumptionsMap(pf, famap);
101
233152
  std::unordered_set<Node> acu;
102
516870
  for (const std::pair<const Node, std::vector<std::shared_ptr<ProofNode>>>&
103
116576
           fa : famap)
104
  {
105
1033740
    Node a = fa.first;
106
910375
    if (ac.find(a) != ac.end())
107
    {
108
      // already covered by an assumption
109
393505
      acu.insert(a);
110
393505
      continue;
111
    }
112
    // trivial assumption
113
123365
    if (a == d_true)
114
    {
115
6
      Trace("pnm-scope") << "- justify trivial True assumption\n";
116
12
      for (std::shared_ptr<ProofNode> pfs : fa.second)
117
      {
118
6
        Assert(pfs->getResult() == a);
119
6
        updateNode(pfs.get(), PfRule::MACRO_SR_PRED_INTRO, {}, {a});
120
      }
121
6
      Trace("pnm-scope") << "...finished" << std::endl;
122
6
      acu.insert(a);
123
6
      continue;
124
    }
125
123359
    Trace("pnm-scope") << "- try matching free assumption " << a << "\n";
126
    // otherwise it may be due to symmetry?
127
246718
    Node aeqSym = CDProof::getSymmFact(a);
128
123359
    Trace("pnm-scope") << "  - try sym " << aeqSym << "\n";
129
246718
    Node aMatch;
130
123359
    if (!aeqSym.isNull() && ac.count(aeqSym))
131
    {
132
245484
      Trace("pnm-scope") << "- reorient assumption " << aeqSym << " via " << a
133
245484
                         << " for " << fa.second.size() << " proof nodes"
134
122742
                         << std::endl;
135
122742
      aMatch = aeqSym;
136
    }
137
    else
138
    {
139
      // Otherwise, may be derivable by rewriting. Note this is used in
140
      // ensuring that proofs from the proof equality engine that involve
141
      // equality with Boolean constants are closed.
142
617
      if (!computedAcr)
143
      {
144
243
        computedAcr = true;
145
3508
        for (const Node& acc : ac)
146
        {
147
6530
          Node accr = theory::Rewriter::rewrite(acc);
148
3265
          if (accr != acc)
149
          {
150
1262
            acr[accr] = acc;
151
          }
152
        }
153
      }
154
1234
      Node ar = theory::Rewriter::rewrite(a);
155
617
      std::unordered_map<Node, Node>::iterator itr = acr.find(ar);
156
617
      if (itr != acr.end())
157
      {
158
617
        aMatch = itr->second;
159
      }
160
    }
161
162
    // if we found a match either by symmetry or rewriting, then we connect
163
    // the assumption here.
164
123359
    if (!aMatch.isNull())
165
    {
166
246718
      std::shared_ptr<ProofNode> pfaa = mkAssume(aMatch);
167
      // must correct the orientation on this leaf
168
246718
      std::vector<std::shared_ptr<ProofNode>> children;
169
123359
      children.push_back(pfaa);
170
246718
      for (std::shared_ptr<ProofNode> pfs : fa.second)
171
      {
172
123359
        Assert(pfs->getResult() == a);
173
        // use SYMM if possible
174
123359
        if (aMatch == aeqSym)
175
        {
176
122742
          updateNode(pfs.get(), PfRule::SYMM, children, {});
177
        }
178
        else
179
        {
180
617
          updateNode(pfs.get(), PfRule::MACRO_SR_PRED_TRANSFORM, children, {a});
181
        }
182
      }
183
123359
      Trace("pnm-scope") << "...finished" << std::endl;
184
123359
      acu.insert(aMatch);
185
123359
      continue;
186
    }
187
    // If we did not find a match, it is an error, since all free assumptions
188
    // should be arguments to SCOPE.
189
    std::stringstream ss;
190
191
    bool dumpProofTraceOn = Trace.isOn("dump-proof-error");
192
    if (dumpProofTraceOn)
193
    {
194
      ss << "The proof : " << *pf << std::endl;
195
    }
196
    ss << std::endl << "Free assumption: " << a << std::endl;
197
    for (const Node& aprint : ac)
198
    {
199
      ss << "- assumption: " << aprint << std::endl;
200
    }
201
    if (!dumpProofTraceOn)
202
    {
203
      ss << "Use -t dump-proof-error for details on proof" << std::endl;
204
    }
205
    Unreachable() << "Generated a proof that is not closed by the scope: "
206
                  << ss.str() << std::endl;
207
  }
208
116576
  if (acu.size() < ac.size())
209
  {
210
    // All assumptions should match a free assumption; if one does not, then
211
    // the explanation could have been smaller.
212
43907
    for (const Node& a : ac)
213
    {
214
40131
      if (acu.find(a) == acu.end())
215
      {
216
25941
        Notice() << "ProofNodeManager::mkScope: assumption " << a
217
                 << " does not match a free assumption in proof" << std::endl;
218
      }
219
    }
220
  }
221
116576
  if (doMinimize && acu.size() < ac.size())
222
  {
223
2757
    assumps.clear();
224
2757
    assumps.insert(assumps.end(), acu.begin(), acu.end());
225
  }
226
113819
  else if (ac.size() < assumps.size())
227
  {
228
    // remove duplicates to avoid redundant literals in clauses
229
167
    assumps.clear();
230
167
    assumps.insert(assumps.end(), ac.begin(), ac.end());
231
  }
232
233152
  Node minExpected;
233
116576
  NodeManager* nm = NodeManager::currentNM();
234
233152
  Node exp;
235
116576
  if (assumps.empty())
236
  {
237
    // SCOPE with no arguments is a no-op, just return original
238
2207
    return pf;
239
  }
240
228738
  Node conc = pf->getResult();
241
114369
  exp = assumps.size() == 1 ? assumps[0] : nm->mkNode(AND, assumps);
242
114369
  if (conc.isConst() && !conc.getConst<bool>())
243
  {
244
40646
    minExpected = exp.notNode();
245
  }
246
  else
247
  {
248
73723
    minExpected = nm->mkNode(IMPLIES, exp, conc);
249
  }
250
114369
  return mkNode(PfRule::SCOPE, {pf}, assumps, minExpected);
251
}
252
253
177938
bool ProofNodeManager::updateNode(
254
    ProofNode* pn,
255
    PfRule id,
256
    const std::vector<std::shared_ptr<ProofNode>>& children,
257
    const std::vector<Node>& args)
258
{
259
177938
  return updateNodeInternal(pn, id, children, args, true);
260
}
261
262
2796220
bool ProofNodeManager::updateNode(ProofNode* pn, ProofNode* pnr)
263
{
264
2796220
  Assert(pn != nullptr);
265
2796220
  Assert(pnr != nullptr);
266
2796220
  if (pn->getResult() != pnr->getResult())
267
  {
268
    return false;
269
  }
270
  // can shortcut re-check of rule
271
2796220
  return updateNodeInternal(
272
2796220
      pn, pnr->getRule(), pnr->getChildren(), pnr->getArguments(), false);
273
}
274
275
19951863
Node ProofNodeManager::checkInternal(
276
    PfRule id,
277
    const std::vector<std::shared_ptr<ProofNode>>& children,
278
    const std::vector<Node>& args,
279
    Node expected)
280
{
281
19951863
  Node res;
282
19951863
  if (d_checker)
283
  {
284
    // check with the checker, which takes expected as argument
285
19951863
    res = d_checker->check(id, children, args, expected);
286
19951863
    Assert(!res.isNull())
287
        << "ProofNodeManager::checkInternal: failed to check proof";
288
  }
289
  else
290
  {
291
    // otherwise we trust the expected value, if it exists
292
    Assert(!expected.isNull()) << "ProofNodeManager::checkInternal: no checker "
293
                                  "or expected value provided";
294
    res = expected;
295
  }
296
19951863
  return res;
297
}
298
299
10308474
ProofChecker* ProofNodeManager::getChecker() const { return d_checker; }
300
301
54628
std::shared_ptr<ProofNode> ProofNodeManager::clone(
302
    std::shared_ptr<ProofNode> pn)
303
{
304
54628
  const ProofNode* orig = pn.get();
305
109256
  std::unordered_map<const ProofNode*, std::shared_ptr<ProofNode>> visited;
306
54628
  std::unordered_map<const ProofNode*, std::shared_ptr<ProofNode>>::iterator it;
307
109256
  std::vector<const ProofNode*> visit;
308
109256
  std::shared_ptr<ProofNode> cloned;
309
54628
  visit.push_back(orig);
310
  const ProofNode* cur;
311
4229400
  while (!visit.empty())
312
  {
313
2087386
    cur = visit.back();
314
2087386
    it = visited.find(cur);
315
2087386
    if (it == visited.end())
316
    {
317
948560
      visited[cur] = nullptr;
318
      const std::vector<std::shared_ptr<ProofNode>>& children =
319
948560
          cur->getChildren();
320
2032758
      for (const std::shared_ptr<ProofNode>& cp : children)
321
      {
322
1084198
        visit.push_back(cp.get());
323
      }
324
948560
      continue;
325
    }
326
1138826
    visit.pop_back();
327
1138826
    if (it->second.get() == nullptr)
328
    {
329
1897120
      std::vector<std::shared_ptr<ProofNode>> cchildren;
330
      const std::vector<std::shared_ptr<ProofNode>>& children =
331
948560
          cur->getChildren();
332
2032758
      for (const std::shared_ptr<ProofNode>& cp : children)
333
      {
334
1084198
        it = visited.find(cp.get());
335
1084198
        Assert(it != visited.end());
336
1084198
        Assert(it->second != nullptr);
337
1084198
        cchildren.push_back(it->second);
338
      }
339
1897120
      cloned = std::make_shared<ProofNode>(
340
1897120
          cur->getRule(), cchildren, cur->getArguments());
341
948560
      visited[cur] = cloned;
342
      // we trust the above cloning does not change what is proven
343
948560
      cloned->d_proven = cur->d_proven;
344
    }
345
  }
346
54628
  Assert(visited.find(orig) != visited.end());
347
109256
  return visited[orig];
348
}
349
350
2974158
bool ProofNodeManager::updateNodeInternal(
351
    ProofNode* pn,
352
    PfRule id,
353
    const std::vector<std::shared_ptr<ProofNode>>& children,
354
    const std::vector<Node>& args,
355
    bool needsCheck)
356
{
357
2974158
  Assert(pn != nullptr);
358
  // ---------------- check for cyclic
359
2974158
  if (options::proofEagerChecking())
360
  {
361
    std::unordered_set<const ProofNode*> visited;
362
    for (const std::shared_ptr<ProofNode>& cpc : children)
363
    {
364
      if (expr::containsSubproof(cpc.get(), pn, visited))
365
      {
366
        std::stringstream ss;
367
        ss << "ProofNodeManager::updateNode: attempting to make cyclic proof! "
368
           << id << " " << pn->getResult() << ", children = " << std::endl;
369
        for (const std::shared_ptr<ProofNode>& cp : children)
370
        {
371
          ss << "  " << cp->getRule() << " " << cp->getResult() << std::endl;
372
        }
373
        ss << "Full children:" << std::endl;
374
        for (const std::shared_ptr<ProofNode>& cp : children)
375
        {
376
          ss << "  - ";
377
          cp->printDebug(ss);
378
          ss << std::endl;
379
        }
380
        Unreachable() << ss.str();
381
      }
382
    }
383
  }
384
  // ---------------- end check for cyclic
385
386
  // should have already computed what is proven
387
2974158
  Assert(!pn->d_proven.isNull())
388
      << "ProofNodeManager::updateProofNode: invalid proof provided";
389
2974158
  if (needsCheck)
390
  {
391
    // We expect to prove the same thing as before
392
355876
    Node res = checkInternal(id, children, args, pn->d_proven);
393
177938
    if (res.isNull())
394
    {
395
      // if it was invalid, then we do not update
396
      return false;
397
    }
398
    // proven field should already be the same as the result
399
177938
    Assert(res == pn->d_proven);
400
  }
401
402
  // we update its value
403
2974158
  pn->setValue(id, children, args);
404
2974158
  return true;
405
}
406
407
29280
}  // namespace cvc5