rustsat-cadical 0.7.5

Interface to the SAT solver CaDiCaL for the RustSAT library.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
#include "internal.hpp"

namespace CaDiCaL {

/*------------------------------------------------------------------------*/

// Implements a variant of bounded variable elimination as originally
// described in our SAT'05 paper introducing 'SATeLite'.  This is an
// inprocessing version, i.e., it is interleaved with search and triggers
// subsumption and strengthening, blocked and covered clause elimination
// during elimination rounds.  It focuses only those variables which
// occurred in removed irredundant clauses since the last time an
// elimination round was run.  By bounding the maximum resolvent size we can
// run each elimination round until completion.  See the code of 'elim' for
// how elimination rounds are interleaved with blocked clause elimination
// and subsumption (which in turn also calls vivification and transitive
// reduction of the binary implication graph).

/*------------------------------------------------------------------------*/

inline double Internal::compute_elim_score (unsigned lit) {
  assert (1 <= lit), assert (lit <= (unsigned) max_var);
  const double pos = noccs (lit);
  const double neg = noccs (-lit);
  if (!pos)
    return -neg;
  if (!neg)
    return -pos;
  double sum = 0, prod = 0;
  if (opts.elimsum)
    sum = opts.elimsum * (pos + neg);
  if (opts.elimprod)
    prod = opts.elimprod * (pos * neg);
  return prod + sum;
}

/*------------------------------------------------------------------------*/

bool elim_more::operator() (unsigned a, unsigned b) {
  const auto s = internal->compute_elim_score (a);
  const auto t = internal->compute_elim_score (b);
  if (s > t)
    return true;
  if (s < t)
    return false;
  return a > b;
}

/*------------------------------------------------------------------------*/

// Note that the new fast subsumption algorithm implemented in 'subsume'
// does not distinguish between irredundant and redundant clauses and is
// also run during search to strengthen and remove 'sticky' redundant
// clauses but also irredundant ones.  So beside learned units during search
// or as consequence of other preprocessors, these subsumption rounds during
// search can remove (irredundant) clauses (and literals), which in turn
// might make new bounded variable elimination possible.  This is tested
// in the 'bool ineliminating ()' guard.

bool Internal::ineliminating () {

  if (!opts.elim)
    return false;
  if (!preprocessing && !opts.inprocessing)
    return false;
  if (preprocessing)
    assert (lim.preprocessing);

  // Respect (increasing) conflict limit.
  //
  if (lim.elim >= stats.conflicts)
    return false;

  // Wait until there are new units or new removed variables
  // (in removed or shrunken irredundant clauses and thus marked).
  //
  if (last.elim.fixed < stats.all.fixed)
    return true;
  if (last.elim.marked < stats.mark.elim)
    return true;

  // VERBOSE (3, "elim not scheduled due to fixpoint");
  return false;
}

/*------------------------------------------------------------------------*/

// Update the global elimination schedule after adding or removing a clause.

void Internal::elim_update_added_clause (Eliminator &eliminator,
                                         Clause *c) {
  assert (!c->redundant);
  ElimSchedule &schedule = eliminator.schedule;
  for (const auto &lit : *c) {
    if (!active (lit))
      continue;
    occs (lit).push_back (c);
    if (frozen (lit))
      continue;
    noccs (lit)++;
    const int idx = abs (lit);
    if (schedule.contains (idx))
      schedule.update (idx);
  }
}

void Internal::elim_update_removed_lit (Eliminator &eliminator, int lit) {
  if (!active (lit))
    return;
  if (frozen (lit))
    return;
  int64_t &score = noccs (lit);
  assert (score > 0);
  score--;
  const int idx = abs (lit);
  ElimSchedule &schedule = eliminator.schedule;
  if (schedule.contains (idx))
    schedule.update (idx);
  else {
    LOG ("rescheduling %d for elimination after removing clause", idx);
    schedule.push_back (idx);
  }
}

void Internal::elim_update_removed_clause (Eliminator &eliminator,
                                           Clause *c, int except) {
  assert (!c->redundant);
  for (const auto &lit : *c) {
    if (lit == except)
      continue;
    assert (lit != -except);
    elim_update_removed_lit (eliminator, lit);
  }
}

/*------------------------------------------------------------------------*/

// Since we do not have watches we have to do our own unit propagation
// during elimination as soon we find a unit clause.  This finds new units
// and also marks clauses satisfied by those units as garbage immediately.

void Internal::elim_propagate (Eliminator &eliminator, int root) {
  assert (val (root) > 0);
  vector<int> work;
  size_t i = 0;
  work.push_back (root);
  while (i < work.size ()) {
    int lit = work[i++];
    LOG ("elimination propagation of %d", lit);
    assert (val (lit) > 0);
    const Occs &ns = occs (-lit);
    for (const auto &c : ns) {
      if (c->garbage)
        continue;
      int unit = 0, satisfied = 0;
      for (const auto &other : *c) {
        const signed char tmp = val (other);
        if (tmp < 0)
          continue;
        if (tmp > 0) {
          satisfied = other;
          break;
        }
        if (unit)
          unit = INT_MIN;
        else
          unit = other;
      }
      if (satisfied) {
        LOG (c, "elimination propagation of %d finds %d satisfied", lit,
             satisfied);
        elim_update_removed_clause (eliminator, c, satisfied);
        mark_garbage (c);
      } else if (!unit) {
        LOG ("empty clause during elimination propagation of %d", lit);
        // need to set conflict = c for lrat
        conflict = c;
        learn_empty_clause ();
        conflict = 0;
        break;
      } else if (unit != INT_MIN) {
        LOG ("new unit %d during elimination propagation of %d", unit, lit);
        build_chain_for_units (unit, c, 0);
        assign_unit (unit);
        work.push_back (unit);
      }
    }
    if (unsat)
      break;
    const Occs &ps = occs (lit);
    for (const auto &c : ps) {
      if (c->garbage)
        continue;
      LOG (c, "elimination propagation of %d produces satisfied", lit);
      elim_update_removed_clause (eliminator, c, lit);
      mark_garbage (c);
    }
  }
}

/*------------------------------------------------------------------------*/

// On-the-fly self-subsuming resolution during variable elimination is due
// to HyoJung Han, Fabio Somenzi, SAT'09.  Basically while resolving two
// clauses we test the resolvent to be smaller than one of the antecedents.
// If this is the case the pivot can be removed from the antecedent
// on-the-fly and the resolution can be skipped during elimination.

void Internal::elim_on_the_fly_self_subsumption (Eliminator &eliminator,
                                                 Clause *c, int pivot) {
  LOG (c, "pivot %d on-the-fly self-subsuming resolution", pivot);
  stats.elimotfstr++;
  stats.strengthened++;
  assert (clause.empty ());
  for (const auto &lit : *c) {
    if (lit == pivot)
      continue;
    const signed char tmp = val (lit);
    assert (tmp <= 0);
    if (tmp < 0)
      continue;
    clause.push_back (lit);
  }
  Clause *r = new_resolved_irredundant_clause ();
  elim_update_added_clause (eliminator, r);
  clause.clear ();
  lrat_chain.clear ();
  elim_update_removed_clause (eliminator, c, pivot);
  mark_garbage (c);
}

/*------------------------------------------------------------------------*/

// Resolve two clauses on the pivot literal 'pivot', which is assumed to
// occur in opposite phases in 'c' and 'd'.  The actual resolvent is stored
// in the temporary global 'clause' if it is not redundant.  It is
// considered redundant if one of the clauses is already marked as garbage
// it is root level satisfied, the resolvent is empty, a unit, or produces a
// self-subsuming resolution, which results in the pivot to be removed from
// at least one of the antecedents.

// Note that current root level assignments are taken into account, i.e., by
// removing root level falsified literals.  The function returns true if the
// resolvent is not redundant and for instance has to be taken into account
// during bounded variable elimination.

// Detected units are immediately assigned and in case the last argument is
// true also propagated eagerly in a elimination specific propagation
// routine, which not only finds units but also updates the schedule.

// When this function is called during computation of the number of
// non-trivial (or non-satisfied) resolvents we can eagerly propagate units.
// But during actually adding the resolvents this results in problems as we
// found one rare test case '../test/trace/reg0056.trace' (out of billions),
// where the pivot itself was assigned during such a propagation while
// adding resolvents and lead to pushing a clause to the reconstruction
// stack that later flipped the value of the pivot (while all other literals
// in that clause were unit implied too).  Not pushing the pivot clauses to
// the reconstruction stack produced a wrong model too.  Our fix is to only
// eagerly propagate during computation of the number of resolvents and
// otherwise delay propagation until the end of elimination (which is less
// precise regarding scheduling but very rarely happens).

bool Internal::resolve_clauses (Eliminator &eliminator, Clause *c,
                                int pivot, Clause *d,
                                const bool propagate_eagerly) {

  assert (!c->redundant);
  assert (!d->redundant);

  stats.elimres++;

  if (c->garbage || d->garbage)
    return false;
  if (c->size > d->size) {
    pivot = -pivot;
    swap (c, d);
  }

  assert (!level);
  assert (clause.empty ());

  int satisfied = 0;    // Contains this satisfying literal.
  int tautological = 0; // Clashing literal if tautological.

  int s = 0; // Actual literals from 'c'.
  int t = 0; // Actual literals from 'd'.

  // First determine whether the first antecedent is satisfied, add its
  // literals to 'clause' and mark them (except for 'pivot').
  //
  for (const auto &lit : *c) {
    if (lit == pivot) {
      s++;
      continue;
    }
    assert (lit != -pivot);
    const signed char tmp = val (lit);
    if (tmp > 0) {
      satisfied = lit;
      break;
    } else if (tmp < 0) {
      if (!lrat)
        continue;
      Flags &f = flags (lit);
      if (f.seen)
        continue;
      analyzed.push_back (lit);
      f.seen = true;
      int64_t id = unit_id (-lit);
      lrat_chain.push_back (id);
      continue;
    } else
      mark (lit), clause.push_back (lit), s++;
  }
  if (satisfied) {
    LOG (c, "satisfied by %d antecedent", satisfied);
    elim_update_removed_clause (eliminator, c, satisfied);
    mark_garbage (c);
    clause.clear ();
    lrat_chain.clear ();
    clear_analyzed_literals ();
    unmark (c);
    return false;
  }

  // Then determine whether the second antecedent is satisfied, add its
  // literal to 'clause' and check whether a clashing literal is found, such
  // that the resolvent would be tautological.
  //
  for (const auto &lit : *d) {
    if (lit == -pivot) {
      t++;
      continue;
    }
    assert (lit != pivot);
    signed char tmp = val (lit);
    if (tmp > 0) {
      satisfied = lit;
      break;
    } else if (tmp < 0) {
      if (!lrat)
        continue;
      Flags &f = flags (lit);
      if (f.seen)
        continue;
      analyzed.push_back (lit);
      f.seen = true;
      int64_t id = unit_id (-lit);
      lrat_chain.push_back (id);
      continue;
    } else if ((tmp = marked (lit)) < 0) {
      tautological = lit;
      break;
    } else if (!tmp)
      clause.push_back (lit), t++;
    else
      assert (tmp > 0), t++;
  }

  clear_analyzed_literals ();
  unmark (c);
  const int64_t size = clause.size ();

  if (lrat) {
    lrat_chain.push_back (d->id);
    lrat_chain.push_back (c->id);
  }

  if (satisfied) {
    LOG (d, "satisfied by %d antecedent", satisfied);
    elim_update_removed_clause (eliminator, d, satisfied);
    mark_garbage (d);
    clause.clear ();
    lrat_chain.clear ();
    return false;
  }

  LOG (c, "first antecedent");
  LOG (d, "second antecedent");

  if (tautological) {
    clause.clear ();
    LOG ("resolvent tautological on %d", tautological);
    lrat_chain.clear ();
    return false;
  }

  if (!size) {
    clause.clear ();
    LOG ("empty resolvent");
    learn_empty_clause (); // already clears lrat_chain.
    return false;
  }

  if (size == 1) {
    int unit = clause[0];
    LOG ("unit resolvent %d", unit);
    clause.clear ();
    assign_unit (unit); // already clears lrat_chain.
    if (propagate_eagerly)
      elim_propagate (eliminator, unit);
    return false;
  }

  LOG (clause, "resolvent");
  assert (!lrat || !lrat_chain.empty ());

  // Double self-subsuming resolution.  The clauses 'c' and 'd' are
  // identical except for the pivot which occurs in different phase.  The
  // resolvent subsumes both antecedents.

  if (s > size && t > size) {
    assert (s == size + 1);
    assert (t == size + 1);
    clause.clear ();
    // LRAT is c + d (+ eventual units)
    elim_on_the_fly_self_subsumption (eliminator, c, pivot);
    LOG (d, "double pivot %d on-the-fly self-subsuming resolution", -pivot);
    stats.elimotfsub++;
    stats.subsumed++;
    elim_update_removed_clause (eliminator, d, -pivot);
    mark_garbage (d);
    return false;
  }

  // Single self-subsuming resolution:  The pivot can be removed from 'c',
  // which is implemented by adding a clause which is the same as 'c' but
  // with 'pivot' removed and then marking 'c' as garbage.

  if (s > size) {
    assert (s == size + 1);
    clause.clear ();
    // LRAT is c + d (+ eventual units)
    elim_on_the_fly_self_subsumption (eliminator, c, pivot);
    return false;
  }

  // Same single self-subsuming resolution situation, but only for 'd'.

  if (t > size) {
    assert (t == size + 1);
    clause.clear ();
    // LRAT is c + d (+ eventual units) -> same.
    elim_on_the_fly_self_subsumption (eliminator, d, -pivot);
    return false;
  }
  if (propagate_eagerly)
    lrat_chain.clear ();
  return true;
}

/*------------------------------------------------------------------------*/

// Check whether the number of non-tautological resolvents on 'pivot' is
// smaller or equal to the number of clauses with 'pivot' or '-pivot'.  This
// is the main criteria of bounded variable elimination.  As a side effect
// it flushes garbage clauses with that variable, sorts its occurrence lists
// (smallest clauses first) and also negates pivot if it has more positive
// than negative occurrences.

bool Internal::elim_resolvents_are_bounded (Eliminator &eliminator,
                                            int pivot) {
  const bool substitute = !eliminator.gates.empty ();
  const bool resolve_gates = eliminator.definition_unit;
  if (substitute)
    LOG ("trying to substitute %d", pivot);

  stats.elimtried++;

  assert (!unsat);
  assert (active (pivot));

  const Occs &ps = occs (pivot);
  const Occs &ns = occs (-pivot);
  const int64_t pos = ps.size ();
  const int64_t neg = ns.size ();
  if (!pos || !neg)
    return lim.elimbound >= 0;
  const int64_t bound = pos + neg + lim.elimbound;

  LOG ("checking number resolvents on %d bounded by "
       "%" PRId64 " = %" PRId64 " + %" PRId64 " + %" PRId64,
       pivot, bound, pos, neg, lim.elimbound);

  // Try all resolutions between a positive occurrence (outer loop) of
  // 'pivot' and a negative occurrence of 'pivot' (inner loop) as long the
  // bound on non-tautological resolvents is not hit and the size of the
  // generated resolvents does not exceed the resolvent clause size limit.

  int64_t resolvents = 0; // Non-tautological resolvents.

  for (const auto &c : ps) {
    assert (!c->redundant);
    if (c->garbage)
      continue;
    for (const auto &d : ns) {
      assert (!d->redundant);
      if (d->garbage)
        continue;
      if (!resolve_gates && substitute && c->gate == d->gate)
        continue;
      stats.elimrestried++;
      if (resolve_clauses (eliminator, c, pivot, d, true)) {
        resolvents++;
        int size = clause.size ();
        clause.clear ();
        LOG ("now at least %" PRId64
             " non-tautological resolvents on pivot %d",
             resolvents, pivot);
        if (size > opts.elimclslim) {
          LOG ("resolvent size %d too big after %" PRId64
               " resolvents on %d",
               size, resolvents, pivot);
          return false;
        }
        if (resolvents > bound) {
          LOG ("too many non-tautological resolvents on %d", pivot);
          return false;
        }
      } else if (unsat)
        return false;
      else if (val (pivot))
        return false;
    }
  }

  LOG ("need %" PRId64 " <= %" PRId64 " non-tautological resolvents",
       resolvents, bound);

  return true;
}

/*------------------------------------------------------------------------*/
// Add all resolvents on 'pivot' and connect them.

inline void Internal::elim_add_resolvents (Eliminator &eliminator,
                                           int pivot) {

  const bool substitute = !eliminator.gates.empty ();
  const bool resolve_gates = eliminator.definition_unit;
  if (substitute) {
    LOG ("substituting pivot %d by resolving with %zd gate clauses", pivot,
         eliminator.gates.size ());
    stats.elimsubst++;
  }
  switch (eliminator.gatetype) {
  case EQUI:
    stats.eliminated_equi++;
    break;
  case AND:
    stats.eliminated_and++;
    break;
  case ITE:
    stats.eliminated_ite++;
    break;
  case XOR:
    stats.eliminated_xor++;
    break;
  case DEF:
    stats.eliminated_def++;
    break;
  case NO:
    break;
  }

  LOG ("adding all resolvents on %d", pivot);

  assert (!val (pivot));
  assert (!flags (pivot).eliminated ());

  const Occs &ps = occs (pivot);
  const Occs &ns = occs (-pivot);
#ifdef LOGGING
  int64_t resolvents = 0;
#endif
  for (auto &c : ps) {
    if (unsat)
      break;
    if (c->garbage)
      continue;
    for (auto &d : ns) {
      if (unsat)
        break;
      if (d->garbage)
        continue;
      if (!resolve_gates && substitute && c->gate == d->gate)
        continue;
      if (!resolve_clauses (eliminator, c, pivot, d, false))
        continue;
      assert (!lrat || !lrat_chain.empty ());
      Clause *r = new_resolved_irredundant_clause ();
      elim_update_added_clause (eliminator, r);
      eliminator.enqueue (r);
      lrat_chain.clear ();
      clause.clear ();
#ifdef LOGGING
      resolvents++;
#endif
    }
  }

  LOG ("added %" PRId64 " resolvents to eliminate %d", resolvents, pivot);
}

/*------------------------------------------------------------------------*/

// Remove clauses with 'pivot' and '-pivot' by marking them as garbage and
// push them on the extension stack.

void Internal::mark_eliminated_clauses_as_garbage (
    Eliminator &eliminator, int pivot, bool &deleted_binary_clause) {
  assert (!unsat);

  LOG ("marking irredundant clauses with %d as garbage", pivot);

  const int64_t substitute = eliminator.gates.size ();
  if (substitute)
    LOG ("pushing %" PRId64 " gate clauses on extension stack", substitute);
#ifndef NDEBUG
  int64_t pushed = 0;
#endif
  Occs &ps = occs (pivot);
  for (const auto &c : ps) {
    if (c->garbage)
      continue;
    assert (!c->redundant);
    if (!substitute || c->gate) {
      if (proof)
        proof->weaken_minus (c);
      if (c->size == 2)
        deleted_binary_clause = true;
      external->push_clause_on_extension_stack (c, pivot);
#ifndef NDEBUG
      pushed++;
#endif
    }
    mark_garbage (c);
    elim_update_removed_clause (eliminator, c, pivot);
  }
  erase_occs (ps);

  LOG ("marking irredundant clauses with %d as garbage", -pivot);

  Occs &ns = occs (-pivot);
  for (const auto &d : ns) {
    if (d->garbage)
      continue;
    assert (!d->redundant);
    if (!substitute || d->gate) {
      if (proof)
        proof->weaken_minus (d);
      if (d->size == 2)
        deleted_binary_clause = true;
      external->push_clause_on_extension_stack (d, -pivot);
#ifndef NDEBUG
      pushed++;
#endif
    }
    mark_garbage (d);
    elim_update_removed_clause (eliminator, d, -pivot);
  }
  erase_occs (ns);

  if (substitute)
    assert (pushed <= substitute);

  // Unfortunately, we can not use the trick by Niklas Soerensson anymore,
  // which avoids saving all clauses on the extension stack.  This would
  // break our new incremental 'restore' logic.
}

/*------------------------------------------------------------------------*/

// Try to eliminate 'pivot' by bounded variable elimination.
void Internal::try_to_eliminate_variable (Eliminator &eliminator, int pivot,
                                          bool &deleted_binary_clause) {

  if (!active (pivot))
    return;
  assert (!frozen (pivot));

  // First flush garbage clauses.
  //
  int64_t pos = flush_occs (pivot);
  int64_t neg = flush_occs (-pivot);

  if (pos > neg) {
    pivot = -pivot;
    swap (pos, neg);
  }
  LOG ("pivot %d occurs positively %" PRId64
       " times and negatively %" PRId64 " times",
       pivot, pos, neg);
  assert (!eliminator.schedule.contains (abs (pivot)));
  assert (pos <= neg);

  if (pos && neg > opts.elimocclim) {
    LOG ("too many occurrences thus not eliminated %d", pivot);
    assert (!eliminator.schedule.contains (abs (pivot)));
    return;
  }

  LOG ("trying to eliminate %d", pivot);
  assert (!flags (pivot).eliminated ());

  // Sort occurrence lists, such that shorter clauses come first.
  Occs &ps = occs (pivot);
  stable_sort (ps.begin (), ps.end (), clause_smaller_size ());
  Occs &ns = occs (-pivot);
  stable_sort (ns.begin (), ns.end (), clause_smaller_size ());

  if (pos)
    find_gate_clauses (eliminator, pivot);

  if (!unsat && !val (pivot)) {
    if (elim_resolvents_are_bounded (eliminator, pivot)) {
      LOG ("number of resolvents on %d are bounded", pivot);
      elim_add_resolvents (eliminator, pivot);
      if (!unsat)
        mark_eliminated_clauses_as_garbage (eliminator, pivot,
                                            deleted_binary_clause);
      if (active (pivot))
        mark_eliminated (pivot);
    } else {
      LOG ("too many resolvents on %d so not eliminated", pivot);
    }
  }

  unmark_gate_clauses (eliminator);
  elim_backward_clauses (eliminator);
}

/*------------------------------------------------------------------------*/

void Internal::
    mark_redundant_clauses_with_eliminated_variables_as_garbage () {
  for (const auto &c : clauses) {
    if (c->garbage || !c->redundant)
      continue;
    bool clean = true;
    for (const auto &lit : *c) {
      Flags &f = flags (lit);
      if (f.eliminated ()) {
        clean = false;
        break;
      }
      if (f.pure ()) {
        clean = false;
        break;
      }
    }
    if (!clean)
      mark_garbage (c);
  }
}

/*------------------------------------------------------------------------*/

// This function performs one round of bounded variable elimination and
// returns the number of eliminated variables. The additional result
// 'completed' is true if this elimination round ran to completion (all
// variables have been tried).  Otherwise it was asynchronously terminated
// or the resolution limit was hit.

int Internal::elim_round (bool &completed, bool &deleted_binary_clause) {

  assert (opts.elim);
  assert (!unsat);

  START_SIMPLIFIER (elim, ELIM);
  stats.elimrounds++;

  int64_t marked_before = last.elim.marked;
  last.elim.marked = stats.mark.elim;
  assert (!level);

  int64_t resolution_limit;

  if (opts.elimlimited) {
    int64_t delta = stats.propagations.search;
    delta *= 1e-3 * opts.elimeffort;
    if (delta < opts.elimmineff)
      delta = opts.elimmineff;
    if (delta > opts.elimmaxeff)
      delta = opts.elimmaxeff;
    delta = max (delta, (int64_t) 2l * active ());

    PHASE ("elim-round", stats.elimrounds,
           "limit of %" PRId64 " resolutions", delta);

    resolution_limit = stats.elimres + delta;
  } else {
    PHASE ("elim-round", stats.elimrounds, "resolutions unlimited");
    resolution_limit = LONG_MAX;
  }

  init_noccs ();

  // First compute the number of occurrences of each literal and at the same
  // time mark satisfied clauses and update 'elim' flags of variables in
  // clauses with root level assigned literals (both false and true).
  //
  for (const auto &c : clauses) {
    if (c->garbage || c->redundant)
      continue;
    bool satisfied = false, falsified = false;
    for (const auto &lit : *c) {
      const signed char tmp = val (lit);
      if (tmp > 0)
        satisfied = true;
      else if (tmp < 0)
        falsified = true;
      else
        assert (active (lit));
    }
    if (satisfied)
      mark_garbage (c); // forces more precise counts
    else {
      for (const auto &lit : *c) {
        if (!active (lit))
          continue;
        if (falsified)
          mark_elim (lit); // simulate unit propagation
        noccs (lit)++;
      }
    }
  }

  init_occs ();

  Eliminator eliminator (this);
  ElimSchedule &schedule = eliminator.schedule;
  assert (schedule.empty ());

  // Now find elimination candidates which occurred in clauses removed since
  // the last time we ran bounded variable elimination, which in turned
  // triggered their 'elim' bit to be set.
  //
  for (auto idx : vars) {
    if (!active (idx))
      continue;
    if (frozen (idx))
      continue;
    if (!flags (idx).elim)
      continue;
    LOG ("scheduling %d for elimination initially", idx);
    schedule.push_back (idx);
  }

  schedule.shrink ();

#ifndef QUIET
  int64_t scheduled = schedule.size ();
#endif

  PHASE ("elim-round", stats.elimrounds,
         "scheduled %" PRId64 " variables %.0f%% for elimination",
         scheduled, percent (scheduled, active ()));

  // Connect irredundant clauses.
  //
  for (const auto &c : clauses)
    if (!c->garbage && !c->redundant)
      for (const auto &lit : *c)
        if (active (lit))
          occs (lit).push_back (c);

#ifndef QUIET
  const int64_t old_resolutions = stats.elimres;
#endif
  const int old_eliminated = stats.all.eliminated;
  const int old_fixed = stats.all.fixed;

  // Limit on garbage literals during variable elimination. If the limit is
  // hit a garbage collection is performed.
  //
  const int64_t garbage_limit = (2 * stats.irrlits / 3) + (1 << 20);

  // Main loops tries to eliminate variables according to the schedule. The
  // schedule is updated dynamically and variables are potentially
  // rescheduled to be tried again if they occur in a removed clause.
  //
#ifndef QUIET
  int64_t tried = 0;
#endif
  while (!unsat && !terminated_asynchronously () &&
         stats.elimres <= resolution_limit && !schedule.empty ()) {
    int idx = schedule.front ();
    schedule.pop_front ();
    flags (idx).elim = false;
    try_to_eliminate_variable (eliminator, idx, deleted_binary_clause);
#ifndef QUIET
    tried++;
#endif
    if (stats.garbage.literals <= garbage_limit)
      continue;
    mark_redundant_clauses_with_eliminated_variables_as_garbage ();
    garbage_collection ();
  }

  // If the schedule is empty all variables have been tried (even
  // rescheduled ones).  Otherwise asynchronous termination happened or we
  // ran into the resolution limit (or derived unsatisfiability).
  //
  completed = !schedule.size ();

  if (!completed)
    last.elim.marked = marked_before;

  PHASE ("elim-round", stats.elimrounds,
         "tried to eliminate %" PRId64 " variables %.0f%% (%zd remain)",
         tried, percent (tried, scheduled), schedule.size ());

  schedule.erase ();

  // Collect potential literal clause instantiation pairs, which needs full
  // occurrence lists and thus we have it here before resetting them.
  //
  Instantiator instantiator;
  if (!unsat && !terminated_asynchronously () && opts.instantiate)
    collect_instantiation_candidates (instantiator);

  reset_occs ();
  reset_noccs ();

  // Mark all redundant clauses with eliminated variables as garbage.
  //
  if (!unsat)
    mark_redundant_clauses_with_eliminated_variables_as_garbage ();

  int eliminated = stats.all.eliminated - old_eliminated;
#ifndef QUIET
  int64_t resolutions = stats.elimres - old_resolutions;
  PHASE ("elim-round", stats.elimrounds,
         "eliminated %d variables %.0f%% in %" PRId64 " resolutions",
         eliminated, percent (eliminated, scheduled), resolutions);
#endif

  last.elim.subsumephases = stats.subsumephases;
  const int units = stats.all.fixed - old_fixed;
  report ('e', !opts.reportall && !(eliminated + units));
  STOP_SIMPLIFIER (elim, ELIM);

  if (!unsat && !terminated_asynchronously () &&
      instantiator) // Do we have candidate pairs?
    instantiate (instantiator);

  return eliminated; // non-zero if successful
}

/*------------------------------------------------------------------------*/

// Increase elimination bound (additional clauses allowed during variable
// elimination), which is triggered if elimination with last bound completed
// (including no new subsumptions).  This was pioneered by GlueMiniSAT in
// the SAT Race 2015 and then picked up Chanseok Oh in his COMinisatPS
// solver, which in turn is used in the Maple series of SAT solvers.
// The bound is no increased if the maximum bound is reached.

void Internal::increase_elimination_bound () {

  if (lim.elimbound >= opts.elimboundmax)
    return;

  if (lim.elimbound < 0)
    lim.elimbound = 0;
  else if (!lim.elimbound)
    lim.elimbound = 1;
  else
    lim.elimbound *= 2;

  if (lim.elimbound > opts.elimboundmax)
    lim.elimbound = opts.elimboundmax;

  PHASE ("elim-phase", stats.elimphases,
         "new elimination bound %" PRId64 "", lim.elimbound);

  // Now reschedule all active variables for elimination again.
  //
#ifdef LOGGING
  int count = 0;
#endif
  for (auto idx : vars) {
    if (!active (idx))
      continue;
    if (flags (idx).elim)
      continue;
    mark_elim (idx);
#ifdef LOGGING
    count++;
#endif
  }
  LOG ("marked %d variables as elimination candidates", count);

  report ('^');
}

void Internal::init_citten () {
  if (!opts.elimdef)
    return;
  assert (!citten);
  citten = kitten_init ();
}

void Internal::reset_citten () {
  if (citten) {
    kitten_release (citten);
    citten = 0;
  }
}

/*------------------------------------------------------------------------*/

void Internal::elim (bool update_limits) {

  if (unsat)
    return;
  if (level)
    backtrack ();
  if (!propagate ()) {
    learn_empty_clause ();
    return;
  }

  stats.elimphases++;
  PHASE ("elim-phase", stats.elimphases,
         "starting at most %d elimination rounds", opts.elimrounds);

  if (external_prop) {
    assert (!level);
    private_steps = true;
  }

#ifndef QUIET
  int old_active_variables = active ();
  int old_eliminated = stats.all.eliminated;
#endif

  // Make sure there was a complete subsumption phase since last
  // elimination
  //
  if (last.elim.subsumephases == stats.subsumephases)
    subsume ();

  reset_watches (); // saves lots of memory

  init_citten ();

  // Alternate one round of bounded variable elimination ('elim_round') and
  // subsumption ('subsume_round'), blocked ('block') and covered clause
  // elimination ('cover') until nothing changes, or the round limit is hit.
  // The loop also aborts early if no variable could be eliminated, the
  // empty clause is resolved, it is asynchronously terminated or a
  // resolution limit is hit.

  // This variable determines whether the whole loop of this bounded
  // variable elimination phase ('elim') ran until completion.  This
  // potentially triggers an incremental increase of the elimination bound.
  //
  bool phase_complete = false, deleted_binary_clause = false;

  int round = 1;
#ifndef QUIET
  int eliminated = 0;
#endif

  bool round_complete = false;
  while (!unsat && !phase_complete && !terminated_asynchronously ()) {
#ifndef QUIET
    int eliminated =
#endif
        elim_round (round_complete, deleted_binary_clause);

    if (!round_complete) {
      PHASE ("elim-phase", stats.elimphases, "last round %d incomplete %s",
             round, eliminated ? "but successful" : "and unsuccessful");
      assert (!phase_complete);
      break;
    }

    if (round++ >= opts.elimrounds) {
      PHASE ("elim-phase", stats.elimphases, "round limit %d hit (%s)",
             round - 1,
             eliminated ? "though last round successful"
                        : "last round unsuccessful anyhow");
      assert (!phase_complete);
      break;
    }

    // Prioritize 'subsumption' over blocked and covered clause elimination.

    if (subsume_round ())
      continue;
    if (block ())
      continue;
    if (cover ())
      continue;

    // Was not able to generate new variable elimination candidates after
    // variable elimination round, neither through subsumption, nor blocked,
    // nor covered clause elimination.
    //
    PHASE ("elim-phase", stats.elimphases,
           "no new variable elimination candidates");

    assert (round_complete);
    phase_complete = true;
  }

  if (phase_complete) {
    stats.elimcompleted++;
    PHASE ("elim-phase", stats.elimphases,
           "fully completed elimination %" PRId64
           " at elimination bound %" PRId64 "",
           stats.elimcompleted, lim.elimbound);
  } else {
    PHASE ("elim-phase", stats.elimphases,
           "incomplete elimination %" PRId64
           " at elimination bound %" PRId64 "",
           stats.elimcompleted + 1, lim.elimbound);
  }

  reset_citten ();
  if (deleted_binary_clause)
    delete_garbage_clauses ();
  init_watches ();
  connect_watches ();

  if (unsat)
    LOG ("elimination derived empty clause");
  else if (propagated < trail.size ()) {
    LOG ("elimination produced %zd units",
         (size_t) (trail.size () - propagated));
    if (!propagate ()) {
      LOG ("propagating units after elimination results in empty clause");
      learn_empty_clause ();
    }
  }

  // If we ran variable elimination until completion we increase the
  // variable elimination bound and reschedule elimination of all variables.
  //
  if (phase_complete)
    increase_elimination_bound ();

#ifndef QUIET
  eliminated = stats.all.eliminated - old_eliminated;
  PHASE ("elim-phase", stats.elimphases, "eliminated %d variables %.2f%%",
         eliminated, percent (eliminated, old_active_variables));
#endif

  if (external_prop) {
    assert (!level);
    private_steps = false;
  }

  if (!update_limits)
    return;

  int64_t delta = scale (opts.elimint * (stats.elimphases + 1));
  lim.elim = stats.conflicts + delta;

  PHASE ("elim-phase", stats.elimphases,
         "new limit at %" PRId64 " conflicts after %" PRId64 " conflicts",
         lim.elim, delta);

  last.elim.fixed = stats.all.fixed;
}

} // namespace CaDiCaL