cadical 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (171) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE.txt +26 -0
  3. data/README.md +101 -0
  4. data/ext/cadical/cadical/LICENSE +28 -0
  5. data/ext/cadical/cadical/README.md +65 -0
  6. data/ext/cadical/cadical/VERSION +1 -0
  7. data/ext/cadical/cadical/src/analyze.cpp +1360 -0
  8. data/ext/cadical/cadical/src/arena.cpp +30 -0
  9. data/ext/cadical/cadical/src/arena.hpp +105 -0
  10. data/ext/cadical/cadical/src/assume.cpp +616 -0
  11. data/ext/cadical/cadical/src/averages.cpp +34 -0
  12. data/ext/cadical/cadical/src/averages.hpp +37 -0
  13. data/ext/cadical/cadical/src/backbone.cpp +631 -0
  14. data/ext/cadical/cadical/src/backtrack.cpp +177 -0
  15. data/ext/cadical/cadical/src/backward.cpp +231 -0
  16. data/ext/cadical/cadical/src/bins.cpp +22 -0
  17. data/ext/cadical/cadical/src/bins.hpp +22 -0
  18. data/ext/cadical/cadical/src/block.cpp +824 -0
  19. data/ext/cadical/cadical/src/block.hpp +37 -0
  20. data/ext/cadical/cadical/src/cadical.cpp +1026 -0
  21. data/ext/cadical/cadical/src/cadical.hpp +1419 -0
  22. data/ext/cadical/cadical/src/ccadical.cpp +208 -0
  23. data/ext/cadical/cadical/src/ccadical.h +74 -0
  24. data/ext/cadical/cadical/src/checker.cpp +649 -0
  25. data/ext/cadical/cadical/src/checker.hpp +173 -0
  26. data/ext/cadical/cadical/src/clause.cpp +689 -0
  27. data/ext/cadical/cadical/src/clause.hpp +190 -0
  28. data/ext/cadical/cadical/src/collect.cpp +545 -0
  29. data/ext/cadical/cadical/src/compact.cpp +550 -0
  30. data/ext/cadical/cadical/src/condition.cpp +940 -0
  31. data/ext/cadical/cadical/src/config.cpp +101 -0
  32. data/ext/cadical/cadical/src/config.hpp +20 -0
  33. data/ext/cadical/cadical/src/congruence.cpp +7880 -0
  34. data/ext/cadical/cadical/src/congruence.hpp +757 -0
  35. data/ext/cadical/cadical/src/constrain.cpp +64 -0
  36. data/ext/cadical/cadical/src/contract.cpp +27 -0
  37. data/ext/cadical/cadical/src/contract.hpp +139 -0
  38. data/ext/cadical/cadical/src/cover.cpp +704 -0
  39. data/ext/cadical/cadical/src/cover.hpp +34 -0
  40. data/ext/cadical/cadical/src/decide.cpp +347 -0
  41. data/ext/cadical/cadical/src/decompose.cpp +738 -0
  42. data/ext/cadical/cadical/src/decompose.hpp +23 -0
  43. data/ext/cadical/cadical/src/deduplicate.cpp +275 -0
  44. data/ext/cadical/cadical/src/definition.cpp +283 -0
  45. data/ext/cadical/cadical/src/delay.hpp +38 -0
  46. data/ext/cadical/cadical/src/drattracer.cpp +153 -0
  47. data/ext/cadical/cadical/src/drattracer.hpp +55 -0
  48. data/ext/cadical/cadical/src/elim.cpp +1171 -0
  49. data/ext/cadical/cadical/src/elim.hpp +53 -0
  50. data/ext/cadical/cadical/src/elimfast.cpp +570 -0
  51. data/ext/cadical/cadical/src/ema.cpp +95 -0
  52. data/ext/cadical/cadical/src/ema.hpp +68 -0
  53. data/ext/cadical/cadical/src/extend.cpp +289 -0
  54. data/ext/cadical/cadical/src/external.cpp +1034 -0
  55. data/ext/cadical/cadical/src/external.hpp +487 -0
  56. data/ext/cadical/cadical/src/external_propagate.cpp +1316 -0
  57. data/ext/cadical/cadical/src/factor.cpp +1011 -0
  58. data/ext/cadical/cadical/src/factor.hpp +54 -0
  59. data/ext/cadical/cadical/src/file.cpp +506 -0
  60. data/ext/cadical/cadical/src/file.hpp +210 -0
  61. data/ext/cadical/cadical/src/flags.cpp +135 -0
  62. data/ext/cadical/cadical/src/flags.hpp +93 -0
  63. data/ext/cadical/cadical/src/flip.cpp +269 -0
  64. data/ext/cadical/cadical/src/format.cpp +89 -0
  65. data/ext/cadical/cadical/src/format.hpp +36 -0
  66. data/ext/cadical/cadical/src/frattracer.cpp +277 -0
  67. data/ext/cadical/cadical/src/frattracer.hpp +67 -0
  68. data/ext/cadical/cadical/src/gates.cpp +766 -0
  69. data/ext/cadical/cadical/src/heap.hpp +212 -0
  70. data/ext/cadical/cadical/src/idruptracer.cpp +566 -0
  71. data/ext/cadical/cadical/src/idruptracer.hpp +112 -0
  72. data/ext/cadical/cadical/src/instantiate.cpp +365 -0
  73. data/ext/cadical/cadical/src/instantiate.hpp +45 -0
  74. data/ext/cadical/cadical/src/internal.cpp +1265 -0
  75. data/ext/cadical/cadical/src/internal.hpp +1941 -0
  76. data/ext/cadical/cadical/src/inttypes.hpp +34 -0
  77. data/ext/cadical/cadical/src/ipasir.cpp +47 -0
  78. data/ext/cadical/cadical/src/ipasir.h +37 -0
  79. data/ext/cadical/cadical/src/kitten.c +2599 -0
  80. data/ext/cadical/cadical/src/kitten.h +97 -0
  81. data/ext/cadical/cadical/src/level.hpp +33 -0
  82. data/ext/cadical/cadical/src/lidruptracer.cpp +657 -0
  83. data/ext/cadical/cadical/src/lidruptracer.hpp +122 -0
  84. data/ext/cadical/cadical/src/limit.cpp +146 -0
  85. data/ext/cadical/cadical/src/limit.hpp +168 -0
  86. data/ext/cadical/cadical/src/logging.cpp +214 -0
  87. data/ext/cadical/cadical/src/logging.hpp +98 -0
  88. data/ext/cadical/cadical/src/lookahead.cpp +520 -0
  89. data/ext/cadical/cadical/src/lratchecker.cpp +833 -0
  90. data/ext/cadical/cadical/src/lratchecker.hpp +168 -0
  91. data/ext/cadical/cadical/src/lrattracer.cpp +200 -0
  92. data/ext/cadical/cadical/src/lrattracer.hpp +61 -0
  93. data/ext/cadical/cadical/src/lucky.cpp +506 -0
  94. data/ext/cadical/cadical/src/message.cpp +218 -0
  95. data/ext/cadical/cadical/src/message.hpp +65 -0
  96. data/ext/cadical/cadical/src/minimize.cpp +224 -0
  97. data/ext/cadical/cadical/src/mobical.cpp +5588 -0
  98. data/ext/cadical/cadical/src/occs.cpp +52 -0
  99. data/ext/cadical/cadical/src/occs.hpp +36 -0
  100. data/ext/cadical/cadical/src/options.cpp +359 -0
  101. data/ext/cadical/cadical/src/options.hpp +442 -0
  102. data/ext/cadical/cadical/src/parse.cpp +471 -0
  103. data/ext/cadical/cadical/src/parse.hpp +75 -0
  104. data/ext/cadical/cadical/src/phases.cpp +56 -0
  105. data/ext/cadical/cadical/src/phases.hpp +19 -0
  106. data/ext/cadical/cadical/src/probe.cpp +990 -0
  107. data/ext/cadical/cadical/src/profile.cpp +107 -0
  108. data/ext/cadical/cadical/src/profile.hpp +283 -0
  109. data/ext/cadical/cadical/src/proof.cpp +691 -0
  110. data/ext/cadical/cadical/src/proof.hpp +125 -0
  111. data/ext/cadical/cadical/src/propagate.cpp +584 -0
  112. data/ext/cadical/cadical/src/queue.cpp +90 -0
  113. data/ext/cadical/cadical/src/queue.hpp +74 -0
  114. data/ext/cadical/cadical/src/radix.hpp +180 -0
  115. data/ext/cadical/cadical/src/random.cpp +207 -0
  116. data/ext/cadical/cadical/src/random.h +44 -0
  117. data/ext/cadical/cadical/src/random.hpp +98 -0
  118. data/ext/cadical/cadical/src/range.hpp +125 -0
  119. data/ext/cadical/cadical/src/reap.cpp +127 -0
  120. data/ext/cadical/cadical/src/reap.hpp +28 -0
  121. data/ext/cadical/cadical/src/reduce.cpp +278 -0
  122. data/ext/cadical/cadical/src/reluctant.hpp +82 -0
  123. data/ext/cadical/cadical/src/rephase.cpp +402 -0
  124. data/ext/cadical/cadical/src/report.cpp +313 -0
  125. data/ext/cadical/cadical/src/resources.cpp +160 -0
  126. data/ext/cadical/cadical/src/resources.hpp +16 -0
  127. data/ext/cadical/cadical/src/restart.cpp +178 -0
  128. data/ext/cadical/cadical/src/restore.cpp +267 -0
  129. data/ext/cadical/cadical/src/score.cpp +51 -0
  130. data/ext/cadical/cadical/src/score.hpp +16 -0
  131. data/ext/cadical/cadical/src/shrink.cpp +507 -0
  132. data/ext/cadical/cadical/src/signal.cpp +136 -0
  133. data/ext/cadical/cadical/src/signal.hpp +33 -0
  134. data/ext/cadical/cadical/src/solution.cpp +50 -0
  135. data/ext/cadical/cadical/src/solver.cpp +1871 -0
  136. data/ext/cadical/cadical/src/stable.cpp +31 -0
  137. data/ext/cadical/cadical/src/stack.h +110 -0
  138. data/ext/cadical/cadical/src/stats.cpp +920 -0
  139. data/ext/cadical/cadical/src/stats.hpp +418 -0
  140. data/ext/cadical/cadical/src/subsume.cpp +645 -0
  141. data/ext/cadical/cadical/src/sweep.cpp +1960 -0
  142. data/ext/cadical/cadical/src/sweep.hpp +61 -0
  143. data/ext/cadical/cadical/src/terminal.cpp +38 -0
  144. data/ext/cadical/cadical/src/terminal.hpp +96 -0
  145. data/ext/cadical/cadical/src/ternary.cpp +450 -0
  146. data/ext/cadical/cadical/src/testing.hpp +24 -0
  147. data/ext/cadical/cadical/src/tier.cpp +189 -0
  148. data/ext/cadical/cadical/src/tracer.hpp +186 -0
  149. data/ext/cadical/cadical/src/transred.cpp +253 -0
  150. data/ext/cadical/cadical/src/unstable.cpp +29 -0
  151. data/ext/cadical/cadical/src/util.cpp +129 -0
  152. data/ext/cadical/cadical/src/util.hpp +181 -0
  153. data/ext/cadical/cadical/src/var.cpp +39 -0
  154. data/ext/cadical/cadical/src/var.hpp +22 -0
  155. data/ext/cadical/cadical/src/veripbtracer.cpp +427 -0
  156. data/ext/cadical/cadical/src/veripbtracer.hpp +106 -0
  157. data/ext/cadical/cadical/src/version.cpp +107 -0
  158. data/ext/cadical/cadical/src/version.hpp +13 -0
  159. data/ext/cadical/cadical/src/vivify.cpp +1893 -0
  160. data/ext/cadical/cadical/src/vivify.hpp +50 -0
  161. data/ext/cadical/cadical/src/walk.cpp +1088 -0
  162. data/ext/cadical/cadical/src/walk.hpp +91 -0
  163. data/ext/cadical/cadical/src/walk_full_occs.cpp +966 -0
  164. data/ext/cadical/cadical/src/warmup.cpp +403 -0
  165. data/ext/cadical/cadical/src/watch.cpp +126 -0
  166. data/ext/cadical/cadical/src/watch.hpp +77 -0
  167. data/ext/cadical/cadical_ext.c +778 -0
  168. data/ext/cadical/extconf.rb +26 -0
  169. data/lib/cadical/version.rb +3 -0
  170. data/lib/cadical.rb +12 -0
  171. metadata +218 -0
@@ -0,0 +1,778 @@
1
+ /* Ruby C extension wrapping the CaDiCaL SAT solver's C API.
2
+ *
3
+ * Decide whether a set of boolean clauses can all be true at once, and if so produce a
4
+ * satisfying assignment. */
5
+
6
+ #include <ruby.h>
7
+ #include <ruby/thread.h>
8
+ #include <stdint.h>
9
+ #include <stdio.h>
10
+ #include <string.h>
11
+
12
+ #include "ccadical.h"
13
+
14
+ /*
15
+ * Document-class: CaDiCaL
16
+ *
17
+ * A Ruby binding for the {CaDiCaL}[https://github.com/arminbiere/cadical] SAT solver.
18
+ *
19
+ * Decide whether a set of boolean clauses can all be true at once, and if so produce a
20
+ * satisfying assignment.
21
+ *
22
+ * This is a thin, general-purpose binding over CaDiCaL's C API (ccadical_*).
23
+ *
24
+ * Typical use:
25
+ *
26
+ * s = CaDiCaL.new
27
+ * x = s.new_var
28
+ * y = s.new_var
29
+ * s.add(x, y) # (x \/ y)
30
+ * s.add(-x, y) # (!x \/ y)
31
+ * s.solve # => :sat
32
+ * s.true?(y) # => true
33
+ *
34
+ * - Make variables with #new_var (each is just an Integer).
35
+ * - A literal is a variable or its negation: +v+ means "variable +v+ is true", +-v+ means false.
36
+ * - A clause is a list of literals; add it as a constraint with #add.
37
+ * - #solve reports whether the clauses can all be satisfied: +:sat+ if so, +:unsat+ if not.
38
+ * - After +:sat+, read a variable's assigned truth value with #true?.
39
+ */
40
+
41
+ static ID id_sat, id_unsat, id_unknown, id_call;
42
+
43
+ /* ccadical_solve returns the IPASIR status codes: 10 = SATISFIABLE, 20 = UNSATISFIABLE,
44
+ * 0 = UNKNOWN. The C API does not give them names, so the constants live here. */
45
+ static VALUE result_sym(int r) {
46
+ return ID2SYM(r == 10 ? id_sat : r == 20 ? id_unsat : id_unknown);
47
+ }
48
+
49
+ /* --- wrapper struct --- */
50
+
51
+ /* The wrapper struct for a CCaDiCaL handle. */
52
+ typedef struct {
53
+ CCaDiCaL *solver;
54
+ VALUE on_terminate; /* proc or Qnil */
55
+ VALUE on_learn; /* proc or Qnil */
56
+ VALUE pending_exc; /* exception from a callback, to re-raise after solve; or Qnil */
57
+ FILE *proof_file; /* open proof-trace file we own, or NULL */
58
+ int state; /* current solver state (mirrors cadical.hpp's State enum). get_cad
59
+ * checks each method's require() contract against it; the mutators
60
+ * update it. Turns CaDiCaL's fatal "wrong state" aborts into exceptions. */
61
+ } cadical_t;
62
+
63
+ static void cad_mark(void *p) {
64
+ cadical_t *c = p;
65
+ rb_gc_mark(c->on_terminate);
66
+ rb_gc_mark(c->on_learn);
67
+ rb_gc_mark(c->pending_exc);
68
+ }
69
+
70
+ static void cad_free(void *p) {
71
+ cadical_t *c = p;
72
+ if (c->proof_file) fclose(c->proof_file);
73
+ if (c->solver) ccadical_release(c->solver);
74
+ xfree(c);
75
+ }
76
+
77
+ static size_t cad_memsize(const void *p) {
78
+ (void)p;
79
+ return sizeof(cadical_t);
80
+ }
81
+
82
+ static const rb_data_type_t cadical_type = {
83
+ "CaDiCaL",
84
+ {cad_mark, cad_free, cad_memsize},
85
+ NULL,
86
+ NULL,
87
+ RUBY_TYPED_FREE_IMMEDIATELY,
88
+ };
89
+
90
+ /* Solver states we can observe from Ruby, a subset of cadical.hpp's State enum with the same
91
+ * bit values. We always terminate clauses, so the transient ADDING state is never left set
92
+ * between calls; SOLVING / INITIALIZING / DELETING are likewise unobservable. Hence for us
93
+ * VALID == READY, and every method's require() contract is one of the masks below. */
94
+ enum {
95
+ CONFIGURING = 2, STEADY = 4, SATISFIED = 32, UNSATISFIED = 64, INCONCLUSIVE = 256
96
+ };
97
+ #define SOLVED (SATISFIED | UNSATISFIED | INCONCLUSIVE)
98
+ #define READY (CONFIGURING | STEADY | SOLVED)
99
+
100
+ static const char *state_name(int s) {
101
+ switch (s) {
102
+ case CONFIGURING: return "configuring";
103
+ case STEADY: return "steady";
104
+ case SATISFIED: return "satisfied";
105
+ case UNSATISFIED: return "unsatisfied";
106
+ case INCONCLUSIVE: return "inconclusive";
107
+ default: return "invalid";
108
+ }
109
+ }
110
+
111
+ /* Fetch the solver, checking it is live and in one of the +allowed+ states (the method's
112
+ * require() contract). Raising here turns CaDiCaL's fatal state aborts into Ruby exceptions. */
113
+ static cadical_t *get_cad(VALUE self, int allowed) {
114
+ cadical_t *c;
115
+ TypedData_Get_Struct(self, cadical_t, &cadical_type, c);
116
+ if (!c->solver) rb_raise(rb_eRuntimeError, "CaDiCaL already released");
117
+ if (!(c->state & allowed))
118
+ rb_raise(rb_eRuntimeError, "CaDiCaL: operation not allowed in the '%s' state", state_name(c->state));
119
+ return c;
120
+ }
121
+
122
+ /* Record the state a call transitions to (cadical.hpp's 'ensure' postcondition). */
123
+ static void ensure(cadical_t *c, int state) { c->state = state; }
124
+
125
+ static VALUE cad_alloc(VALUE klass) {
126
+ cadical_t *c;
127
+ VALUE obj = TypedData_Make_Struct(klass, cadical_t, &cadical_type, c);
128
+ c->solver = ccadical_init();
129
+ c->on_terminate = Qnil;
130
+ c->on_learn = Qnil;
131
+ c->pending_exc = Qnil;
132
+ c->proof_file = NULL;
133
+ c->state = CONFIGURING;
134
+ return obj;
135
+ }
136
+
137
+ /*
138
+ * call-seq:
139
+ * solver.release -> nil
140
+ *
141
+ * Release the solver and its resources now (instead of at GC); the object is unusable
142
+ * afterwards.
143
+ */
144
+ static VALUE cad_release(VALUE self) {
145
+ cadical_t *c = get_cad(self, READY);
146
+ if (c->proof_file) {
147
+ fclose(c->proof_file);
148
+ c->proof_file = NULL;
149
+ }
150
+ ccadical_release(c->solver);
151
+ c->solver = NULL;
152
+ return Qnil;
153
+ }
154
+
155
+ /* --- clauses / assumptions / constraints --- */
156
+
157
+ /*
158
+ * call-seq:
159
+ * solver.add(*lits) -> self
160
+ *
161
+ * Add a permanent clause: the disjunction of the given literals.
162
+ *
163
+ * Unlike the C API you need not add the terminating 0, it is appended for you. With no
164
+ * literals it adds the empty clause (an unsatisfiable formula).
165
+ */
166
+ static VALUE cad_add(int argc, VALUE *argv, VALUE self) {
167
+ cadical_t *c = get_cad(self, READY);
168
+ int i;
169
+ for (i = 0; i < argc; i++) ccadical_add(c->solver, NUM2INT(argv[i]));
170
+ ccadical_add(c->solver, 0);
171
+ ensure(c, STEADY); /* adding a clause leaves the SATISFIED / UNSATISFIED state */
172
+ return self;
173
+ }
174
+
175
+ /*
176
+ * call-seq:
177
+ * solver.assume(lit) -> self
178
+ *
179
+ * Assume valid non-zero literal +lit+ for next call to #solve. These assumptions are reset
180
+ * after the call to #solve as well as after returning from #simplify.
181
+ *
182
+ * See #failed.
183
+ */
184
+ static VALUE cad_assume(VALUE self, VALUE lit) {
185
+ cadical_t *c = get_cad(self, READY);
186
+ ccadical_assume(c->solver, NUM2INT(lit));
187
+ ensure(c, STEADY);
188
+ return self;
189
+ }
190
+
191
+ /*
192
+ * call-seq:
193
+ * solver.constrain(*lits) -> self
194
+ *
195
+ * Same functionality as #add, but the clause only exists for the next call to #solve (same
196
+ * lifetime as assumptions). Only one constraint may exist at a time. A new constraint
197
+ * replaces the old.
198
+ *
199
+ * See #constraint_failed?.
200
+ */
201
+ static VALUE cad_constrain(int argc, VALUE *argv, VALUE self) {
202
+ cadical_t *c = get_cad(self, READY);
203
+ int i;
204
+ for (i = 0; i < argc; i++) ccadical_constrain(c->solver, NUM2INT(argv[i]));
205
+ ccadical_constrain(c->solver, 0);
206
+ ensure(c, STEADY);
207
+ return self;
208
+ }
209
+
210
+ /* --- solving --- */
211
+
212
+ /* Re-raise an exception deferred from a callback during the last solve/simplify. */
213
+ static void reraise_pending(cadical_t *c) {
214
+ if (!NIL_P(c->pending_exc)) {
215
+ VALUE e = c->pending_exc;
216
+ c->pending_exc = Qnil;
217
+ rb_exc_raise(e);
218
+ }
219
+ }
220
+
221
+ /* #solve / #simplify run the (possibly long) search with the GVL released, so other Ruby
222
+ * threads keep running. The terminate/learn callbacks re-acquire the GVL (see below). */
223
+ struct solve_call {
224
+ CCaDiCaL *solver;
225
+ int result;
226
+ };
227
+
228
+ static void *do_solve(void *p) {
229
+ struct solve_call *a = p;
230
+ a->result = ccadical_solve(a->solver);
231
+ return NULL;
232
+ }
233
+
234
+ static void *do_simplify(void *p) {
235
+ struct solve_call *a = p;
236
+ a->result = ccadical_simplify(a->solver);
237
+ return NULL;
238
+ }
239
+
240
+ /* Unblock a GVL-released search if the calling thread is interrupted (e.g. Thread#kill):
241
+ * ccadical_terminate stops the search asynchronously, as the C API allows. */
242
+ static void unblock_solve(void *p) {
243
+ struct solve_call *a = p;
244
+ ccadical_terminate(a->solver);
245
+ }
246
+
247
+ /*
248
+ * call-seq:
249
+ * solver.solve -> :sat or :unsat or :unknown
250
+ *
251
+ * Try to solve the current formula. Returns +:sat+ (satisfiable), +:unsat+ (unsatisfiable),
252
+ * or +:unknown+ (limit reached or interrupted through #terminate).
253
+ */
254
+ static VALUE cad_solve(VALUE self) {
255
+ cadical_t *c = get_cad(self, READY);
256
+ struct solve_call a = { c->solver, 0 };
257
+ rb_thread_call_without_gvl(do_solve, &a, unblock_solve, &a);
258
+ ensure(c, a.result == 10 ? SATISFIED : a.result == 20 ? UNSATISFIED : INCONCLUSIVE);
259
+ reraise_pending(c);
260
+ return result_sym(a.result);
261
+ }
262
+
263
+ /*
264
+ * call-seq:
265
+ * solver.simplify -> :sat or :unsat or :unknown
266
+ *
267
+ * This function executes preprocessing rounds. It is similar to #solve except that no CDCL nor local search, nor
268
+ * lucky phases are executed. The result values are also the same: +:sat+, +:unsat+, or
269
+ * +:unknown+.
270
+ *
271
+ * As #solve it resets current assumptions and limits before returning.
272
+ */
273
+ static VALUE cad_simplify(VALUE self) {
274
+ cadical_t *c = get_cad(self, READY);
275
+ struct solve_call a = { c->solver, 0 };
276
+ rb_thread_call_without_gvl(do_simplify, &a, unblock_solve, &a);
277
+ ensure(c, a.result == 10 ? SATISFIED : a.result == 20 ? UNSATISFIED : INCONCLUSIVE);
278
+ reraise_pending(c);
279
+ return result_sym(a.result);
280
+ }
281
+
282
+ /*
283
+ * call-seq:
284
+ * solver.val(lit) -> integer
285
+ *
286
+ * Get the value of a valid non-zero literal +lit+. This follows the IPASIR semantics which
287
+ * says to return +lit+ if +lit+ is assigned to true and +-lit+ if +lit+ is assigned to false.
288
+ * This has the consequence that the returned literal is always assigned to true and thus might
289
+ * be a bit confusing. To avoid the headache of these semantics (which we unfortunately should
290
+ * follow to be compatible with IPASIR) the user can simply use positive variable indices
291
+ * instead of literals. Then the returned integer is negative if the variable is assigned to
292
+ * false and positive if it is assigned to true.
293
+ *
294
+ * Raises RuntimeError unless the last #solve returned +:sat+.
295
+ */
296
+ static VALUE cad_val(VALUE self, VALUE lit) {
297
+ return INT2NUM(ccadical_val(get_cad(self, SATISFIED)->solver, NUM2INT(lit)));
298
+ }
299
+
300
+ /*
301
+ * call-seq:
302
+ * solver.true?(lit) -> true or false
303
+ *
304
+ * Determine whether +lit+ is assigned to true (i.e. <tt>val(lit) == lit</tt>).
305
+ *
306
+ * Raises RuntimeError unless the last #solve returned +:sat+.
307
+ */
308
+ static VALUE cad_is_true(VALUE self, VALUE lit) {
309
+ int l = NUM2INT(lit);
310
+ return ccadical_val(get_cad(self, SATISFIED)->solver, l) == l ? Qtrue : Qfalse;
311
+ }
312
+
313
+ /*
314
+ * call-seq:
315
+ * solver.failed(lit) -> true or false
316
+ *
317
+ * Determine whether the valid non-zero literal +lit+ is in the core. Returns true if the
318
+ * literal is in the core and false otherwise. Note that the core does not have to be minimal.
319
+ *
320
+ * Raises RuntimeError unless the last #solve returned +:unsat+.
321
+ */
322
+ static VALUE cad_failed(VALUE self, VALUE lit) {
323
+ return ccadical_failed(get_cad(self, UNSATISFIED)->solver, NUM2INT(lit)) ? Qtrue : Qfalse;
324
+ }
325
+
326
+ /*
327
+ * call-seq:
328
+ * solver.constraint_failed? -> true or false
329
+ *
330
+ * Determine whether the constraint was used to prove the unsatisfiability. Note that the
331
+ * formula might still be unsatisfiable without the constraint.
332
+ *
333
+ * Raises RuntimeError unless the last #solve returned +:unsat+.
334
+ */
335
+ static VALUE cad_constraint_failed(VALUE self) {
336
+ return ccadical_constraint_failed(get_cad(self, UNSATISFIED)->solver) ? Qtrue : Qfalse;
337
+ }
338
+
339
+ /*
340
+ * call-seq:
341
+ * solver.fixed(lit) -> 1, -1, or 0
342
+ *
343
+ * Root level assigned variables can be queried with this function. It returns +1+ if the
344
+ * literal is implied by the formula, +-1+ if its negation is implied, or +0+ if this is
345
+ * unclear at this point.
346
+ */
347
+ static VALUE cad_fixed(VALUE self, VALUE lit) {
348
+ return INT2NUM(ccadical_fixed(get_cad(self, READY)->solver, NUM2INT(lit)));
349
+ }
350
+
351
+ /* --- callbacks (set_terminate / set_learn) --- */
352
+
353
+ static VALUE call_proc0(VALUE proc) { return rb_funcall(proc, id_call, 0); }
354
+
355
+ static void *terminate_body(void *state) {
356
+ cadical_t *c = state;
357
+ int err = 0;
358
+ VALUE r = rb_protect(call_proc0, c->on_terminate, &err);
359
+ if (err) {
360
+ c->pending_exc = rb_errinfo();
361
+ rb_set_errinfo(Qnil);
362
+ return (void *)1; /* abort the solve so we return and can re-raise */
363
+ }
364
+ return (void *)(intptr_t)(RTEST(r) ? 1 : 0);
365
+ }
366
+
367
+ static int terminate_trampoline(void *state) {
368
+ cadical_t *c = state;
369
+ if (NIL_P(c->on_terminate)) return 0; /* no callback: cheap, no GVL needed */
370
+ if (!NIL_P(c->pending_exc)) return 1; /* already failed: keep aborting */
371
+ return (int)(intptr_t)rb_thread_call_with_gvl(terminate_body, c);
372
+ }
373
+
374
+ struct learn_call {
375
+ cadical_t *c;
376
+ int *clause;
377
+ };
378
+
379
+ static VALUE learn_invoke(VALUE arg) {
380
+ struct learn_call *lc = (struct learn_call *)arg;
381
+ VALUE ary = rb_ary_new();
382
+ int *p;
383
+ for (p = lc->clause; *p; p++) rb_ary_push(ary, INT2NUM(*p));
384
+ return rb_funcall(lc->c->on_learn, id_call, 1, ary);
385
+ }
386
+
387
+ static void *learn_body(void *arg) {
388
+ struct learn_call *lc = arg;
389
+ int err = 0;
390
+ rb_protect(learn_invoke, (VALUE)lc, &err);
391
+ if (err) {
392
+ lc->c->pending_exc = rb_errinfo();
393
+ rb_set_errinfo(Qnil);
394
+ /* Unlike terminate, the learn callback returns void and cannot signal an abort, so ask
395
+ * the solver to stop asynchronously; #solve then returns and re-raises the exception. */
396
+ ccadical_terminate(lc->c->solver);
397
+ }
398
+ return NULL;
399
+ }
400
+
401
+ static void learn_trampoline(void *state, int *clause) {
402
+ cadical_t *c = state;
403
+ struct learn_call lc;
404
+ if (NIL_P(c->on_learn) || !NIL_P(c->pending_exc)) return;
405
+ lc.c = c;
406
+ lc.clause = clause;
407
+ rb_thread_call_with_gvl(learn_body, &lc);
408
+ }
409
+
410
+ /*
411
+ * call-seq:
412
+ * solver.set_terminate { -> bool } -> self
413
+ * solver.set_terminate -> self
414
+ *
415
+ * Add a callback which is checked regularly for termination: return a truthy value from the
416
+ * block to abort the search (#solve then returns +:unknown+). There can only be one such
417
+ * callback; installing a second replaces the first, and calling with no block clears it.
418
+ */
419
+ static VALUE cad_set_terminate(VALUE self) {
420
+ cadical_t *c = get_cad(self, READY);
421
+ if (rb_block_given_p()) {
422
+ c->on_terminate = rb_block_proc();
423
+ ccadical_set_terminate(c->solver, c, terminate_trampoline);
424
+ } else {
425
+ c->on_terminate = Qnil;
426
+ ccadical_set_terminate(c->solver, NULL, NULL);
427
+ }
428
+ return self;
429
+ }
430
+
431
+ /*
432
+ * call-seq:
433
+ * solver.set_learn(max_length) { |clause| ... } -> self
434
+ * solver.set_learn(max_length) -> self
435
+ *
436
+ * Add a callback which allows to export learned clauses: the block is invoked with each
437
+ * learned clause of at most +max_length+ literals, as an Array of literals.
438
+ * Installing a second callback replaces the first, and calling with no block clears it.
439
+ */
440
+ static VALUE cad_set_learn(VALUE self, VALUE max_length) {
441
+ cadical_t *c = get_cad(self, READY);
442
+ if (rb_block_given_p()) {
443
+ c->on_learn = rb_block_proc();
444
+ ccadical_set_learn(c->solver, c, NUM2INT(max_length), learn_trampoline);
445
+ } else {
446
+ c->on_learn = Qnil;
447
+ ccadical_set_learn(c->solver, NULL, NUM2INT(max_length), NULL);
448
+ }
449
+ return self;
450
+ }
451
+
452
+ /*
453
+ * call-seq:
454
+ * solver.terminate -> self
455
+ *
456
+ * Force termination of #solve asynchronously.
457
+ */
458
+ static VALUE cad_terminate(VALUE self) {
459
+ ccadical_terminate(get_cad(self, READY)->solver);
460
+ return self;
461
+ }
462
+
463
+ /* --- options / limits / stats --- */
464
+
465
+ /*
466
+ * call-seq:
467
+ * solver.set_option(name, value) -> self
468
+ *
469
+ * Set the option +name+ to +value+. If the option value is out of range the actual value is
470
+ * computed as the closest (minimum or maximum) value possible. Options can only be set right
471
+ * after initialization; the binding raises RuntimeError otherwise.
472
+ */
473
+ static VALUE cad_set_option(VALUE self, VALUE name, VALUE val) {
474
+ ccadical_set_option(get_cad(self, CONFIGURING)->solver, StringValueCStr(name), NUM2INT(val));
475
+ return self;
476
+ }
477
+
478
+ /*
479
+ * call-seq:
480
+ * solver.get_option(name) -> integer
481
+ *
482
+ * Get the current value of the option +name+. If +name+ is invalid then zero is returned.
483
+ */
484
+ static VALUE cad_get_option(VALUE self, VALUE name) {
485
+ return INT2NUM(ccadical_get_option(get_cad(self, READY)->solver, StringValueCStr(name)));
486
+ }
487
+
488
+ /*
489
+ * call-seq:
490
+ * solver.limit(name, value) -> self
491
+ *
492
+ * Specify search limits, where currently +name+ can be "conflicts", "decisions",
493
+ * "preprocessing", or "localsearch". The first two limits are unbounded by default. Thus using
494
+ * a negative limit for conflicts or decisions switches back to the default of unlimited search
495
+ * (for that particular limit). The preprocessing limit determines the number of preprocessing
496
+ * rounds, which is zero by default. Similarly, the local search limit determines the number of
497
+ * local search rounds (zero by default).
498
+ *
499
+ * These limits are only valid for the next #solve or #simplify call and reset to their default
500
+ * after #solve returns (as well as overwritten and reset during calls to #simplify).
501
+ *
502
+ * An unknown +name+ raises ArgumentError.
503
+ */
504
+ static VALUE cad_limit(VALUE self, VALUE name, VALUE limit) {
505
+ const char *n = StringValueCStr(name);
506
+ cadical_t *c = get_cad(self, READY);
507
+ if (strcmp(n, "conflicts") && strcmp(n, "decisions") &&
508
+ strcmp(n, "preprocessing") && strcmp(n, "localsearch"))
509
+ rb_raise(rb_eArgError, "unknown CaDiCaL limit: %s", n);
510
+ ccadical_limit(c->solver, n, NUM2INT(limit));
511
+ return self;
512
+ }
513
+
514
+ /*
515
+ * call-seq:
516
+ * solver.active -> integer
517
+ *
518
+ * Returns the number of currently active variables. A variable becomes active if a clause is
519
+ * added with it, and inactive again if it is eliminated or fixed at the root level.
520
+ */
521
+ static VALUE cad_active(VALUE self) {
522
+ return LL2NUM(ccadical_active(get_cad(self, READY)->solver));
523
+ }
524
+
525
+ /*
526
+ * call-seq:
527
+ * solver.irredundant -> integer
528
+ *
529
+ * Returns the number of currently active irredundant clauses. A clause becomes inactive if it
530
+ * is satisfied, subsumed, or eliminated.
531
+ */
532
+ static VALUE cad_irredundant(VALUE self) {
533
+ return LL2NUM(ccadical_irredundant(get_cad(self, READY)->solver));
534
+ }
535
+
536
+ /* --- variables / phases / freezing --- */
537
+
538
+ /*
539
+ * call-seq:
540
+ * solver.declare_one_more_variable -> integer
541
+ *
542
+ * Returns the next fresh variable that was not used internally.
543
+ */
544
+ static VALUE cad_declare_one_more_variable(VALUE self) {
545
+ cadical_t *c = get_cad(self, READY);
546
+ int v = ccadical_declare_one_more_variable(c->solver);
547
+ ensure(c, STEADY);
548
+ return INT2NUM(v);
549
+ }
550
+
551
+ /*
552
+ * call-seq:
553
+ * solver.declare_more_variables(n) -> integer
554
+ *
555
+ * Increase the maximum variable index by +n+ new variables. Returns the new maximum variable
556
+ * index, which is the highest variable name of the consecutive range of newly declared
557
+ * variables.
558
+ */
559
+ static VALUE cad_declare_more_variables(VALUE self, VALUE n) {
560
+ cadical_t *c = get_cad(self, READY);
561
+ int v = ccadical_declare_more_variables(c->solver, NUM2INT(n));
562
+ ensure(c, STEADY);
563
+ return INT2NUM(v);
564
+ }
565
+
566
+ /*
567
+ * call-seq:
568
+ * solver.vars -> integer
569
+ *
570
+ * The library internally keeps a maximum variable index, which this returns.
571
+ */
572
+ static VALUE cad_vars(VALUE self) {
573
+ return INT2NUM(ccadical_vars(get_cad(self, READY)->solver));
574
+ }
575
+
576
+ /*
577
+ * call-seq:
578
+ * solver.phase(lit) -> self
579
+ *
580
+ * Force the default decision phase of a variable to a certain value (the sign of +lit+).
581
+ */
582
+ static VALUE cad_phase(VALUE self, VALUE lit) {
583
+ ccadical_phase(get_cad(self, READY)->solver, NUM2INT(lit));
584
+ return self;
585
+ }
586
+
587
+ /*
588
+ * call-seq:
589
+ * solver.unphase(lit) -> self
590
+ *
591
+ * Reset the default decision phase of +lit+'s variable forced by #phase.
592
+ */
593
+ static VALUE cad_unphase(VALUE self, VALUE lit) {
594
+ ccadical_unphase(get_cad(self, READY)->solver, NUM2INT(lit));
595
+ return self;
596
+ }
597
+
598
+ /* CaDiCaL's freeze/melt/frozen for literals, exposed as #freeze_lit / #melt_lit / #frozen_lit?.
599
+ * The +_lit+ suffix keeps #freeze_lit and #frozen_lit? from shadowing Ruby's Object#freeze /
600
+ * #frozen? (which take no argument and mean object immutability); #melt_lit follows suit. */
601
+
602
+ /*
603
+ * call-seq:
604
+ * solver.freeze_lit(lit) -> self
605
+ *
606
+ * #freeze_lit, #melt_lit and #frozen_lit? are reference-counting functions which avoid restoring
607
+ * clauses but require substantial user guidance. This was the only way to use inprocessing in
608
+ * incremental SAT solving in Lingeling (and before in MiniSAT's 'freeze' / 'thaw') and which did
609
+ * not use automatic clause restoring. In general this is slower than restoring clauses and should
610
+ * not be used.
611
+ *
612
+ * In essence the user freezes variables which potentially are still needed in clauses added or
613
+ * assumptions used after the next #solve call. As in Lingeling you can freeze a variable multiple
614
+ * times, but then have to #melt_lit it the same number of times again in order to enable variable
615
+ * eliminating on it etc. The arguments can be literals (negative indices) but conceptually
616
+ * variables are frozen.
617
+ *
618
+ * In the old way of doing things without restore you should not use a variable incrementally (in
619
+ * #add or #assume), which was used before and potentially could have been eliminated in a previous
620
+ * #solve call. This can lead to spurious satisfying assignment. In order to check this API
621
+ * contract one can use the "checkfrozen" option (via #set_option). This has the drawback that
622
+ * restoring clauses implicitly would fail with a fatal error message even if in principle the
623
+ * solver could just restore clauses. Thus this option is disabled by default.
624
+ *
625
+ * See the CaDiCaL SAT'19 paper [FazekasBiereScholl-SAT'19] for more details.
626
+ */
627
+ static VALUE cad_freeze_lit(VALUE self, VALUE lit) {
628
+ ccadical_freeze(get_cad(self, READY)->solver, NUM2INT(lit));
629
+ return self;
630
+ }
631
+
632
+ /*
633
+ * call-seq:
634
+ * solver.melt_lit(lit) -> self
635
+ *
636
+ * Melt (thaw) the variable of +lit+, reversing one #freeze_lit: once its freeze count reaches
637
+ * zero, simplification may eliminate the variable again. The variable must currently be frozen.
638
+ */
639
+ static VALUE cad_melt_lit(VALUE self, VALUE lit) {
640
+ ccadical_melt(get_cad(self, READY)->solver, NUM2INT(lit));
641
+ return self;
642
+ }
643
+
644
+ /*
645
+ * call-seq:
646
+ * solver.frozen_lit?(lit) -> true or false
647
+ *
648
+ * Returns whether the variable of +lit+ is currently frozen (see #freeze_lit).
649
+ */
650
+ static VALUE cad_frozen_lit(VALUE self, VALUE lit) {
651
+ return ccadical_frozen(get_cad(self, READY)->solver, NUM2INT(lit)) ? Qtrue : Qfalse;
652
+ }
653
+
654
+ /* --- proof tracing --- */
655
+
656
+ /*
657
+ * call-seq:
658
+ * solver.trace_proof(path) -> true or false
659
+ *
660
+ * Enable clausal proof tracing to the file at +path+; returns true if it was successfully
661
+ * opened for writing. Must be called before any #add or #solve, else it raises RuntimeError.
662
+ */
663
+ static VALUE cad_trace_proof(VALUE self, VALUE path) {
664
+ cadical_t *c = get_cad(self, CONFIGURING);
665
+ const char *p = StringValueCStr(path);
666
+ FILE *f = fopen(p, "w");
667
+ int ok;
668
+ if (!f) rb_sys_fail(p);
669
+ ok = ccadical_trace_proof(c->solver, f, p);
670
+ if (!ok) {
671
+ fclose(f);
672
+ return Qfalse;
673
+ }
674
+ if (c->proof_file) fclose(c->proof_file);
675
+ c->proof_file = f;
676
+ return Qtrue;
677
+ }
678
+
679
+ /*
680
+ * call-seq:
681
+ * solver.close_proof -> self
682
+ *
683
+ * Close the proof trace early. Before actually closing, CaDiCaL checks whether a proof step (a
684
+ * clause addition or deletion) has been traced since the last flush and, if so, flushes it
685
+ * first. The file opened by #trace_proof is closed as well.
686
+ *
687
+ * Raises RuntimeError unless a proof trace is currently open (CaDiCaL would otherwise abort the
688
+ * process).
689
+ */
690
+ static VALUE cad_close_proof(VALUE self) {
691
+ cadical_t *c = get_cad(self, READY);
692
+ if (!c->proof_file) rb_raise(rb_eRuntimeError, "no proof trace is open (see #trace_proof)");
693
+ ccadical_close_proof(c->solver);
694
+ fclose(c->proof_file);
695
+ c->proof_file = NULL;
696
+ return self;
697
+ }
698
+
699
+ /*
700
+ * call-seq:
701
+ * solver.conclude -> self
702
+ *
703
+ * Triggers the conclusion of incremental proofs. If the solver is satisfied it gives the model
704
+ * to the proof tracer; if it is unsatisfied it learns the failing core (for failed assumptions
705
+ * the core negated as a single clause, for a failing constraint possibly several) and concludes
706
+ * with the ids of the newly learnt clauses or the id of the global conflict; if the result was
707
+ * unknown it collects the currently entailed literals and adds them to the proof.
708
+ *
709
+ * Raises RuntimeError unless a #solve or #simplify has completed.
710
+ */
711
+ static VALUE cad_conclude(VALUE self) {
712
+ ccadical_conclude(get_cad(self, SOLVED)->solver);
713
+ return self;
714
+ }
715
+
716
+ /* --- class-level --- */
717
+
718
+ /*
719
+ * call-seq:
720
+ * CaDiCaL.signature -> string
721
+ *
722
+ * The name of this library.
723
+ */
724
+ static VALUE cad_signature(VALUE klass) {
725
+ (void)klass;
726
+ return rb_str_new_cstr(ccadical_signature());
727
+ }
728
+
729
+ void Init_cadical(void) {
730
+ VALUE c;
731
+
732
+ id_sat = rb_intern("sat");
733
+ id_unsat = rb_intern("unsat");
734
+ id_unknown = rb_intern("unknown");
735
+ id_call = rb_intern("call");
736
+
737
+ c = rb_define_class("CaDiCaL", rb_cObject);
738
+ rb_define_alloc_func(c, cad_alloc);
739
+
740
+ rb_define_singleton_method(c, "signature", cad_signature, 0);
741
+
742
+ rb_define_method(c, "release", cad_release, 0);
743
+
744
+ rb_define_method(c, "add", cad_add, -1);
745
+ rb_define_method(c, "assume", cad_assume, 1);
746
+ rb_define_method(c, "constrain", cad_constrain, -1);
747
+
748
+ rb_define_method(c, "solve", cad_solve, 0);
749
+ rb_define_method(c, "simplify", cad_simplify, 0);
750
+ rb_define_method(c, "val", cad_val, 1);
751
+ rb_define_method(c, "true?", cad_is_true, 1);
752
+ rb_define_method(c, "failed", cad_failed, 1);
753
+ rb_define_method(c, "constraint_failed?", cad_constraint_failed, 0);
754
+ rb_define_method(c, "fixed", cad_fixed, 1);
755
+
756
+ rb_define_method(c, "set_terminate", cad_set_terminate, 0);
757
+ rb_define_method(c, "set_learn", cad_set_learn, 1);
758
+ rb_define_method(c, "terminate", cad_terminate, 0);
759
+
760
+ rb_define_method(c, "set_option", cad_set_option, 2);
761
+ rb_define_method(c, "get_option", cad_get_option, 1);
762
+ rb_define_method(c, "limit", cad_limit, 2);
763
+ rb_define_method(c, "active", cad_active, 0);
764
+ rb_define_method(c, "irredundant", cad_irredundant, 0);
765
+
766
+ rb_define_method(c, "declare_one_more_variable", cad_declare_one_more_variable, 0);
767
+ rb_define_method(c, "declare_more_variables", cad_declare_more_variables, 1);
768
+ rb_define_method(c, "vars", cad_vars, 0);
769
+ rb_define_method(c, "phase", cad_phase, 1);
770
+ rb_define_method(c, "unphase", cad_unphase, 1);
771
+ rb_define_method(c, "freeze_lit", cad_freeze_lit, 1);
772
+ rb_define_method(c, "melt_lit", cad_melt_lit, 1);
773
+ rb_define_method(c, "frozen_lit?", cad_frozen_lit, 1);
774
+
775
+ rb_define_method(c, "trace_proof", cad_trace_proof, 1);
776
+ rb_define_method(c, "close_proof", cad_close_proof, 0);
777
+ rb_define_method(c, "conclude", cad_conclude, 0);
778
+ }