1 |
|
/****************************************************************************** |
2 |
|
* Top contributors (to current version): |
3 |
|
* Haniel Barbosa, Andrew Reynolds |
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 proof manager for Minisat. |
14 |
|
*/ |
15 |
|
|
16 |
|
#include "prop/sat_proof_manager.h" |
17 |
|
|
18 |
|
#include "options/proof_options.h" |
19 |
|
#include "proof/proof_node_algorithm.h" |
20 |
|
#include "proof/theory_proof_step_buffer.h" |
21 |
|
#include "prop/cnf_stream.h" |
22 |
|
#include "prop/minisat/minisat.h" |
23 |
|
|
24 |
|
namespace cvc5 { |
25 |
|
namespace prop { |
26 |
|
|
27 |
1254 |
SatProofManager::SatProofManager(Minisat::Solver* solver, |
28 |
|
CnfStream* cnfStream, |
29 |
|
context::UserContext* userContext, |
30 |
1254 |
ProofNodeManager* pnm) |
31 |
|
: d_solver(solver), |
32 |
|
d_cnfStream(cnfStream), |
33 |
|
d_pnm(pnm), |
34 |
|
d_resChains(pnm, true, userContext), |
35 |
|
d_resChainPg(userContext, pnm), |
36 |
|
d_assumptions(userContext), |
37 |
1254 |
d_conflictLit(undefSatVariable) |
38 |
|
{ |
39 |
1254 |
d_true = NodeManager::currentNM()->mkConst(true); |
40 |
1254 |
d_false = NodeManager::currentNM()->mkConst(false); |
41 |
1254 |
} |
42 |
|
|
43 |
|
void SatProofManager::printClause(const Minisat::Clause& clause) |
44 |
|
{ |
45 |
|
for (unsigned i = 0, size = clause.size(); i < size; ++i) |
46 |
|
{ |
47 |
|
SatLiteral satLit = MinisatSatSolver::toSatLiteral(clause[i]); |
48 |
|
Trace("sat-proof") << satLit << " "; |
49 |
|
} |
50 |
|
} |
51 |
|
|
52 |
85040 |
Node SatProofManager::getClauseNode(SatLiteral satLit) |
53 |
|
{ |
54 |
85040 |
Assert(d_cnfStream->getNodeCache().find(satLit) |
55 |
|
!= d_cnfStream->getNodeCache().end()) |
56 |
|
<< "SatProofManager::getClauseNode: literal " << satLit |
57 |
|
<< " undefined.\n"; |
58 |
85040 |
return d_cnfStream->getNodeCache()[satLit]; |
59 |
|
} |
60 |
|
|
61 |
487118 |
Node SatProofManager::getClauseNode(const Minisat::Clause& clause) |
62 |
|
{ |
63 |
974236 |
std::vector<Node> clauseNodes; |
64 |
2824408 |
for (unsigned i = 0, size = clause.size(); i < size; ++i) |
65 |
|
{ |
66 |
2337290 |
SatLiteral satLit = MinisatSatSolver::toSatLiteral(clause[i]); |
67 |
2337290 |
Assert(d_cnfStream->getNodeCache().find(satLit) |
68 |
|
!= d_cnfStream->getNodeCache().end()) |
69 |
|
<< "SatProofManager::getClauseNode: literal " << satLit |
70 |
|
<< " undefined\n"; |
71 |
2337290 |
clauseNodes.push_back(d_cnfStream->getNodeCache()[satLit]); |
72 |
|
} |
73 |
|
// order children by node id |
74 |
487118 |
std::sort(clauseNodes.begin(), clauseNodes.end()); |
75 |
974236 |
return NodeManager::currentNM()->mkNode(kind::OR, clauseNodes); |
76 |
|
} |
77 |
|
|
78 |
23735 |
void SatProofManager::startResChain(const Minisat::Clause& start) |
79 |
|
{ |
80 |
23735 |
if (Trace.isOn("sat-proof")) |
81 |
|
{ |
82 |
|
Trace("sat-proof") << "SatProofManager::startResChain: "; |
83 |
|
printClause(start); |
84 |
|
Trace("sat-proof") << "\n"; |
85 |
|
} |
86 |
23735 |
d_resLinks.emplace_back(getClauseNode(start), Node::null(), true); |
87 |
23735 |
} |
88 |
|
|
89 |
179050 |
void SatProofManager::addResolutionStep(Minisat::Lit lit, bool redundant) |
90 |
|
{ |
91 |
179050 |
SatLiteral satLit = MinisatSatSolver::toSatLiteral(lit); |
92 |
358100 |
Node litNode = d_cnfStream->getNodeCache()[satLit]; |
93 |
179050 |
bool negated = satLit.isNegated(); |
94 |
179050 |
Assert(!negated || litNode.getKind() == kind::NOT); |
95 |
179050 |
if (!redundant) |
96 |
|
{ |
97 |
285176 |
Trace("sat-proof") << "SatProofManager::addResolutionStep: {" |
98 |
285176 |
<< satLit.isNegated() << "} [" << satLit << "] " |
99 |
142588 |
<< ~satLit << "\n"; |
100 |
|
// if lit is negated then the chain resolution construction will use it as a |
101 |
|
// pivot occurring as is in the second clause and the node under the |
102 |
|
// negation in the first clause |
103 |
285176 |
d_resLinks.emplace_back(d_cnfStream->getNodeCache()[~satLit], |
104 |
285176 |
negated ? litNode[0] : litNode, |
105 |
427764 |
!satLit.isNegated()); |
106 |
|
} |
107 |
|
else |
108 |
|
{ |
109 |
72924 |
Trace("sat-proof") << "SatProofManager::addResolutionStep: redundant lit " |
110 |
36462 |
<< satLit << " stored\n"; |
111 |
36462 |
d_redundantLits.push_back(satLit); |
112 |
|
} |
113 |
179050 |
} |
114 |
|
|
115 |
296763 |
void SatProofManager::addResolutionStep(const Minisat::Clause& clause, |
116 |
|
Minisat::Lit lit) |
117 |
|
{ |
118 |
296763 |
SatLiteral satLit = MinisatSatSolver::toSatLiteral(lit); |
119 |
593526 |
Node litNode = d_cnfStream->getNodeCache()[satLit]; |
120 |
296763 |
bool negated = satLit.isNegated(); |
121 |
296763 |
Assert(!negated || litNode.getKind() == kind::NOT); |
122 |
593526 |
Node clauseNode = getClauseNode(clause); |
123 |
|
// if lit is negative then the chain resolution construction will use it as a |
124 |
|
// pivot occurring as is in the second clause and the node under the |
125 |
|
// negation in the first clause, which means that the third argument of the |
126 |
|
// tuple must be false |
127 |
296763 |
d_resLinks.emplace_back(clauseNode, negated ? litNode[0] : litNode, negated); |
128 |
296763 |
if (Trace.isOn("sat-proof")) |
129 |
|
{ |
130 |
|
Trace("sat-proof") << "SatProofManager::addResolutionStep: {" |
131 |
|
<< satLit.isNegated() << "} [" << ~satLit << "] "; |
132 |
|
printClause(clause); |
133 |
|
Trace("sat-proof") << "\nSatProofManager::addResolutionStep:\t" |
134 |
|
<< clauseNode << "\n"; |
135 |
|
} |
136 |
296763 |
} |
137 |
|
|
138 |
2555 |
void SatProofManager::endResChain(Minisat::Lit lit) |
139 |
|
{ |
140 |
2555 |
SatLiteral satLit = MinisatSatSolver::toSatLiteral(lit); |
141 |
5110 |
Trace("sat-proof") << "SatProofManager::endResChain: chain_res for " |
142 |
2555 |
<< satLit; |
143 |
2555 |
endResChain(getClauseNode(satLit), {satLit}); |
144 |
2555 |
} |
145 |
|
|
146 |
21180 |
void SatProofManager::endResChain(const Minisat::Clause& clause) |
147 |
|
{ |
148 |
21180 |
if (Trace.isOn("sat-proof")) |
149 |
|
{ |
150 |
|
Trace("sat-proof") << "SatProofManager::endResChain: chain_res for "; |
151 |
|
printClause(clause); |
152 |
|
} |
153 |
42360 |
std::set<SatLiteral> clauseLits; |
154 |
259963 |
for (unsigned i = 0, size = clause.size(); i < size; ++i) |
155 |
|
{ |
156 |
238783 |
clauseLits.insert(MinisatSatSolver::toSatLiteral(clause[i])); |
157 |
|
} |
158 |
21180 |
endResChain(getClauseNode(clause), clauseLits); |
159 |
21180 |
} |
160 |
|
|
161 |
23735 |
void SatProofManager::endResChain(Node conclusion, |
162 |
|
const std::set<SatLiteral>& conclusionLits) |
163 |
|
{ |
164 |
23735 |
Trace("sat-proof") << ", " << conclusion << "\n"; |
165 |
23735 |
if (d_resChains.hasGenerator(conclusion)) |
166 |
|
{ |
167 |
890 |
Trace("sat-proof") |
168 |
445 |
<< "SatProofManager::endResChain: skip repeated proof of " << conclusion |
169 |
445 |
<< "\n"; |
170 |
|
// clearing |
171 |
445 |
d_resLinks.clear(); |
172 |
445 |
d_redundantLits.clear(); |
173 |
445 |
return; |
174 |
|
} |
175 |
|
// first process redundant literals |
176 |
46443 |
std::set<SatLiteral> visited; |
177 |
23290 |
unsigned pos = d_resLinks.size(); |
178 |
59108 |
for (SatLiteral satLit : d_redundantLits) |
179 |
|
{ |
180 |
35818 |
processRedundantLit(satLit, conclusionLits, visited, pos); |
181 |
|
} |
182 |
23290 |
d_redundantLits.clear(); |
183 |
|
// build resolution chain |
184 |
|
// the conclusion is stored already in the arguments because of the |
185 |
|
// possibility of reordering |
186 |
46443 |
std::vector<Node> children, args{conclusion}; |
187 |
582623 |
for (unsigned i = 0, size = d_resLinks.size(); i < size; ++i) |
188 |
|
{ |
189 |
1118666 |
Node clause, pivot; |
190 |
|
bool posFirst; |
191 |
559333 |
std::tie(clause, pivot, posFirst) = d_resLinks[i]; |
192 |
559333 |
children.push_back(clause); |
193 |
559333 |
Trace("sat-proof") << "SatProofManager::endResChain: "; |
194 |
559333 |
if (i > 0) |
195 |
|
{ |
196 |
1072086 |
Trace("sat-proof") << "{" << posFirst << "} [" |
197 |
536043 |
<< d_cnfStream->getTranslationCache()[pivot] << "] "; |
198 |
|
} |
199 |
|
// special case for clause (or l1 ... ln) being a single literal |
200 |
|
// corresponding itself to a clause, which is indicated by the pivot being |
201 |
|
// of the form (not (or l1 ... ln)) |
202 |
1118666 |
if (clause.getKind() == kind::OR |
203 |
1677999 |
&& !(pivot.getKind() == kind::NOT && pivot[0].getKind() == kind::OR |
204 |
559333 |
&& pivot[0] == clause)) |
205 |
|
{ |
206 |
2215607 |
for (unsigned j = 0, sizeJ = clause.getNumChildren(); j < sizeJ; ++j) |
207 |
|
{ |
208 |
1823855 |
Trace("sat-proof") << d_cnfStream->getTranslationCache()[clause[j]]; |
209 |
1823855 |
if (j < sizeJ - 1) |
210 |
|
{ |
211 |
1432103 |
Trace("sat-proof") << ", "; |
212 |
|
} |
213 |
|
} |
214 |
|
} |
215 |
|
else |
216 |
|
{ |
217 |
335162 |
Assert(d_cnfStream->getTranslationCache().find(clause) |
218 |
|
!= d_cnfStream->getTranslationCache().end()) |
219 |
167581 |
<< "clause node " << clause |
220 |
167581 |
<< " treated as unit has no literal. Pivot is " << pivot << "\n"; |
221 |
167581 |
Trace("sat-proof") << d_cnfStream->getTranslationCache()[clause]; |
222 |
|
} |
223 |
559333 |
Trace("sat-proof") << " : "; |
224 |
559333 |
if (i > 0) |
225 |
|
{ |
226 |
536043 |
args.push_back(posFirst ? d_true : d_false); |
227 |
536043 |
args.push_back(pivot); |
228 |
536043 |
Trace("sat-proof") << "{" << posFirst << "} [" << pivot << "] "; |
229 |
|
} |
230 |
559333 |
Trace("sat-proof") << clause << "\n"; |
231 |
|
} |
232 |
|
// clearing |
233 |
23290 |
d_resLinks.clear(); |
234 |
|
// whether no-op |
235 |
23290 |
if (children.size() == 1) |
236 |
|
{ |
237 |
4 |
Trace("sat-proof") << "SatProofManager::endResChain: no-op. The conclusion " |
238 |
2 |
<< conclusion << " is set-equal to premise " |
239 |
2 |
<< children[0] << "\n"; |
240 |
2 |
return; |
241 |
|
} |
242 |
|
// whether trivial cycle |
243 |
582214 |
for (const Node& child : children) |
244 |
|
{ |
245 |
559061 |
if (conclusion == child) |
246 |
|
{ |
247 |
270 |
Trace("sat-proof") |
248 |
135 |
<< "SatProofManager::endResChain: no-op. The conclusion " |
249 |
135 |
<< conclusion << " is equal to a premise\n"; |
250 |
135 |
return; |
251 |
|
} |
252 |
|
} |
253 |
|
// since the conclusion can be both reordered and without duplicates and the |
254 |
|
// SAT solver does not record this information, we use a MACRO_RESOLUTION |
255 |
|
// step, which bypasses these. Note that we could generate a chain resolution |
256 |
|
// rule here by explicitly computing the detailed steps, but leave this for |
257 |
|
// post-processing. |
258 |
46306 |
ProofStep ps(PfRule::MACRO_RESOLUTION_TRUST, children, args); |
259 |
|
// note that we must tell the proof generator to overwrite if repeated |
260 |
23153 |
d_resChainPg.addStep(conclusion, ps); |
261 |
|
// the premises of this resolution may not have been justified yet, so we do |
262 |
|
// not pass assumptions to check closedness |
263 |
23153 |
d_resChains.addLazyStep(conclusion, &d_resChainPg); |
264 |
|
} |
265 |
|
|
266 |
140965 |
void SatProofManager::processRedundantLit( |
267 |
|
SatLiteral lit, |
268 |
|
const std::set<SatLiteral>& conclusionLits, |
269 |
|
std::set<SatLiteral>& visited, |
270 |
|
unsigned pos) |
271 |
|
{ |
272 |
281930 |
Trace("sat-proof") << push |
273 |
140965 |
<< "SatProofManager::processRedundantLit: Lit: " << lit |
274 |
140965 |
<< "\n"; |
275 |
140965 |
if (visited.count(lit)) |
276 |
|
{ |
277 |
34415 |
Trace("sat-proof") << "already visited\n" << pop; |
278 |
96565 |
return; |
279 |
|
} |
280 |
|
Minisat::Solver::TCRef reasonRef = |
281 |
106550 |
d_solver->reason(Minisat::var(MinisatSatSolver::toMinisatLit(lit))); |
282 |
106550 |
if (reasonRef == Minisat::Solver::TCRef_Undef) |
283 |
|
{ |
284 |
55470 |
Trace("sat-proof") << "unit, add link to lit " << lit << " at pos: " << pos |
285 |
27735 |
<< "\n" |
286 |
27735 |
<< pop; |
287 |
27735 |
visited.insert(lit); |
288 |
55470 |
Node litNode = d_cnfStream->getNodeCache()[lit]; |
289 |
27735 |
bool negated = lit.isNegated(); |
290 |
27735 |
Assert(!negated || litNode.getKind() == kind::NOT); |
291 |
|
|
292 |
83205 |
d_resLinks.emplace(d_resLinks.begin() + pos, |
293 |
55470 |
d_cnfStream->getNodeCache()[~lit], |
294 |
55470 |
negated ? litNode[0] : litNode, |
295 |
138675 |
!negated); |
296 |
27735 |
return; |
297 |
|
} |
298 |
78815 |
Assert(reasonRef >= 0 && reasonRef < d_solver->ca.size()) |
299 |
|
<< "reasonRef " << reasonRef << " and d_satSolver->ca.size() " |
300 |
|
<< d_solver->ca.size() << "\n"; |
301 |
78815 |
const Minisat::Clause& reason = d_solver->ca[reasonRef]; |
302 |
78815 |
if (Trace.isOn("sat-proof")) |
303 |
|
{ |
304 |
|
Trace("sat-proof") << "reason: "; |
305 |
|
printClause(reason); |
306 |
|
Trace("sat-proof") << "\n"; |
307 |
|
} |
308 |
|
// check if redundant literals in the reason. The first literal is the one we |
309 |
|
// will be eliminating, so we check the others |
310 |
257418 |
for (unsigned i = 1, size = reason.size(); i < size; ++i) |
311 |
|
{ |
312 |
178603 |
SatLiteral satLit = MinisatSatSolver::toSatLiteral(reason[i]); |
313 |
|
// if literal does not occur in the conclusion we process it as well |
314 |
178603 |
if (!conclusionLits.count(satLit)) |
315 |
|
{ |
316 |
105147 |
processRedundantLit(satLit, conclusionLits, visited, pos); |
317 |
|
} |
318 |
|
} |
319 |
78815 |
Assert(!visited.count(lit)); |
320 |
78815 |
visited.insert(lit); |
321 |
157630 |
Trace("sat-proof") << "clause, add link to lit " << lit << " at pos: " << pos |
322 |
78815 |
<< "\n" |
323 |
78815 |
<< pop; |
324 |
|
// add the step before steps for children. Note that the step is with the |
325 |
|
// reason, not only with ~lit, since the learned clause is built under the |
326 |
|
// assumption that the redundant literal is removed via the resolution with |
327 |
|
// the explanation of its negation |
328 |
157630 |
Node clauseNode = getClauseNode(reason); |
329 |
157630 |
Node litNode = d_cnfStream->getNodeCache()[lit]; |
330 |
78815 |
bool negated = lit.isNegated(); |
331 |
78815 |
Assert(!negated || litNode.getKind() == kind::NOT); |
332 |
236445 |
d_resLinks.emplace(d_resLinks.begin() + pos, |
333 |
|
clauseNode, |
334 |
157630 |
negated ? litNode[0] : litNode, |
335 |
315260 |
!negated); |
336 |
|
} |
337 |
|
|
338 |
34360 |
void SatProofManager::explainLit(SatLiteral lit, |
339 |
|
std::unordered_set<TNode>& premises) |
340 |
|
{ |
341 |
34360 |
Trace("sat-proof") << push << "SatProofManager::explainLit: Lit: " << lit; |
342 |
52674 |
Node litNode = getClauseNode(lit); |
343 |
34360 |
Trace("sat-proof") << " [" << litNode << "]\n"; |
344 |
|
// Note that if we had two literals for (= a b) and (= b a) and we had already |
345 |
|
// a proof for (= a b) this test would return true for (= b a), which could |
346 |
|
// lead to open proof. However we should never have two literals like this in |
347 |
|
// the SAT solver since they'd be rewritten to the same one |
348 |
34360 |
if (d_resChainPg.hasProofFor(litNode)) |
349 |
|
{ |
350 |
20934 |
Trace("sat-proof") << "SatProofManager::explainLit: already justified " |
351 |
10467 |
<< lit << ", ABORT\n" |
352 |
10467 |
<< pop; |
353 |
10467 |
return; |
354 |
|
} |
355 |
|
Minisat::Solver::TCRef reasonRef = |
356 |
23893 |
d_solver->reason(Minisat::var(MinisatSatSolver::toMinisatLit(lit))); |
357 |
23893 |
if (reasonRef == Minisat::Solver::TCRef_Undef) |
358 |
|
{ |
359 |
5577 |
Trace("sat-proof") << "SatProofManager::explainLit: no SAT reason\n" << pop; |
360 |
5577 |
return; |
361 |
|
} |
362 |
18316 |
Assert(reasonRef >= 0 && reasonRef < d_solver->ca.size()) |
363 |
|
<< "reasonRef " << reasonRef << " and d_satSolver->ca.size() " |
364 |
|
<< d_solver->ca.size() << "\n"; |
365 |
18316 |
const Minisat::Clause& reason = d_solver->ca[reasonRef]; |
366 |
18316 |
unsigned size = reason.size(); |
367 |
18316 |
if (Trace.isOn("sat-proof")) |
368 |
|
{ |
369 |
|
Trace("sat-proof") << "SatProofManager::explainLit: with clause: "; |
370 |
|
printClause(reason); |
371 |
|
Trace("sat-proof") << "\n"; |
372 |
|
} |
373 |
|
#ifdef CVC5_ASSERTIONS |
374 |
|
// pedantically check that the negation of the literal to explain *does not* |
375 |
|
// occur in the reason, otherwise we will loop forever |
376 |
65782 |
for (unsigned i = 0; i < size; ++i) |
377 |
|
{ |
378 |
47466 |
AlwaysAssert(~MinisatSatSolver::toSatLiteral(reason[i]) != lit) |
379 |
|
<< "cyclic justification\n"; |
380 |
|
} |
381 |
|
#endif |
382 |
|
// add the reason clause first |
383 |
36630 |
std::vector<Node> children{getClauseNode(reason)}, args; |
384 |
|
// save in the premises |
385 |
18316 |
premises.insert(children.back()); |
386 |
|
// Since explainLit calls can reallocate memory in the |
387 |
|
// SAT solver, we directly get the literals we need to explain so we no |
388 |
|
// longer depend on the reference to reason |
389 |
36630 |
std::vector<Node> toExplain{children.back().begin(), children.back().end()}; |
390 |
18316 |
Trace("sat-proof") << push; |
391 |
65782 |
for (unsigned i = 0; i < size; ++i) |
392 |
|
{ |
393 |
|
#ifdef CVC5_ASSERTIONS |
394 |
|
// pedantically make sure that the reason stays the same |
395 |
47466 |
const Minisat::Clause& reloadedReason = d_solver->ca[reasonRef]; |
396 |
47466 |
AlwaysAssert(size == static_cast<unsigned>(reloadedReason.size())); |
397 |
47466 |
AlwaysAssert(children[0] == getClauseNode(reloadedReason)); |
398 |
|
#endif |
399 |
47466 |
SatLiteral currLit = d_cnfStream->getTranslationCache()[toExplain[i]]; |
400 |
|
// ignore the lit we are trying to explain... |
401 |
47466 |
if (currLit == lit) |
402 |
|
{ |
403 |
18316 |
continue; |
404 |
|
} |
405 |
58300 |
std::unordered_set<TNode> childPremises; |
406 |
29150 |
explainLit(~currLit, childPremises); |
407 |
|
// save to resolution chain premises / arguments |
408 |
29150 |
Assert(d_cnfStream->getNodeCache().find(currLit) |
409 |
|
!= d_cnfStream->getNodeCache().end()); |
410 |
29150 |
children.push_back(d_cnfStream->getNodeCache()[~currLit]); |
411 |
58300 |
Node currLitNode = d_cnfStream->getNodeCache()[currLit]; |
412 |
29150 |
bool negated = currLit.isNegated(); |
413 |
29150 |
Assert(!negated || currLitNode.getKind() == kind::NOT); |
414 |
|
// note this is the opposite of what is done in addResolutionStep. This is |
415 |
|
// because here the clause, which contains the literal being analyzed, is |
416 |
|
// the first clause rather than the second |
417 |
29150 |
args.push_back(!negated ? d_true : d_false); |
418 |
29150 |
args.push_back(negated ? currLitNode[0] : currLitNode); |
419 |
|
// add child premises and the child itself |
420 |
29150 |
premises.insert(childPremises.begin(), childPremises.end()); |
421 |
29150 |
premises.insert(d_cnfStream->getNodeCache()[~currLit]); |
422 |
|
} |
423 |
18316 |
if (Trace.isOn("sat-proof")) |
424 |
|
{ |
425 |
|
Trace("sat-proof") << pop << "SatProofManager::explainLit: chain_res for " |
426 |
|
<< lit << ", " << litNode << " with clauses:\n"; |
427 |
|
for (unsigned i = 0, csize = children.size(); i < csize; ++i) |
428 |
|
{ |
429 |
|
Trace("sat-proof") << "SatProofManager::explainLit: " << children[i]; |
430 |
|
if (i > 0) |
431 |
|
{ |
432 |
|
Trace("sat-proof") << " [" << args[i - 1] << "]"; |
433 |
|
} |
434 |
|
Trace("sat-proof") << "\n"; |
435 |
|
} |
436 |
|
} |
437 |
|
// if justification of children contains the expected conclusion, avoid the |
438 |
|
// cyclic proof by aborting. |
439 |
18316 |
if (premises.count(litNode)) |
440 |
|
{ |
441 |
4 |
Trace("sat-proof") << "SatProofManager::explainLit: CYCLIC PROOF of " << lit |
442 |
2 |
<< " [" << litNode << "], ABORT\n" |
443 |
2 |
<< pop; |
444 |
2 |
return; |
445 |
|
} |
446 |
18314 |
Trace("sat-proof") << pop; |
447 |
|
// create step |
448 |
18314 |
args.insert(args.begin(), litNode); |
449 |
36628 |
ProofStep ps(PfRule::MACRO_RESOLUTION_TRUST, children, args); |
450 |
18314 |
d_resChainPg.addStep(litNode, ps); |
451 |
|
// the premises in the limit of the justification may correspond to other |
452 |
|
// links in the chain which have, themselves, literals yet to be justified. So |
453 |
|
// we are not ready yet to check closedness w.r.t. CNF transformation of the |
454 |
|
// preprocessed assertions |
455 |
18314 |
d_resChains.addLazyStep(litNode, &d_resChainPg); |
456 |
|
} |
457 |
|
|
458 |
1362 |
void SatProofManager::finalizeProof(Node inConflictNode, |
459 |
|
const std::vector<SatLiteral>& inConflict) |
460 |
|
{ |
461 |
2724 |
Trace("sat-proof") |
462 |
1362 |
<< "SatProofManager::finalizeProof: conflicting clause node: " |
463 |
1362 |
<< inConflictNode << "\n"; |
464 |
|
// nothing to do |
465 |
1362 |
if (inConflictNode == d_false) |
466 |
|
{ |
467 |
413 |
return; |
468 |
|
} |
469 |
949 |
if (Trace.isOn("sat-proof-debug2")) |
470 |
|
{ |
471 |
|
Trace("sat-proof-debug2") |
472 |
|
<< push << "SatProofManager::finalizeProof: saved proofs in chain:\n"; |
473 |
|
std::map<Node, std::shared_ptr<ProofNode>> links = d_resChains.getLinks(); |
474 |
|
std::unordered_set<Node> skip; |
475 |
|
for (const std::pair<const Node, std::shared_ptr<ProofNode>>& link : links) |
476 |
|
{ |
477 |
|
if (skip.count(link.first)) |
478 |
|
{ |
479 |
|
continue; |
480 |
|
} |
481 |
|
auto it = d_cnfStream->getTranslationCache().find(link.first); |
482 |
|
if (it != d_cnfStream->getTranslationCache().end()) |
483 |
|
{ |
484 |
|
Trace("sat-proof-debug2") |
485 |
|
<< "SatProofManager::finalizeProof: " << it->second; |
486 |
|
} |
487 |
|
// a refl step added due to double elim negation, ignore |
488 |
|
else if (link.second->getRule() == PfRule::REFL) |
489 |
|
{ |
490 |
|
continue; |
491 |
|
} |
492 |
|
// a clause |
493 |
|
else |
494 |
|
{ |
495 |
|
Trace("sat-proof-debug2") << "SatProofManager::finalizeProof:"; |
496 |
|
Assert(link.first.getKind() == kind::OR) << link.first; |
497 |
|
for (const Node& n : link.first) |
498 |
|
{ |
499 |
|
it = d_cnfStream->getTranslationCache().find(n); |
500 |
|
Assert(it != d_cnfStream->getTranslationCache().end()); |
501 |
|
Trace("sat-proof-debug2") << it->second << " "; |
502 |
|
} |
503 |
|
} |
504 |
|
Trace("sat-proof-debug2") << "\n"; |
505 |
|
Trace("sat-proof-debug2") |
506 |
|
<< "SatProofManager::finalizeProof: " << link.first << "\n"; |
507 |
|
// get resolution |
508 |
|
Node cur = link.first; |
509 |
|
std::shared_ptr<ProofNode> pfn = link.second; |
510 |
|
while (pfn->getRule() != PfRule::MACRO_RESOLUTION_TRUST) |
511 |
|
{ |
512 |
|
Assert(pfn->getChildren().size() == 1 |
513 |
|
&& pfn->getChildren()[0]->getRule() == PfRule::ASSUME) |
514 |
|
<< *link.second.get() << "\n" |
515 |
|
<< *pfn.get(); |
516 |
|
cur = pfn->getChildren()[0]->getResult(); |
517 |
|
// retrieve justification of assumption in the links |
518 |
|
Assert(links.find(cur) != links.end()); |
519 |
|
pfn = links[cur]; |
520 |
|
// ignore it in the rest of the outside loop |
521 |
|
skip.insert(cur); |
522 |
|
} |
523 |
|
std::vector<Node> fassumps; |
524 |
|
expr::getFreeAssumptions(pfn.get(), fassumps); |
525 |
|
Trace("sat-proof-debug2") << push; |
526 |
|
for (const Node& fa : fassumps) |
527 |
|
{ |
528 |
|
Trace("sat-proof-debug2") << "SatProofManager::finalizeProof: - "; |
529 |
|
it = d_cnfStream->getTranslationCache().find(fa); |
530 |
|
if (it != d_cnfStream->getTranslationCache().end()) |
531 |
|
{ |
532 |
|
Trace("sat-proof-debug2") << it->second << "\n"; |
533 |
|
continue; |
534 |
|
} |
535 |
|
// then it's a clause |
536 |
|
Assert(fa.getKind() == kind::OR); |
537 |
|
for (const Node& n : fa) |
538 |
|
{ |
539 |
|
it = d_cnfStream->getTranslationCache().find(n); |
540 |
|
Assert(it != d_cnfStream->getTranslationCache().end()); |
541 |
|
Trace("sat-proof-debug2") << it->second << " "; |
542 |
|
} |
543 |
|
Trace("sat-proof-debug2") << "\n"; |
544 |
|
} |
545 |
|
Trace("sat-proof-debug2") << pop; |
546 |
|
Trace("sat-proof-debug2") |
547 |
|
<< "SatProofManager::finalizeProof: " << *pfn.get() << "\n=======\n"; |
548 |
|
; |
549 |
|
} |
550 |
|
Trace("sat-proof-debug2") << pop; |
551 |
|
} |
552 |
|
// We will resolve away of the literals l_1...l_n in inConflict. At this point |
553 |
|
// each ~l_i must be either explainable, the result of a previously saved |
554 |
|
// resolution chain, or an input. In account of it possibly being the first, |
555 |
|
// we call explainLit on each ~l_i while accumulating the children and |
556 |
|
// arguments for the resolution step to conclude false. |
557 |
1898 |
std::vector<Node> children{inConflictNode}, args; |
558 |
1898 |
std::unordered_set<TNode> premises; |
559 |
4541 |
for (unsigned i = 0, size = inConflict.size(); i < size; ++i) |
560 |
|
{ |
561 |
3592 |
Assert(d_cnfStream->getNodeCache().find(inConflict[i]) |
562 |
|
!= d_cnfStream->getNodeCache().end()); |
563 |
7184 |
std::unordered_set<TNode> childPremises; |
564 |
3592 |
explainLit(~inConflict[i], childPremises); |
565 |
7184 |
Node negatedLitNode = d_cnfStream->getNodeCache()[~inConflict[i]]; |
566 |
|
// save to resolution chain premises / arguments |
567 |
3592 |
children.push_back(negatedLitNode); |
568 |
7184 |
Node litNode = d_cnfStream->getNodeCache()[inConflict[i]]; |
569 |
3592 |
bool negated = inConflict[i].isNegated(); |
570 |
3592 |
Assert(!negated || litNode.getKind() == kind::NOT); |
571 |
|
// note this is the opposite of what is done in addResolutionStep. This is |
572 |
|
// because here the clause, which contains the literal being analyzed, is |
573 |
|
// the first clause rather than the second |
574 |
3592 |
args.push_back(!negated ? d_true : d_false); |
575 |
3592 |
args.push_back(negated ? litNode[0] : litNode); |
576 |
|
// add child premises and the child itself |
577 |
3592 |
premises.insert(childPremises.begin(), childPremises.end()); |
578 |
3592 |
premises.insert(negatedLitNode); |
579 |
3592 |
Trace("sat-proof") << "===========\n"; |
580 |
|
} |
581 |
949 |
if (Trace.isOn("sat-proof")) |
582 |
|
{ |
583 |
|
Trace("sat-proof") << "SatProofManager::finalizeProof: chain_res for false " |
584 |
|
"with clauses:\n"; |
585 |
|
for (unsigned i = 0, size = children.size(); i < size; ++i) |
586 |
|
{ |
587 |
|
Trace("sat-proof") << "SatProofManager::finalizeProof: " << children[i]; |
588 |
|
if (i > 0) |
589 |
|
{ |
590 |
|
Trace("sat-proof") << " [" << args[i - 1] << "]"; |
591 |
|
} |
592 |
|
Trace("sat-proof") << "\n"; |
593 |
|
} |
594 |
|
} |
595 |
|
// create step |
596 |
949 |
args.insert(args.begin(), d_false); |
597 |
1898 |
ProofStep ps(PfRule::MACRO_RESOLUTION_TRUST, children, args); |
598 |
949 |
d_resChainPg.addStep(d_false, ps); |
599 |
|
// not yet ready to check closedness because maybe only now we will justify |
600 |
|
// literals used in resolutions |
601 |
949 |
d_resChains.addLazyStep(d_false, &d_resChainPg); |
602 |
|
// Fix point justification of literals in leaves of the proof of false |
603 |
|
bool expanded; |
604 |
1198 |
do |
605 |
|
{ |
606 |
1198 |
expanded = false; |
607 |
1198 |
Trace("sat-proof") << "expand assumptions to prove false\n"; |
608 |
2396 |
std::shared_ptr<ProofNode> pfn = d_resChains.getProofFor(d_false); |
609 |
1198 |
Assert(pfn); |
610 |
1198 |
Trace("sat-proof-debug") << "sat proof of flase: " << *pfn.get() << "\n"; |
611 |
2396 |
std::vector<Node> fassumps; |
612 |
1198 |
expr::getFreeAssumptions(pfn.get(), fassumps); |
613 |
1198 |
if (Trace.isOn("sat-proof")) |
614 |
|
{ |
615 |
|
for (const Node& fa : fassumps) |
616 |
|
{ |
617 |
|
Trace("sat-proof") << "- "; |
618 |
|
auto it = d_cnfStream->getTranslationCache().find(fa); |
619 |
|
if (it != d_cnfStream->getTranslationCache().end()) |
620 |
|
{ |
621 |
|
Trace("sat-proof") << it->second << "\n"; |
622 |
|
Trace("sat-proof") << "- " << fa << "\n"; |
623 |
|
continue; |
624 |
|
} |
625 |
|
// then it's a clause |
626 |
|
Assert(fa.getKind() == kind::OR); |
627 |
|
for (const Node& n : fa) |
628 |
|
{ |
629 |
|
it = d_cnfStream->getTranslationCache().find(n); |
630 |
|
Assert(it != d_cnfStream->getTranslationCache().end()); |
631 |
|
Trace("sat-proof") << it->second << " "; |
632 |
|
} |
633 |
|
Trace("sat-proof") << "\n"; |
634 |
|
Trace("sat-proof") << "- " << fa << "\n"; |
635 |
|
} |
636 |
|
} |
637 |
|
|
638 |
|
// for each assumption, see if it has a reason |
639 |
108421 |
for (const Node& fa : fassumps) |
640 |
|
{ |
641 |
|
// ignore already processed assumptions |
642 |
136116 |
if (premises.count(fa)) |
643 |
|
{ |
644 |
28893 |
Trace("sat-proof") << "already processed assumption " << fa << "\n"; |
645 |
134498 |
continue; |
646 |
|
} |
647 |
|
// ignore input assumptions. This is necessary to avoid rare collisions |
648 |
|
// between input clauses and literals that are equivalent at the node |
649 |
|
// level. In trying to justify the literal below if, it was previously |
650 |
|
// propagated (say, in a previous check-sat call that survived the |
651 |
|
// user-context changes) but no longer holds, then we may introduce a |
652 |
|
// bogus proof for it, rather than keeping it as an input. |
653 |
155042 |
if (d_assumptions.contains(fa)) |
654 |
|
{ |
655 |
76712 |
Trace("sat-proof") << "input assumption " << fa << "\n"; |
656 |
76712 |
continue; |
657 |
|
} |
658 |
|
// ignore non-literals |
659 |
1618 |
auto it = d_cnfStream->getTranslationCache().find(fa); |
660 |
1618 |
if (it == d_cnfStream->getTranslationCache().end()) |
661 |
|
{ |
662 |
|
Trace("sat-proof") << "no lit assumption " << fa << "\n"; |
663 |
|
premises.insert(fa); |
664 |
|
continue; |
665 |
|
} |
666 |
3236 |
Trace("sat-proof") << "lit assumption (" << it->second << "), " << fa |
667 |
1618 |
<< "\n"; |
668 |
|
// mark another iteration for the loop, as some resolution link may be |
669 |
|
// connected because of the new justifications |
670 |
1618 |
expanded = true; |
671 |
3236 |
std::unordered_set<TNode> childPremises; |
672 |
1618 |
explainLit(it->second, childPremises); |
673 |
|
// add the premises used in the justification. We know they will have |
674 |
|
// been as expanded as possible |
675 |
1618 |
premises.insert(childPremises.begin(), childPremises.end()); |
676 |
|
// add free assumption itself |
677 |
1618 |
premises.insert(fa); |
678 |
|
} |
679 |
|
} while (expanded); |
680 |
|
// now we should be able to close it |
681 |
949 |
if (options::proofEagerChecking()) |
682 |
|
{ |
683 |
|
std::vector<Node> assumptionsVec; |
684 |
|
for (const Node& a : d_assumptions) |
685 |
|
{ |
686 |
|
assumptionsVec.push_back(a); |
687 |
|
} |
688 |
|
d_resChains.addLazyStep(d_false, &d_resChainPg, assumptionsVec); |
689 |
|
} |
690 |
|
} |
691 |
|
|
692 |
49 |
void SatProofManager::storeUnitConflict(Minisat::Lit inConflict) |
693 |
|
{ |
694 |
49 |
Assert(d_conflictLit == undefSatVariable); |
695 |
49 |
d_conflictLit = MinisatSatSolver::toSatLiteral(inConflict); |
696 |
49 |
} |
697 |
|
|
698 |
49 |
void SatProofManager::finalizeProof() |
699 |
|
{ |
700 |
49 |
Assert(d_conflictLit != undefSatVariable); |
701 |
98 |
Trace("sat-proof") |
702 |
49 |
<< "SatProofManager::finalizeProof: conflicting (lazy) satLit: " |
703 |
49 |
<< d_conflictLit << "\n"; |
704 |
49 |
finalizeProof(getClauseNode(d_conflictLit), {d_conflictLit}); |
705 |
49 |
} |
706 |
|
|
707 |
470 |
void SatProofManager::finalizeProof(Minisat::Lit inConflict, bool adding) |
708 |
|
{ |
709 |
470 |
SatLiteral satLit = MinisatSatSolver::toSatLiteral(inConflict); |
710 |
940 |
Trace("sat-proof") << "SatProofManager::finalizeProof: conflicting satLit: " |
711 |
470 |
<< satLit << "\n"; |
712 |
940 |
Node clauseNode = getClauseNode(satLit); |
713 |
470 |
if (adding) |
714 |
|
{ |
715 |
470 |
registerSatAssumptions({clauseNode}); |
716 |
|
} |
717 |
470 |
finalizeProof(clauseNode, {satLit}); |
718 |
470 |
} |
719 |
|
|
720 |
843 |
void SatProofManager::finalizeProof(const Minisat::Clause& inConflict, |
721 |
|
bool adding) |
722 |
|
{ |
723 |
843 |
if (Trace.isOn("sat-proof")) |
724 |
|
{ |
725 |
|
Trace("sat-proof") |
726 |
|
<< "SatProofManager::finalizeProof: conflicting clause: "; |
727 |
|
printClause(inConflict); |
728 |
|
Trace("sat-proof") << "\n"; |
729 |
|
} |
730 |
1686 |
std::vector<SatLiteral> clause; |
731 |
4329 |
for (unsigned i = 0, size = inConflict.size(); i < size; ++i) |
732 |
|
{ |
733 |
3486 |
clause.push_back(MinisatSatSolver::toSatLiteral(inConflict[i])); |
734 |
|
} |
735 |
1686 |
Node clauseNode = getClauseNode(inConflict); |
736 |
843 |
if (adding) |
737 |
|
{ |
738 |
19 |
registerSatAssumptions({clauseNode}); |
739 |
|
} |
740 |
843 |
finalizeProof(clauseNode, clause); |
741 |
843 |
} |
742 |
|
|
743 |
2811 |
std::shared_ptr<ProofNode> SatProofManager::getProof() |
744 |
|
{ |
745 |
2811 |
std::shared_ptr<ProofNode> pfn = d_resChains.getProofFor(d_false); |
746 |
2811 |
if (!pfn) |
747 |
|
{ |
748 |
852 |
pfn = d_pnm->mkAssume(d_false); |
749 |
|
} |
750 |
2811 |
return pfn; |
751 |
|
} |
752 |
|
|
753 |
23803 |
void SatProofManager::registerSatLitAssumption(Minisat::Lit lit) |
754 |
|
{ |
755 |
47606 |
Trace("sat-proof") << "SatProofManager::registerSatLitAssumption: - " |
756 |
47606 |
<< getClauseNode(MinisatSatSolver::toSatLiteral(lit)) |
757 |
23803 |
<< "\n"; |
758 |
23803 |
d_assumptions.insert(getClauseNode(MinisatSatSolver::toSatLiteral(lit))); |
759 |
23803 |
} |
760 |
|
|
761 |
735571 |
void SatProofManager::registerSatAssumptions(const std::vector<Node>& assumps) |
762 |
|
{ |
763 |
1471142 |
for (const Node& a : assumps) |
764 |
|
{ |
765 |
1471142 |
Trace("sat-proof") << "SatProofManager::registerSatAssumptions: - " << a |
766 |
735571 |
<< "\n"; |
767 |
735571 |
d_assumptions.insert(a); |
768 |
|
} |
769 |
735571 |
} |
770 |
|
|
771 |
|
} // namespace prop |
772 |
29337 |
} // namespace cvc5 |