GCC Code Coverage Report
Directory: . Exec Total Coverage
File: src/proof/proof_node_manager.cpp Lines: 145 188 77.1 %
Date: 2021-08-16 Branches: 238 804 29.6 %

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
3768
ProofNodeManager::ProofNodeManager(ProofChecker* pc) : d_checker(pc)
32
{
33
3768
  d_true = NodeManager::currentNM()->mkConst(true);
34
3768
}
35
36
18775163
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
37550326
  Trace("pnm") << "ProofNodeManager::mkNode " << id << " {" << expected.getId()
43
18775163
               << "} " << expected << "\n";
44
37550326
  Node res = checkInternal(id, children, args, expected);
45
18775163
  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
37550326
      std::make_shared<ProofNode>(id, children, args);
53
18775163
  pn->d_proven = res;
54
18775163
  return pn;
55
}
56
57
9785961
std::shared_ptr<ProofNode> ProofNodeManager::mkAssume(Node fact)
58
{
59
9785961
  Assert(!fact.isNull());
60
9785961
  Assert(fact.getType().isBoolean());
61
9785961
  return mkNode(PfRule::ASSUME, {}, {fact}, fact);
62
}
63
64
365
std::shared_ptr<ProofNode> ProofNodeManager::mkTrans(
65
    const std::vector<std::shared_ptr<ProofNode>>& children, Node expected)
66
{
67
365
  Assert(!children.empty());
68
365
  if (children.size() == 1)
69
  {
70
    Assert(expected.isNull() || children[0]->getResult() == expected);
71
    return children[0];
72
  }
73
365
  return mkNode(PfRule::TRANS, children, {}, expected);
74
}
75
76
170984
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
170984
  if (!ensureClosed)
84
  {
85
51555
    return mkNode(PfRule::SCOPE, {pf}, assumps, expected);
86
  }
87
119429
  Trace("pnm-scope") << "ProofNodeManager::mkScope " << assumps << std::endl;
88
  // we first ensure the assumptions are flattened
89
238858
  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
238858
  std::unordered_map<Node, Node> acr;
95
  // whether we have compute the map above
96
119429
  bool computedAcr = false;
97
98
  // The free assumptions of the proof
99
238858
  std::map<Node, std::vector<std::shared_ptr<ProofNode>>> famap;
100
119429
  expr::getFreeAssumptionsMap(pf, famap);
101
238858
  std::unordered_set<Node> acu;
102
525648
  for (const std::pair<const Node, std::vector<std::shared_ptr<ProofNode>>>&
103
119429
           fa : famap)
104
  {
105
1051296
    Node a = fa.first;
106
925271
    if (ac.find(a) != ac.end())
107
    {
108
      // already covered by an assumption
109
399623
      acu.insert(a);
110
399623
      continue;
111
    }
112
    // trivial assumption
113
126025
    if (a == d_true)
114
    {
115
      Trace("pnm-scope") << "- justify trivial True assumption\n";
116
      for (std::shared_ptr<ProofNode> pfs : fa.second)
117
      {
118
        Assert(pfs->getResult() == a);
119
        updateNode(pfs.get(), PfRule::MACRO_SR_PRED_INTRO, {}, {a});
120
      }
121
      Trace("pnm-scope") << "...finished" << std::endl;
122
      acu.insert(a);
123
      continue;
124
    }
125
126025
    Trace("pnm-scope") << "- try matching free assumption " << a << "\n";
126
    // otherwise it may be due to symmetry?
127
252050
    Node aeqSym = CDProof::getSymmFact(a);
128
126025
    Trace("pnm-scope") << "  - try sym " << aeqSym << "\n";
129
252050
    Node aMatch;
130
126025
    if (!aeqSym.isNull() && ac.count(aeqSym))
131
    {
132
250816
      Trace("pnm-scope") << "- reorient assumption " << aeqSym << " via " << a
133
250816
                         << " for " << fa.second.size() << " proof nodes"
134
125408
                         << std::endl;
135
125408
      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
126025
    if (!aMatch.isNull())
165
    {
166
252050
      std::shared_ptr<ProofNode> pfaa = mkAssume(aMatch);
167
      // must correct the orientation on this leaf
168
252050
      std::vector<std::shared_ptr<ProofNode>> children;
169
126025
      children.push_back(pfaa);
170
252050
      for (std::shared_ptr<ProofNode> pfs : fa.second)
171
      {
172
126025
        Assert(pfs->getResult() == a);
173
        // use SYMM if possible
174
126025
        if (aMatch == aeqSym)
175
        {
176
125408
          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
126025
      Trace("pnm-scope") << "...finished" << std::endl;
184
126025
      acu.insert(aMatch);
185
126025
      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
119429
  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
45095
    for (const Node& a : ac)
213
    {
214
40892
      if (acu.find(a) == acu.end())
215
      {
216
26380
        Notice() << "ProofNodeManager::mkScope: assumption " << a
217
                 << " does not match a free assumption in proof" << std::endl;
218
      }
219
    }
220
  }
221
119429
  if (doMinimize && acu.size() < ac.size())
222
  {
223
3181
    assumps.clear();
224
3181
    assumps.insert(assumps.end(), acu.begin(), acu.end());
225
  }
226
116248
  else if (ac.size() < assumps.size())
227
  {
228
    // remove duplicates to avoid redundant literals in clauses
229
156
    assumps.clear();
230
156
    assumps.insert(assumps.end(), ac.begin(), ac.end());
231
  }
232
238858
  Node minExpected;
233
119429
  NodeManager* nm = NodeManager::currentNM();
234
238858
  Node exp;
235
119429
  if (assumps.empty())
236
  {
237
    // SCOPE with no arguments is a no-op, just return original
238
2239
    return pf;
239
  }
240
234380
  Node conc = pf->getResult();
241
117190
  exp = assumps.size() == 1 ? assumps[0] : nm->mkNode(AND, assumps);
242
117190
  if (conc.isConst() && !conc.getConst<bool>())
243
  {
244
40945
    minExpected = exp.notNode();
245
  }
246
  else
247
  {
248
76245
    minExpected = nm->mkNode(IMPLIES, exp, conc);
249
  }
250
117190
  return mkNode(PfRule::SCOPE, {pf}, assumps, minExpected);
251
}
252
253
181236
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
181236
  return updateNodeInternal(pn, id, children, args, true);
260
}
261
262
2192510
bool ProofNodeManager::updateNode(ProofNode* pn, ProofNode* pnr)
263
{
264
2192510
  Assert(pn != nullptr);
265
2192510
  Assert(pnr != nullptr);
266
2192510
  if (pn->getResult() != pnr->getResult())
267
  {
268
    return false;
269
  }
270
  // can shortcut re-check of rule
271
2192510
  return updateNodeInternal(
272
2192510
      pn, pnr->getRule(), pnr->getChildren(), pnr->getArguments(), false);
273
}
274
275
18956399
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
18956399
  Node res;
282
18956399
  if (d_checker)
283
  {
284
    // check with the checker, which takes expected as argument
285
18956399
    res = d_checker->check(id, children, args, expected);
286
18956399
    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
18956399
  return res;
297
}
298
299
11339032
ProofChecker* ProofNodeManager::getChecker() const { return d_checker; }
300
301
56848
std::shared_ptr<ProofNode> ProofNodeManager::clone(
302
    std::shared_ptr<ProofNode> pn)
303
{
304
56848
  const ProofNode* orig = pn.get();
305
113696
  std::unordered_map<const ProofNode*, std::shared_ptr<ProofNode>> visited;
306
56848
  std::unordered_map<const ProofNode*, std::shared_ptr<ProofNode>>::iterator it;
307
113696
  std::vector<const ProofNode*> visit;
308
113696
  std::shared_ptr<ProofNode> cloned;
309
56848
  visit.push_back(orig);
310
  const ProofNode* cur;
311
4300594
  while (!visit.empty())
312
  {
313
2121873
    cur = visit.back();
314
2121873
    it = visited.find(cur);
315
2121873
    if (it == visited.end())
316
    {
317
965695
      visited[cur] = nullptr;
318
      const std::vector<std::shared_ptr<ProofNode>>& children =
319
965695
          cur->getChildren();
320
2065025
      for (const std::shared_ptr<ProofNode>& cp : children)
321
      {
322
1099330
        visit.push_back(cp.get());
323
      }
324
965695
      continue;
325
    }
326
1156178
    visit.pop_back();
327
1156178
    if (it->second.get() == nullptr)
328
    {
329
1931390
      std::vector<std::shared_ptr<ProofNode>> cchildren;
330
      const std::vector<std::shared_ptr<ProofNode>>& children =
331
965695
          cur->getChildren();
332
2065025
      for (const std::shared_ptr<ProofNode>& cp : children)
333
      {
334
1099330
        it = visited.find(cp.get());
335
1099330
        Assert(it != visited.end());
336
1099330
        Assert(it->second != nullptr);
337
1099330
        cchildren.push_back(it->second);
338
      }
339
1931390
      cloned = std::make_shared<ProofNode>(
340
1931390
          cur->getRule(), cchildren, cur->getArguments());
341
965695
      visited[cur] = cloned;
342
      // we trust the above cloning does not change what is proven
343
965695
      cloned->d_proven = cur->d_proven;
344
    }
345
  }
346
56848
  Assert(visited.find(orig) != visited.end());
347
113696
  return visited[orig];
348
}
349
350
2373746
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
2373746
  Assert(pn != nullptr);
358
  // ---------------- check for cyclic
359
2373746
  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
2373746
  Assert(!pn->d_proven.isNull())
388
      << "ProofNodeManager::updateProofNode: invalid proof provided";
389
2373746
  if (needsCheck)
390
  {
391
    // We expect to prove the same thing as before
392
362472
    Node res = checkInternal(id, children, args, pn->d_proven);
393
181236
    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
181236
    Assert(res == pn->d_proven);
400
  }
401
402
  // we update its value
403
2373746
  pn->setValue(id, children, args);
404
2373746
  return true;
405
}
406
407
29340
}  // namespace cvc5