1 /******************************************
2 Copyright (c) 2016, Mate Soos
3
4 Permission is hereby granted, free of charge, to any person obtaining a copy
5 of this software and associated documentation files (the "Software"), to deal
6 in the Software without restriction, including without limitation the rights
7 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 copies of the Software, and to permit persons to whom the Software is
9 furnished to do so, subject to the following conditions:
10
11 The above copyright notice and this permission notice shall be included in
12 all copies or substantial portions of the Software.
13
14 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 THE SOFTWARE.
21 ***********************************************/
22
23 #include "hyperengine.h"
24 #include "clauseallocator.h"
25
26 using namespace CMSat;
27
HyperEngine(const SolverConf * _conf,Solver * _solver,std::atomic<bool> * _must_interrupt_inter)28 HyperEngine::HyperEngine(
29 const SolverConf *_conf
30 , Solver* _solver
31 , std::atomic<bool>* _must_interrupt_inter) :
32 PropEngine(_conf, _solver, _must_interrupt_inter)
33 {
34 }
35
36 HyperEngine::~HyperEngine() = default;
37
propagate_bfs(const uint64_t timeout)38 Lit HyperEngine::propagate_bfs(const uint64_t timeout)
39 {
40 timedOutPropagateFull = false;
41 propStats.otfHyperPropCalled++;
42 #ifdef VERBOSE_DEBUG_FULLPROP
43 cout << "Prop full BFS started" << endl;
44 #endif
45
46 PropBy confl;
47
48 //Assert startup: only 1 enqueued, uselessBin is empty
49 assert(uselessBin.empty());
50 //assert(decisionLevel() == 1);
51
52 //The toplevel decision has to be set specifically
53 //If we came here as part of a backtrack to decision level 1, then
54 //this is already set, and there is no need to set it
55 if (trail.size() - trail_lim.back() == 1) {
56 //Set up root node
57 Lit root = trail[qhead].lit;
58 varData[root.var()].reason = PropBy(~lit_Undef, false, false, false);
59 }
60
61 uint32_t nlBinQHead = qhead;
62 uint32_t lBinQHead = qhead;
63
64 needToAddBinClause.clear();
65 PropResult ret = PROP_NOTHING;
66 start:
67
68 //Early-abort if too much time was used (from prober)
69 if (propStats.otfHyperTime + propStats.bogoProps > timeout) {
70 timedOutPropagateFull = true;
71 return lit_Undef;
72 }
73
74 //Propagate binary irred
75 while (nlBinQHead < trail.size()) {
76 const Lit p = trail[nlBinQHead++].lit;
77 watch_subarray_const ws = watches[~p];
78 propStats.bogoProps += 1;
79 for(const Watched *k = ws.begin(), *end = ws.end()
80 ; k != end
81 ; k++
82 ) {
83
84 //If something other than irred binary, skip
85 if (!k->isBin() || k->red())
86 continue;
87
88 ret = prop_bin_with_ancestor_info(p, k, confl);
89 if (ret == PROP_FAIL)
90 return analyzeFail(confl);
91
92 }
93 propStats.bogoProps += ws.size()*4;
94 }
95
96 //Propagate binary redundant
97 ret = PROP_NOTHING;
98 while (lBinQHead < trail.size()) {
99 const Lit p = trail[lBinQHead].lit;
100 watch_subarray_const ws = watches[~p];
101 propStats.bogoProps += 1;
102 size_t done = 0;
103
104 for(const Watched *k = ws.begin(), *end = ws.end(); k != end; k++, done++) {
105
106 //If something other than redundant binary, skip
107 if (!k->isBin() || !k->red())
108 continue;
109
110 ret = prop_bin_with_ancestor_info(p, k, confl);
111 if (ret == PROP_FAIL) {
112 return analyzeFail(confl);
113 } else if (ret == PROP_SOMETHING) {
114 propStats.bogoProps += done*4;
115 goto start;
116 } else {
117 assert(ret == PROP_NOTHING);
118 }
119 }
120 lBinQHead++;
121 propStats.bogoProps += done*4;
122 }
123
124 ret = PROP_NOTHING;
125 while (qhead < trail.size()) {
126 const Lit p = trail[qhead].lit;
127 watch_subarray ws = watches[~p];
128 propStats.bogoProps += 1;
129
130 Watched* i = ws.begin();
131 Watched* j = ws.begin();
132 Watched* end = ws.end();
133 for(; i != end; i++) {
134 if (i->isBin()) {
135 *j++ = *i;
136 continue;
137 }
138
139 if (i->isClause()) {
140 ret = prop_normal_cl_with_ancestor_info(i, j, p, confl);
141 if (ret == PROP_SOMETHING || ret == PROP_FAIL) {
142 i++;
143 break;
144 } else {
145 assert(ret == PROP_NOTHING);
146 continue;
147 }
148 }
149 }
150 propStats.bogoProps += ws.size()*4;
151 while(i != end)
152 *j++ = *i++;
153 ws.shrink_(end-j);
154
155 if (ret == PROP_FAIL) {
156 return analyzeFail(confl);
157 } else if (ret == PROP_SOMETHING) {
158 propStats.bogoProps += ws.size()*4;
159 goto start;
160 }
161
162 qhead++;
163 propStats.bogoProps += ws.size()*4;
164 }
165
166 return lit_Undef;
167 }
168
169 //Add binary clause to deepest common ancestor
add_hyper_bin(const Lit p)170 void HyperEngine::add_hyper_bin(const Lit p)
171 {
172 propStats.otfHyperTime += 2;
173
174 Lit deepestAncestor = lit_Undef;
175 bool hyperBinNotAdded = true;
176 if (currAncestors.size() > 1) {
177 deepestAncestor = deepest_common_ancestor();
178
179 #ifdef VERBOSE_DEBUG_FULLPROP
180 cout << "Adding hyper-bin clause: " << p << " , " << ~deepestAncestor << endl;
181 #endif
182 needToAddBinClause.insert(BinaryClause(p, ~deepestAncestor, true));
183 *drat << add << p << (~deepestAncestor)
184 #ifdef STATS_NEEDED
185 << 0
186 << sumConflicts
187 #endif
188 << fin;
189
190 hyperBinNotAdded = false;
191 } else {
192 //0-level propagation is NEVER made by propFull
193 assert(currAncestors.size() > 0);
194
195 #ifdef VERBOSE_DEBUG_FULLPROP
196 cout
197 << "Not adding hyper-bin because only ONE lit is not set at"
198 << "level 0 in long clause, but that long clause needs to be cleaned"
199 << endl;
200 #endif
201 deepestAncestor = currAncestors[0];
202 hyperBinNotAdded = true;
203 }
204
205 enqueue_with_acestor_info(p, deepestAncestor, true);
206 varData[p.var()].reason.setHyperbin(true);
207 varData[p.var()].reason.setHyperbinNotAdded(hyperBinNotAdded);
208 }
209
210 /**
211 We can try both ways: either binary clause can be removed.
212 Try to remove one, then the other
213 Return which one is to be removed
214 */
remove_which_bin_due_to_trans_red(Lit conflict,Lit thisAncestor,bool thisStepRed)215 Lit HyperEngine::remove_which_bin_due_to_trans_red(
216 Lit conflict
217 , Lit thisAncestor
218 , bool thisStepRed
219 ) {
220 propStats.otfHyperTime += 1;
221 const PropBy& data = varData[conflict.var()].reason;
222
223 bool onlyIrred = !data.isRedStep();
224 Lit lookingForAncestor = data.getAncestor();
225
226 if (thisAncestor == lit_Undef || lookingForAncestor == lit_Undef)
227 return lit_Undef;
228
229 propStats.otfHyperTime += 1;
230 bool second_is_deeper = false;
231 bool ambivalent = true;
232 if (use_depth_trick) {
233 ambivalent = depth[thisAncestor.var()] == depth[lookingForAncestor.var()];
234 if (depth[thisAncestor.var()] < depth[lookingForAncestor.var()]) {
235 second_is_deeper = true;
236 }
237 }
238 #ifdef DEBUG_DEPTH
239 cout
240 << "1st: " << std::setw(6) << thisAncestor
241 << " depth: " << std::setw(4) << depth[thisAncestor.var()]
242 << " 2nd: " << std::setw(6) << lookingForAncestor
243 << " depth: " << std::setw(4) << depth[lookingForAncestor.var()]
244 ;
245 #endif
246
247
248 if ((ambivalent || !second_is_deeper) &&
249 is_ancestor_of(
250 conflict
251 , thisAncestor
252 , thisStepRed
253 , onlyIrred
254 , lookingForAncestor
255 )
256 ) {
257 #ifdef DEBUG_DEPTH
258 cout << " -- OK" << endl;
259 #endif
260 //assert(ambivalent || !second_is_deeper);
261 return thisAncestor;
262 }
263
264 onlyIrred = !thisStepRed;
265 thisStepRed = data.isRedStep();
266 std::swap(lookingForAncestor, thisAncestor);
267 if ((ambivalent || second_is_deeper) &&
268 is_ancestor_of(
269 conflict
270 , thisAncestor
271 , thisStepRed
272 , onlyIrred
273 , lookingForAncestor
274 )
275 ) {
276 #ifdef DEBUG_DEPTH
277 cout << " -- OK" << endl;
278 #endif
279 //assert(ambivalent || second_is_deeper);
280 return thisAncestor;
281 }
282
283 #ifdef DEBUG_DEPTH
284 cout << " -- NOTK" << endl;
285 #endif
286
287 return lit_Undef;
288 }
289
290 /**
291 hop backwards from thisAncestor until:
292 1) we reach ancestor of 'conflict' -- at this point, we return TRUE
293 2) we reach an invalid point. Either root, or an invalid hop. We return FALSE.
294 */
is_ancestor_of(const Lit conflict,Lit thisAncestor,const bool thisStepRed,const bool onlyIrred,const Lit lookingForAncestor)295 bool HyperEngine::is_ancestor_of(
296 const Lit conflict
297 , Lit thisAncestor
298 , const bool thisStepRed
299 , const bool onlyIrred
300 , const Lit lookingForAncestor
301 ) {
302 propStats.otfHyperTime += 1;
303 #ifdef VERBOSE_DEBUG_FULLPROP
304 cout << "is_ancestor_of."
305 << "conflict: " << conflict
306 << " thisAncestor: " << thisAncestor
307 << " thisStepRed: " << thisStepRed
308 << " onlyIrred: " << onlyIrred
309 << " lookingForAncestor: " << lookingForAncestor << endl;
310 #endif
311
312 //Was propagated at level 0 -- clauseCleaner will remove the clause
313 if (lookingForAncestor == lit_Undef)
314 return false;
315
316 if (lookingForAncestor == thisAncestor) {
317 #ifdef VERBOSE_DEBUG_FULLPROP
318 cout << "Last position inside prop queue is not saved during propFull" << endl
319 << "This may be the same exact binary clause -- not removing" << endl;
320 #endif
321 return false;
322 }
323
324 #ifdef VERBOSE_DEBUG_FULLPROP
325 cout << "Looking for ancestor of " << conflict << " : " << lookingForAncestor << endl;
326 cout << "This step based on redundant cl? " << (thisStepRed ? "yes" : "false") << endl;
327 cout << "Only irred is acceptable?" << (onlyIrred ? "yes" : "no") << endl;
328 cout << "This step would be based on redundant cl?" << (thisStepRed ? "yes" : "no") << endl;
329 #endif
330
331 if (onlyIrred && thisStepRed) {
332 #ifdef VERBOSE_DEBUG_FULLPROP
333 cout << "This step doesn't work -- is redundant but needs irred" << endl;
334 #endif
335 return false;
336 }
337
338 //This is as low as we should search -- we cannot find what we are searchig for lower than this
339 const size_t bottom = depth[lookingForAncestor.var()];
340
341 while(thisAncestor != lit_Undef
342 && (!use_depth_trick || bottom <= depth[thisAncestor.var()])
343 ) {
344 #ifdef VERBOSE_DEBUG_FULLPROP
345 cout << "Current acestor: " << thisAncestor
346 << " redundant step? " << varData[thisAncestor.var()].reason.isRedStep()
347 << endl;
348 #endif
349
350 if (thisAncestor == conflict) {
351 #ifdef VERBOSE_DEBUG_FULLPROP
352 cout << "We are trying to step over the conflict."
353 << " That would create a loop." << endl;
354 #endif
355 return false;
356 }
357
358 if (thisAncestor == lookingForAncestor) {
359 #ifdef VERBOSE_DEBUG_FULLPROP
360 cout << "Ancestor found" << endl;
361 #endif
362 return true;
363 }
364
365 const PropBy& data = varData[thisAncestor.var()].reason;
366 if ((onlyIrred && data.isRedStep())
367 || data.getHyperbinNotAdded()
368 ) {
369 #ifdef VERBOSE_DEBUG_FULLPROP
370 cout << "Wrong kind of hop would be needed" << endl;
371 #endif
372 return false; //reached would-be redundant hop (but this is irred)
373 }
374
375 thisAncestor = data.getAncestor();
376 propStats.otfHyperTime += 1;
377 }
378
379 #ifdef VERBOSE_DEBUG_FULLPROP
380 cout << "Exit, reached root" << endl;
381 #endif
382
383 return false;
384 }
385
add_hyper_bin(const Lit p,const Clause & cl)386 void HyperEngine::add_hyper_bin(const Lit p, const Clause& cl)
387 {
388 assert(value(p.var()) == l_Undef);
389
390 #ifdef VERBOSE_DEBUG_FULLPROP
391 cout << "Enqueing " << p
392 << " with ancestor clause: " << cl
393 << endl;
394 #endif
395
396 currAncestors.clear();
397 size_t i = 0;
398 for (Clause::const_iterator
399 it = cl.begin(), end = cl.end()
400 ; it != end
401 ; ++it, i++
402 ) {
403 if (*it != p) {
404 assert(value(*it) == l_False);
405 if (varData[it->var()].level != 0)
406 currAncestors.push_back(~*it);
407 }
408 }
409
410 add_hyper_bin(p);
411 }
412
413 //Analyze why did we fail at decision level 1
analyzeFail(const PropBy propBy)414 Lit HyperEngine::analyzeFail(const PropBy propBy)
415 {
416 //Clear out the datastructs we will be usin
417 currAncestors.clear();
418
419 //First, we set the ancestors, based on the clause
420 //Each literal in the clause is an ancestor. So just 'push' them inside the
421 //'currAncestors' variable
422 switch(propBy.getType()) {
423 case binary_t: {
424 const Lit lit = ~propBy.lit2();
425 if (varData[lit.var()].level != 0)
426 currAncestors.push_back(lit);
427
428 if (varData[failBinLit.var()].level != 0)
429 currAncestors.push_back(~failBinLit);
430
431 break;
432 }
433
434 case clause_t: {
435 const uint32_t offset = propBy.get_offset();
436 const Clause& cl = *cl_alloc.ptr(offset);
437 for(size_t i = 0; i < cl.size(); i++) {
438 if (varData[cl[i].var()].level != 0)
439 currAncestors.push_back(~cl[i]);
440 }
441 break;
442 }
443
444 #ifdef USE_GAUSS
445 case xor_t:
446 #endif
447 case null_clause_t:
448 assert(false);
449 break;
450 }
451
452 Lit foundLit = deepest_common_ancestor();
453
454 return foundLit;
455 }
456
deepest_common_ancestor()457 Lit HyperEngine::deepest_common_ancestor()
458 {
459 //Then, we go back on each ancestor recursively, and exit on the first one
460 //that unifies ALL the previous ancestors. That is the lowest common ancestor
461 assert(toClear.empty());
462 Lit foundLit = lit_Undef;
463 while(foundLit == lit_Undef) {
464 #ifdef VERBOSE_DEBUG_FULLPROP
465 cout << "LEVEL analyzeFail" << endl;
466 #endif
467 size_t num_lit_undef = 0;
468 for (vector<Lit>::iterator
469 it = currAncestors.begin(), end = currAncestors.end()
470 ; it != end
471 ; ++it
472 ) {
473 propStats.otfHyperTime += 1;
474
475 //We have reached the top of the graph, the other 'threads' that
476 //are still stepping back will find which literal is the lowest
477 //common ancestor
478 if (*it == lit_Undef) {
479 #ifdef VERBOSE_DEBUG_FULLPROP
480 cout << "seen lit_Undef" << endl;
481 #endif
482 num_lit_undef++;
483 assert(num_lit_undef != currAncestors.size());
484 continue;
485 }
486
487 //Increase path count
488 seen[it->toInt()]++;
489
490 //Visited counter has to be cleared later, so add it to the
491 //to-be-cleared set
492 if (seen[it->toInt()] == 1)
493 toClear.push_back(*it);
494
495 #ifdef VERBOSE_DEBUG_FULLPROP
496 cout << "seen " << *it << " : " << seen[it->toInt()] << endl;
497 #endif
498
499 //Is this point where all the 'threads' that are stepping backwards
500 //reach each other? If so, we have found what we were looking for!
501 //We can exit, and return 'foundLit'
502 if (seen[it->toInt()] == currAncestors.size()) {
503 foundLit = *it;
504 break;
505 }
506
507 //Update ancestor to its own ancestor, i.e. step up this 'thread'
508 *it = varData[it->var()].reason.getAncestor();
509 }
510 }
511 #ifdef VERBOSE_DEBUG_FULLPROP
512 cout << "END" << endl;
513 #endif
514 assert(foundLit != lit_Undef);
515
516 //Clear nodes we have visited
517 propStats.otfHyperTime += toClear.size()/2;
518 for(const Lit lit: toClear) {
519 seen[lit.toInt()] = 0;
520 }
521 toClear.clear();
522
523 return foundLit;
524 }
525
remove_bin_clause(Lit lit)526 void HyperEngine::remove_bin_clause(Lit lit)
527 {
528 //The binary clause we should remove
529 const BinaryClause clauseToRemove(
530 ~varData[lit.var()].reason.getAncestor()
531 , lit
532 , varData[lit.var()].reason.isRedStep()
533 );
534
535 //We now remove the clause
536 //If it's hyper-bin, then we remove the to-be-added hyper-binary clause
537 //However, if the hyper-bin was never added because only 1 literal was unbound at level 0 (i.e. through
538 //clause cleaning, the clause would have been 2-long), then we don't do anything.
539 if (!varData[lit.var()].reason.getHyperbin()) {
540 #ifdef VERBOSE_DEBUG_FULLPROP
541 cout << "Normal removing clause " << clauseToRemove << endl;
542 #endif
543 propStats.otfHyperTime += 2;
544 uselessBin.insert(clauseToRemove);
545 } else if (!varData[lit.var()].reason.getHyperbinNotAdded()) {
546 #ifdef VERBOSE_DEBUG_FULLPROP
547 cout << "Removing hyper-bin clause " << clauseToRemove << endl;
548 #endif
549 propStats.otfHyperTime += needToAddBinClause.size()/4;
550 std::set<BinaryClause>::iterator it = needToAddBinClause.find(clauseToRemove);
551
552 //In case this is called after a backtrack to decisionLevel 1
553 //then in fact we might have already cleaned the
554 //'needToAddBinClause'. When called from probing, the IF below
555 //must ALWAYS be true
556 if (it != needToAddBinClause.end()) {
557 propStats.otfHyperTime += 2;
558 needToAddBinClause.erase(it);
559 }
560 //This will subsume the clause later, so don't remove it
561 }
562 }
563
prop_bin_with_ancestor_info(const Lit p,const Watched * k,PropBy & confl)564 PropResult HyperEngine::prop_bin_with_ancestor_info(
565 const Lit p
566 , const Watched* k
567 , PropBy& confl
568 ) {
569 const Lit lit = k->lit2();
570 const lbool val = value(lit);
571 if (val == l_Undef) {
572 //Never propagated before
573 enqueue_with_acestor_info(lit, p, k->red());
574 return PROP_SOMETHING;
575
576 } else if (val == l_False) {
577 //Conflict
578 #ifdef VERBOSE_DEBUG_FULLPROP
579 cout << "Conflict from " << p << " , " << lit << endl;
580 #endif //VERBOSE_DEBUG_FULLPROP
581
582 //Update stats
583 if (k->red())
584 lastConflictCausedBy = ConflCausedBy::binred;
585 else
586 lastConflictCausedBy = ConflCausedBy::binirred;
587
588 failBinLit = lit;
589 confl = PropBy(~p, k->red());
590 return PROP_FAIL;
591
592 } else if (varData[lit.var()].level != 0 && perform_transitive_reduction) {
593 //Propaged already
594 assert(val == l_True);
595
596 #ifdef VERBOSE_DEBUG_FULLPROP
597 cout << "Lit " << p << " also wants to propagate " << lit << endl;
598 #endif
599 Lit remove = remove_which_bin_due_to_trans_red(lit, p, k->red());
600
601 //Remove this one
602 if (remove == p) {
603 Lit origAnc = varData[lit.var()].reason.getAncestor();
604 assert(origAnc != lit_Undef);
605
606 remove_bin_clause(lit);
607
608 //Update data indicating what lead to lit
609 varData[lit.var()].reason = PropBy(~p, k->red(), false, false);
610 assert(varData[p.var()].level != 0);
611 depth[lit.var()] = depth[p.var()] + 1;
612 //NOTE: we don't update the levels of other literals... :S
613
614 //for correctness, we would need this, but that would need re-writing of history :S
615 //if (!onlyIrred) return PropBy();
616
617 } else if (remove != lit_Undef) {
618 #ifdef VERBOSE_DEBUG_FULLPROP
619 cout << "Removing this bin clause" << endl;
620 #endif
621 propStats.otfHyperTime += 2;
622 uselessBin.insert(BinaryClause(~p, lit, k->red()));
623 }
624 }
625
626 return PROP_NOTHING;
627 }
628
629
prop_normal_cl_with_ancestor_info(Watched * i,Watched * & j,const Lit p,PropBy & confl)630 PropResult HyperEngine::prop_normal_cl_with_ancestor_info(
631 Watched* i
632 , Watched*& j
633 , const Lit p
634 , PropBy& confl
635 ) {
636 //Blocked literal is satisfied, so clause is satisfied
637 if (value(i->getBlockedLit()) == l_True) {
638 *j++ = *i;
639 return PROP_NOTHING;
640 }
641
642 //Dereference pointer
643 propStats.bogoProps += 4;
644 const ClOffset offset = i->get_offset();
645 Clause& c = *cl_alloc.ptr(offset);
646
647 PropResult ret = prop_normal_helper(c, offset, j, p);
648 if (ret != PROP_TODO)
649 return ret;
650
651 // Did not find watch -- clause is unit under assignment:
652 *j++ = *i;
653 if (value(c[0]) == l_False) {
654 return handle_normal_prop_fail(c, offset, confl);
655 }
656
657 add_hyper_bin(c[0], c);
658
659 return PROP_SOMETHING;
660 }
661
mem_used() const662 size_t HyperEngine::mem_used() const
663 {
664 size_t mem = 0;
665 mem += PropEngine::mem_used();
666 mem += currAncestors.capacity()*sizeof(Lit);
667
668 return mem;
669 }
670
enqueue_with_acestor_info(const Lit p,const Lit ancestor,const bool redStep)671 void HyperEngine::enqueue_with_acestor_info(
672 const Lit p
673 , const Lit ancestor
674 , const bool redStep
675 ) {
676 //only called at decision level 1 during solving OR
677 //during intree probing
678 enqueue(p, decisionLevel(), PropBy(~ancestor, redStep, false, false));
679
680 assert(varData[ancestor.var()].level != 0);
681
682 if (use_depth_trick) {
683 depth[p.var()] = depth[ancestor.var()] + 1;
684 } else {
685 depth[p.var()] = 0;
686 }
687 #if defined(DEBUG_DEPTH) || defined(VERBOSE_DEBUG_FULLPROP)
688 cout
689 << "Enqueued "
690 << std::setw(6) << (p)
691 << " by " << std::setw(6) << (~ancestor)
692 << " at depth " << std::setw(4) << depth[p.var()]
693 << " at dec level: " << decisionLevel()
694 << endl;
695 #endif
696 }
697