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
#include "internal.hpp"

namespace CaDiCaL {

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

// This procedure can be used to produce all possible hyper ternary
// resolvents from ternary clauses.  In contrast to hyper binary resolution
// we would only try to produce ternary clauses from ternary clauses, i.e.,
// do not consider quaternary clauses as antecedents.  Of course if a binary
// clause is generated we keep it too.  In any case we have to make sure
// though that we do not add clauses which are already in the formula or are
// subsumed by a binary clause in the formula.  This procedure simulates
// structural hashing for multiplexer (if-then-else) and binary XOR gates in
// combination with equivalent literal substitution ('decompose') if run
// until to completion (which in the current implementation is too costly
// though and would need to be interleaved more eagerly with equivalent
// literal substitution).  For more information see our CPAIOR'13 paper.

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

// Check whether a binary clause consisting of the permutation of the given
// literals already exists.

bool Internal::ternary_find_binary_clause (int a, int b) {
  assert (occurring ());
  assert (active (a));
  assert (active (b));
  size_t s = occs (a).size ();
  size_t t = occs (b).size ();
  int lit = s < t ? a : b;
  if (opts.ternaryocclim < (int) occs (lit).size ())
    return true;
  for (const auto &c : occs (lit)) {
    if (c->size != 2)
      continue;
    const int *lits = c->literals;
    if (lits[0] == a && lits[1] == b)
      return true;
    if (lits[0] == b && lits[1] == a)
      return true;
  }
  return false;
}

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

// Check whether a ternary clause consisting of the permutation of the given
// literals already exists or is subsumed by an existing binary clause.

bool Internal::ternary_find_ternary_clause (int a, int b, int c) {
  assert (occurring ());
  assert (active (a));
  assert (active (b));
  assert (active (c));
  size_t r = occs (a).size ();
  size_t s = occs (b).size ();
  size_t t = occs (c).size ();
  int lit;
  if (r < s)
    lit = (t < r) ? c : a;
  else
    lit = (t < s) ? c : b;
  if (opts.ternaryocclim < (int) occs (lit).size ())
    return true;
  for (const auto &d : occs (lit)) {
    const int *lits = d->literals;
    if (d->size == 2) {
      if (lits[0] == a && lits[1] == b)
        return true;
      if (lits[0] == b && lits[1] == a)
        return true;
      if (lits[0] == a && lits[1] == c)
        return true;
      if (lits[0] == c && lits[1] == a)
        return true;
      if (lits[0] == b && lits[1] == c)
        return true;
      if (lits[0] == c && lits[1] == b)
        return true;
    } else {
      assert (d->size == 3);
      if (lits[0] == a && lits[1] == b && lits[2] == c)
        return true;
      if (lits[0] == a && lits[1] == c && lits[2] == b)
        return true;
      if (lits[0] == b && lits[1] == a && lits[2] == c)
        return true;
      if (lits[0] == b && lits[1] == c && lits[2] == a)
        return true;
      if (lits[0] == c && lits[1] == a && lits[2] == b)
        return true;
      if (lits[0] == c && lits[1] == b && lits[2] == a)
        return true;
    }
  }
  return false;
}

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

// Try to resolve the two ternary clauses on the given pivot (assumed to
// occur positively in the first clause, negatively in the second).  If the
// resolvent has four literals, is tautological, already exists or in the
// case of a ternary resolvent is subsumed by an existing binary clause then
// 'false' is returned.  The global 'clause' contains the resolvent and
// needs to be cleared in any case.

bool Internal::hyper_ternary_resolve (Clause *c, int pivot, Clause *d) {
  LOG ("hyper binary resolving on pivot %d", pivot);
  LOG (c, "1st antecedent");
  LOG (d, "2nd antecedent");
  stats.ternres++;
  assert (c->size == 3);
  assert (d->size == 3);
  assert (clause.empty ());
  for (const auto &lit : *c)
    if (lit != pivot)
      clause.push_back (lit);
  for (const auto &lit : *d) {
    if (lit == -pivot)
      continue;
    if (lit == clause[0])
      continue;
    if (lit == -clause[0])
      return false;
    if (lit == clause[1])
      continue;
    if (lit == -clause[1])
      return false;
    clause.push_back (lit);
  }
  size_t size = clause.size ();
  if (size > 3)
    return false;
  if (size == 2 && ternary_find_binary_clause (clause[0], clause[1]))
    return false;
  if (size == 3 &&
      ternary_find_ternary_clause (clause[0], clause[1], clause[2]))
    return false;
  return true;
}

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

// Produce all ternary resolvents on literal 'pivot' and increment the
// 'steps' counter by the number of clauses containing 'pivot' which are
// used during this process.  The reason for choosing this metric to measure
// the effort spent in 'ternary' is that it should be similar to one
// propagation step during search.

void Internal::ternary_lit (int pivot, int64_t &steps, int64_t &htrs) {
  LOG ("starting hyper ternary resolutions on pivot %d", pivot);
  steps -= 1 + cache_lines (occs (pivot).size (), sizeof (Clause *));
  for (const auto &c : occs (pivot)) {
    if (steps < 0)
      break;
    if (htrs < 0)
      break;
    if (c->garbage)
      continue;
    if (c->size != 3) {
      assert (c->size == 2);
      continue;
    }
    if (--steps < 0)
      break;
    bool assigned = false;
    for (const auto &lit : *c)
      if (val (lit)) {
        assigned = true;
        break;
      }
    if (assigned)
      continue;
    steps -= 1 + cache_lines (occs (-pivot).size (), sizeof (Clause *));
    for (const auto &d : occs (-pivot)) {
      if (htrs < 0)
        break;
      if (--steps < 0)
        break;
      if (d->garbage)
        continue;
      if (d->size != 3) {
        assert (d->size == 2);
        continue;
      }
      for (const auto &lit : *d)
        if (val (lit)) {
          assigned = true;
          break;
        }
      if (assigned)
        continue;
      assert (clause.empty ());
      htrs--;
      if (hyper_ternary_resolve (c, pivot, d)) {
        size_t size = clause.size ();
        bool red = (size == 3 || (c->redundant && d->redundant));
        if (lrat) {
          assert (lrat_chain.empty ());
          lrat_chain.push_back (c->id);
          lrat_chain.push_back (d->id);
        }
        Clause *r = new_hyper_ternary_resolved_clause (red);
        if (red)
          r->hyper = true;
        lrat_chain.clear ();
        clause.clear ();
        LOG (r, "hyper ternary resolved");
        stats.htrs++;
        for (const auto &lit : *r)
          occs (lit).push_back (r);
        if (size == 2) {
          LOG ("hyper ternary resolvent subsumes both antecedents");
          mark_garbage (c);
          mark_garbage (d);
          stats.htrs2++;
          break;
        } else {
          assert (r->size == 3);
          stats.htrs3++;
        }
      } else {
        LOG (clause, "ignoring size %zd resolvent", clause.size ());
        clause.clear ();
      }
    }
  }
}

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

// Same as 'ternary_lit' but pick the phase of the variable based on the
// number of positive and negative occurrence.

void Internal::ternary_idx (int idx, int64_t &steps, int64_t &htrs) {
  assert (0 < idx);
  assert (idx <= max_var);
  steps -= 3;
  if (!active (idx))
    return;
  if (!flags (idx).ternary)
    return;
  int pos = occs (idx).size ();
  int neg = occs (-idx).size ();
  if (pos <= opts.ternaryocclim && neg <= opts.ternaryocclim) {
    LOG ("index %d has %zd positive and %zd negative occurrences", idx,
         occs (idx).size (), occs (-idx).size ());
    int pivot = (neg < pos ? -idx : idx);
    ternary_lit (pivot, steps, htrs);
  }
  flags (idx).ternary = false;
}

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

// One round of ternary resolution over all variables.  As in 'block' and
// 'elim' we maintain a persistent global flag 'ternary' for each variable,
// which records, whether a ternary clause containing it was added.  Then we
// can focus on those variables for which we have not tried ternary
// resolution before and nothing changed for them since then.  This works
// across multiple calls to 'ternary' as well as 'ternary_round' since this
// 'ternary' variable flag is updated during adding (ternary) resolvents.
// This function goes over each variable just once.

bool Internal::ternary_round (int64_t &steps_limit, int64_t &htrs_limit) {

  assert (!unsat);

#ifndef QUIET
  int64_t bincon = 0;
  int64_t terncon = 0;
#endif

  init_occs ();

  steps_limit -= 1 + cache_lines (clauses.size (), sizeof (Clause *));
  for (const auto &c : clauses) {
    steps_limit--;
    if (c->garbage)
      continue;
    if (c->size > 3)
      continue;
    bool assigned = false, marked = false;
    for (const auto &lit : *c) {
      if (val (lit)) {
        assigned = true;
        break;
      }
      if (flags (lit).ternary)
        marked = true;
    }
    if (assigned)
      continue;
    if (c->size == 2) {
#ifndef QUIET
      bincon++;
#endif
    } else {
      assert (c->size == 3);
      if (!marked)
        continue;
#ifndef QUIET
      terncon++;
#endif
    }

    for (const auto &lit : *c)
      occs (lit).push_back (c);
  }

  PHASE ("ternary", stats.ternary,
         "connected %" PRId64 " ternary %.0f%% "
         "and %" PRId64 " binary clauses %.0f%%",
         terncon, percent (terncon, clauses.size ()), bincon,
         percent (bincon, clauses.size ()));

  // Try ternary resolution on all variables once.
  //
  for (auto idx : vars) {
    if (terminated_asynchronously ())
      break;
    if (steps_limit < 0)
      break;
    if (htrs_limit < 0)
      break;
    ternary_idx (idx, steps_limit, htrs_limit);
  }

  // Gather some statistics for the verbose messages below and also
  // determine whether new variables have been marked and it would make
  // sense to run another round of ternary resolution over those variables.
  //
  int remain = 0;
  for (auto idx : vars) {
    if (!active (idx))
      continue;
    if (!flags (idx).ternary)
      continue;
    remain++;
  }
  if (remain)
    PHASE ("ternary", stats.ternary, "%d variables remain %.0f%%", remain,
           percent (remain, max_var));
  else
    PHASE ("ternary", stats.ternary, "completed hyper ternary resolution");

  reset_occs ();
  assert (!unsat);

  return remain; // Are there variables that should be tried again?
}

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

bool Internal::ternary () {

  if (!opts.ternary)
    return false;
  if (unsat)
    return false;
  if (terminated_asynchronously ())
    return false;

  // No new ternary clauses added since last time?
  //
  if (last.ternary.marked == stats.mark.ternary)
    return false;

  SET_EFFORT_LIMIT (limit, ternary, true);

  START_SIMPLIFIER (ternary, TERNARY);
  stats.ternary++;

  assert (!level);

  assert (!unsat);
  if (watching ())
    reset_watches ();

  // The number of clauses derived through ternary resolution can grow
  // substantially, particularly for random formulas.  Thus we limit the
  // number of added clauses too (actually the number of 'htrs').
  //
  int64_t htrs_limit = stats.current.redundant + stats.current.irredundant;
  htrs_limit *= opts.ternarymaxadd;
  htrs_limit /= 100;

  // approximation of ternary ticks.
  // TODO: count with ternary.ticks directly.
  int64_t steps_limit = stats.ticks.ternary - limit;
  stats.ticks.ternary = limit;

  // With 'stats.ternary' we actually count the number of calls to
  // 'ternary_round' and not the number of calls to 'ternary'. But before
  // the first round we want to show the limit on the number of steps and
  // thus we increase counter for the first round here and skip increasing
  // it in the loop below.
  //
  PHASE ("ternary", stats.ternary,
         "will run a maximum of %d rounds "
         "limited to %" PRId64 " steps and %" PRId64 " clauses",
         opts.ternaryrounds, steps_limit, htrs_limit);

  bool resolved_binary_clause = false;
  bool completed = false;

  for (int round = 0;
       !terminated_asynchronously () && round < opts.ternaryrounds;
       round++) {
    if (htrs_limit < 0)
      break;
    if (steps_limit < 0)
      break;
    if (round)
      stats.ternary++;
    int old_htrs2 = stats.htrs2;
    int old_htrs3 = stats.htrs3;
    completed = ternary_round (steps_limit, htrs_limit);
    int delta_htrs2 = stats.htrs2 - old_htrs2;
    int delta_htrs3 = stats.htrs3 - old_htrs3;
    PHASE ("ternary", stats.ternary,
           "derived %d ternary and %d binary resolvents", delta_htrs3,
           delta_htrs2);
    report ('3', !opts.reportall && !(delta_htrs2 + delta_htrs2));
    if (delta_htrs2)
      resolved_binary_clause = true;
    if (!delta_htrs3)
      break;
  }

  assert (!occurring ());
  assert (!unsat);
  init_watches ();
  connect_watches ();
  if (!propagate ()) {
    LOG ("propagation after connecting watches results in inconsistency");
    learn_empty_clause ();
  }

  if (completed)
    last.ternary.marked = stats.mark.ternary;

  STOP_SIMPLIFIER (ternary, TERNARY);

  return resolved_binary_clause;
}

} // namespace CaDiCaL