plan-verify 0.0.0-stage → 0.1.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 (222) hide show
  1. package/.github/workflows/ci.yml +48 -0
  2. package/README.md +1431 -2
  3. package/assets/icon.png +0 -0
  4. package/assets/logo-mark.png +0 -0
  5. package/assets/logo.png +0 -0
  6. package/examples/checkout.ears.md +74 -0
  7. package/examples/order-always.ears.md +3 -0
  8. package/examples/order-awkward.ears.md +8 -0
  9. package/examples/order-bad.json +1 -0
  10. package/examples/order-chain.ears.md +9 -0
  11. package/examples/order-chosen.ears.md +7 -0
  12. package/examples/order-clear.ears.md +8 -0
  13. package/examples/order-combined.ears.md +10 -0
  14. package/examples/order-conflict.ears.md +8 -0
  15. package/examples/order-covered.ears.md +9 -0
  16. package/examples/order-duplicate.ears.md +8 -0
  17. package/examples/order-fail.json +5 -0
  18. package/examples/order-misuse.ears.md +7 -0
  19. package/examples/order-negation.ears.md +7 -0
  20. package/examples/order-pass.json +5 -0
  21. package/examples/order-quantity.ears.md +8 -0
  22. package/examples/order-redundant.ears.md +9 -0
  23. package/examples/order-repair.ears.md +9 -0
  24. package/examples/order-repeat.ears.md +13 -0
  25. package/examples/order-same.ears.md +3 -0
  26. package/examples/order-status.ears.md +7 -0
  27. package/examples/order-unanswered.ears.md +8 -0
  28. package/examples/order-unhandled.ears.md +8 -0
  29. package/examples/order-unmeasured.ears.md +10 -0
  30. package/examples/order-unused.ears.md +13 -0
  31. package/examples/order-vacuous.ears.md +9 -0
  32. package/examples/order-vague.ears.md +7 -0
  33. package/examples/order-wide.ears.md +5 -0
  34. package/examples/order-wording.ears.md +8 -0
  35. package/examples/order.ears.md +7 -0
  36. package/examples/payment-if.ears.md +7 -0
  37. package/examples/plan-to-problems/README.md +54 -0
  38. package/examples/plan-to-problems/expected-problems.md +25 -0
  39. package/examples/plan-to-problems/generate.mjs +186 -0
  40. package/examples/plan-to-problems/generated.ears.md +27 -0
  41. package/examples/plan-to-problems/lint.json +576 -0
  42. package/examples/plan-to-problems/plan.md +5 -0
  43. package/examples/plan-to-problems/planted.txt +5 -0
  44. package/examples/plan-to-problems/problems.md +112 -0
  45. package/examples/status-placed.ears.md +3 -0
  46. package/package.json +25 -4
  47. package/scripts/install-scriptc.sh +16 -0
  48. package/skills/plan-verify/SKILL.md +1515 -0
  49. package/src/check.ts +250 -0
  50. package/src/decide.ts +1324 -0
  51. package/src/ears.ts +1520 -0
  52. package/src/jsonvalue.ts +228 -0
  53. package/src/lint.ts +1245 -0
  54. package/src/main.ts +273 -0
  55. package/src/policy.ts +148 -0
  56. package/src/repair.ts +567 -0
  57. package/src/report.ts +329 -0
  58. package/src/scenarios.ts +149 -0
  59. package/src/smt.ts +97 -0
  60. package/src/solver.ts +868 -0
  61. package/src/spec.ts +307 -0
  62. package/src/term.ts +331 -0
  63. package/src/text.ts +95 -0
  64. package/test/fixtures/malformed.ears.md +3 -0
  65. package/test/fixtures/unknown-phrase.ears.md +5 -0
  66. package/test/golden/check-fail-json.exit +1 -0
  67. package/test/golden/check-fail-json.stderr +0 -0
  68. package/test/golden/check-fail-json.stdout +13 -0
  69. package/test/golden/check-fail.exit +1 -0
  70. package/test/golden/check-fail.stderr +0 -0
  71. package/test/golden/check-fail.stdout +7 -0
  72. package/test/golden/check-pass-json.exit +1 -0
  73. package/test/golden/check-pass-json.stderr +0 -0
  74. package/test/golden/check-pass-json.stdout +6 -0
  75. package/test/golden/check-pass.exit +1 -0
  76. package/test/golden/check-pass.stderr +0 -0
  77. package/test/golden/check-pass.stdout +3 -0
  78. package/test/golden/emit-smt-json-usage.exit +1 -0
  79. package/test/golden/emit-smt-json-usage.stderr +0 -0
  80. package/test/golden/emit-smt-json-usage.stdout +4 -0
  81. package/test/golden/emit-smt-json.exit +1 -0
  82. package/test/golden/emit-smt-json.stderr +0 -0
  83. package/test/golden/emit-smt-json.stdout +5 -0
  84. package/test/golden/emit-smt.exit +1 -0
  85. package/test/golden/emit-smt.stderr +0 -0
  86. package/test/golden/emit-smt.stdout +10 -0
  87. package/test/golden/help.exit +1 -0
  88. package/test/golden/help.stderr +0 -0
  89. package/test/golden/help.stdout +8 -0
  90. package/test/golden/lint-awkward.exit +1 -0
  91. package/test/golden/lint-awkward.stderr +0 -0
  92. package/test/golden/lint-awkward.stdout +29 -0
  93. package/test/golden/lint-chain.exit +1 -0
  94. package/test/golden/lint-chain.stderr +0 -0
  95. package/test/golden/lint-chain.stdout +56 -0
  96. package/test/golden/lint-checkout.exit +1 -0
  97. package/test/golden/lint-checkout.stderr +0 -0
  98. package/test/golden/lint-checkout.stdout +301 -0
  99. package/test/golden/lint-clean.exit +1 -0
  100. package/test/golden/lint-clean.stderr +0 -0
  101. package/test/golden/lint-clean.stdout +9 -0
  102. package/test/golden/lint-clear.exit +1 -0
  103. package/test/golden/lint-clear.stderr +0 -0
  104. package/test/golden/lint-clear.stdout +9 -0
  105. package/test/golden/lint-conflict-json.exit +1 -0
  106. package/test/golden/lint-conflict-json.stderr +0 -0
  107. package/test/golden/lint-conflict-json.stdout +80 -0
  108. package/test/golden/lint-conflict.exit +1 -0
  109. package/test/golden/lint-conflict.stderr +0 -0
  110. package/test/golden/lint-conflict.stdout +41 -0
  111. package/test/golden/lint-duplicate.exit +1 -0
  112. package/test/golden/lint-duplicate.stderr +0 -0
  113. package/test/golden/lint-duplicate.stdout +14 -0
  114. package/test/golden/lint-gap.exit +1 -0
  115. package/test/golden/lint-gap.stderr +0 -0
  116. package/test/golden/lint-gap.stdout +12 -0
  117. package/test/golden/lint-misuse.exit +1 -0
  118. package/test/golden/lint-misuse.stderr +0 -0
  119. package/test/golden/lint-misuse.stdout +18 -0
  120. package/test/golden/lint-negation.exit +1 -0
  121. package/test/golden/lint-negation.stderr +0 -0
  122. package/test/golden/lint-negation.stdout +13 -0
  123. package/test/golden/lint-quantity.exit +1 -0
  124. package/test/golden/lint-quantity.stderr +0 -0
  125. package/test/golden/lint-quantity.stdout +41 -0
  126. package/test/golden/lint-redundant.exit +1 -0
  127. package/test/golden/lint-redundant.stderr +0 -0
  128. package/test/golden/lint-redundant.stdout +14 -0
  129. package/test/golden/lint-repair-json.exit +1 -0
  130. package/test/golden/lint-repair-json.stderr +0 -0
  131. package/test/golden/lint-repair-json.stdout +127 -0
  132. package/test/golden/lint-repair.exit +1 -0
  133. package/test/golden/lint-repair.stderr +0 -0
  134. package/test/golden/lint-repair.stdout +54 -0
  135. package/test/golden/lint-repeat.exit +1 -0
  136. package/test/golden/lint-repeat.stderr +0 -0
  137. package/test/golden/lint-repeat.stdout +48 -0
  138. package/test/golden/lint-status.exit +1 -0
  139. package/test/golden/lint-status.stderr +0 -0
  140. package/test/golden/lint-status.stdout +44 -0
  141. package/test/golden/lint-unanswered.exit +1 -0
  142. package/test/golden/lint-unanswered.stderr +0 -0
  143. package/test/golden/lint-unanswered.stdout +12 -0
  144. package/test/golden/lint-unhandled.exit +1 -0
  145. package/test/golden/lint-unhandled.stderr +0 -0
  146. package/test/golden/lint-unhandled.stdout +12 -0
  147. package/test/golden/lint-unmeasured.exit +1 -0
  148. package/test/golden/lint-unmeasured.stderr +0 -0
  149. package/test/golden/lint-unmeasured.stdout +13 -0
  150. package/test/golden/lint-unused-json.exit +1 -0
  151. package/test/golden/lint-unused-json.stderr +0 -0
  152. package/test/golden/lint-unused-json.stdout +37 -0
  153. package/test/golden/lint-unused.exit +1 -0
  154. package/test/golden/lint-unused.stderr +0 -0
  155. package/test/golden/lint-unused.stdout +25 -0
  156. package/test/golden/lint-vacuous-json.exit +1 -0
  157. package/test/golden/lint-vacuous-json.stderr +0 -0
  158. package/test/golden/lint-vacuous-json.stdout +27 -0
  159. package/test/golden/lint-vacuous.exit +1 -0
  160. package/test/golden/lint-vacuous.stderr +0 -0
  161. package/test/golden/lint-vacuous.stdout +19 -0
  162. package/test/golden/lint-vague.exit +1 -0
  163. package/test/golden/lint-vague.stderr +0 -0
  164. package/test/golden/lint-vague.stdout +21 -0
  165. package/test/golden/lint-wide.exit +1 -0
  166. package/test/golden/lint-wide.stderr +0 -0
  167. package/test/golden/lint-wide.stdout +9 -0
  168. package/test/golden/lint-wording.exit +1 -0
  169. package/test/golden/lint-wording.stderr +0 -0
  170. package/test/golden/lint-wording.stdout +21 -0
  171. package/test/golden/malformed-scenario.exit +1 -0
  172. package/test/golden/malformed-scenario.stderr +1 -0
  173. package/test/golden/malformed-scenario.stdout +0 -0
  174. package/test/golden/malformed-spec.exit +1 -0
  175. package/test/golden/malformed-spec.stderr +1 -0
  176. package/test/golden/malformed-spec.stdout +0 -0
  177. package/test/golden/policy-counterexample.exit +1 -0
  178. package/test/golden/policy-counterexample.stderr +0 -0
  179. package/test/golden/policy-counterexample.stdout +13 -0
  180. package/test/golden/policy-covered.exit +1 -0
  181. package/test/golden/policy-covered.stderr +0 -0
  182. package/test/golden/policy-covered.stdout +19 -0
  183. package/test/golden/policy-holds.exit +1 -0
  184. package/test/golden/policy-holds.stderr +0 -0
  185. package/test/golden/policy-holds.stdout +3 -0
  186. package/test/golden/policy-json.exit +1 -0
  187. package/test/golden/policy-json.stderr +0 -0
  188. package/test/golden/policy-json.stdout +34 -0
  189. package/test/golden/policy-status.exit +1 -0
  190. package/test/golden/policy-status.stderr +0 -0
  191. package/test/golden/policy-status.stdout +16 -0
  192. package/test/golden/policy-vacuous.exit +1 -0
  193. package/test/golden/policy-vacuous.stderr +0 -0
  194. package/test/golden/policy-vacuous.stdout +3 -0
  195. package/test/golden/policy-wide.exit +1 -0
  196. package/test/golden/policy-wide.stderr +0 -0
  197. package/test/golden/policy-wide.stdout +15 -0
  198. package/test/golden/policy.exit +1 -0
  199. package/test/golden/policy.stderr +0 -0
  200. package/test/golden/policy.stdout +19 -0
  201. package/test/golden/scenarios-json.exit +1 -0
  202. package/test/golden/scenarios-json.stderr +0 -0
  203. package/test/golden/scenarios-json.stdout +75 -0
  204. package/test/golden/scenarios-status.exit +1 -0
  205. package/test/golden/scenarios-status.stderr +0 -0
  206. package/test/golden/scenarios-status.stdout +24 -0
  207. package/test/golden/scenarios.exit +1 -0
  208. package/test/golden/scenarios.stderr +0 -0
  209. package/test/golden/scenarios.stdout +26 -0
  210. package/test/golden/unit.exit +1 -0
  211. package/test/golden/unit.stderr +0 -0
  212. package/test/golden/unit.stdout +2 -0
  213. package/test/golden/unknown-phrase.exit +1 -0
  214. package/test/golden/unknown-phrase.stderr +1 -0
  215. package/test/golden/unknown-phrase.stdout +0 -0
  216. package/test/golden/usage.exit +1 -0
  217. package/test/golden/usage.stderr +9 -0
  218. package/test/golden/usage.stdout +0 -0
  219. package/test/run-all.ts +499 -0
  220. package/test/soundness.ts +375 -0
  221. package/test/unit.ts +1352 -0
  222. package/tsconfig.json +15 -0
package/src/decide.ts ADDED
@@ -0,0 +1,1324 @@
1
+ /**
2
+ * Decision procedure for EARS requirements.
3
+ *
4
+ * Boolean structure is a DPLL search over a Tseitin CNF. Linear integer
5
+ * constraints, including enum indexes encoded as bounded integers, are posted
6
+ * into a bounds propagator. The search returns the lexicographically least
7
+ * model: false before true, enums in declaration order, integers from low to
8
+ * high. A conflict is a driver situation for which the output constraints are
9
+ * unsatisfiable. Large integer domains are decided from bounds and from the
10
+ * finitely many points where a comparison changes, not by listing the domain.
11
+ */
12
+
13
+ import { domainSize, findEnum, isDriver, type Requirement, type Spec, type Term } from "./spec.ts";
14
+ import { Binding, evalAssertion, evalBool, T_FALSE, T_TRUE } from "./term.ts";
15
+
16
+ const TRUE_ATOM = 0;
17
+ const OP_LE = 0;
18
+ const OP_GE = 1;
19
+ const OP_EQ = 2;
20
+ const OP_NE = 3;
21
+
22
+ class AtomInfo {
23
+ kind: number;
24
+ sym: number;
25
+ vars: number[];
26
+ coeffs: number[];
27
+ constant: number;
28
+ op: number;
29
+
30
+ constructor() {
31
+ this.kind = 2;
32
+ this.sym = -1;
33
+ this.vars = [];
34
+ this.coeffs = [];
35
+ this.constant = 0;
36
+ this.op = OP_LE;
37
+ }
38
+ }
39
+
40
+ class Ineq {
41
+ vars: number[];
42
+ coeffs: number[];
43
+ rhs: number;
44
+
45
+ constructor() {
46
+ this.vars = [];
47
+ this.coeffs = [];
48
+ this.rhs = 0;
49
+ }
50
+ }
51
+
52
+ class Diseq {
53
+ vars: number[];
54
+ coeffs: number[];
55
+ rhs: number;
56
+
57
+ constructor() {
58
+ this.vars = [];
59
+ this.coeffs = [];
60
+ this.rhs = 0;
61
+ }
62
+ }
63
+
64
+ class Lin {
65
+ vars: number[];
66
+ coeffs: number[];
67
+ constant: number;
68
+
69
+ constructor() {
70
+ this.vars = [];
71
+ this.coeffs = [];
72
+ this.constant = 0;
73
+ }
74
+ }
75
+
76
+ /**
77
+ * Lexicographically least full assignment satisfying every holds requirement,
78
+ * with at least one fails requirement false when that list is non-empty.
79
+ * wantGuard 0 ignores guard, 1 requires it true, 2 requires it false.
80
+ * A null guard with wantGuard 2 has no model.
81
+ * pins, when present, fixes every driver (pinDrivers true) or every known symbol.
82
+ */
83
+ export function minimumModel(
84
+ spec: Spec,
85
+ holds: Requirement[],
86
+ fails: Requirement[],
87
+ guard: Term | null,
88
+ wantGuard: number,
89
+ pins: Binding | null,
90
+ pinDrivers: boolean,
91
+ ): Binding | null {
92
+ if (wantGuard === 2 && guard === null) return null;
93
+ const engine = new Engine(spec);
94
+ if (!engine.compile(holds, fails, guard, wantGuard)) return null;
95
+ engine.reset();
96
+ if (pins !== null && !engine.applyPins(pins, pinDrivers)) return null;
97
+ if (!engine.propagate()) return null;
98
+ if (!engine.search(0, true)) return null;
99
+ return engine.toBinding(true);
100
+ }
101
+
102
+ /** True when some full assignment satisfies every requirement in holds. */
103
+ export function satisfiable(spec: Spec, holds: Requirement[]): boolean {
104
+ return new TheoryQuery(spec, holds).hasModel();
105
+ }
106
+
107
+ /**
108
+ * Lexicographically least driver assignment for which holds has no output model.
109
+ * Null when every driver situation has an output model.
110
+ */
111
+ export function firstUnsatDrivers(spec: Spec, holds: Requirement[]): Binding | null {
112
+ return new TheoryQuery(spec, holds).badDrivers();
113
+ }
114
+
115
+ /** True when the pinned driver situation still has an output model. */
116
+ export function outputsSat(spec: Spec, holds: Requirement[], drivers: Binding): boolean {
117
+ return new TheoryQuery(spec, holds).pinnedSat(drivers);
118
+ }
119
+
120
+ class Engine {
121
+ spec: Spec;
122
+ nsym: number;
123
+ baseLo: number[];
124
+ baseHi: number[];
125
+ isBool: boolean[];
126
+ drivers: number[];
127
+ atoms: AtomInfo[];
128
+ boolAtom: number[];
129
+ clauses: number[][];
130
+ holds: Requirement[];
131
+ fails: Requirement[];
132
+ guard: Term | null;
133
+ wantGuard: number;
134
+ val: number[];
135
+ lo: number[];
136
+ hi: number[];
137
+ ineq: Ineq[];
138
+ ne: Diseq[];
139
+ assigned: number;
140
+ ticks: number;
141
+ trailK: number[];
142
+ trailI: number[];
143
+ trailP: number[];
144
+ trailN: number;
145
+ snapStack: number[];
146
+ snapN: number;
147
+ w1: number[];
148
+ w2: number[];
149
+ watches: number[][];
150
+ units: number[];
151
+ emptyClause: boolean;
152
+ propQ: number[];
153
+ propH: number;
154
+ propN: number;
155
+ scratch: Binding;
156
+
157
+ constructor(spec: Spec) {
158
+ this.spec = spec;
159
+ this.nsym = spec.symbols.length;
160
+ this.baseLo = [];
161
+ this.baseHi = [];
162
+ this.isBool = [];
163
+ this.drivers = [];
164
+ this.atoms = [];
165
+ this.boolAtom = [];
166
+ this.clauses = [];
167
+ this.holds = [];
168
+ this.fails = [];
169
+ this.guard = null;
170
+ this.wantGuard = 0;
171
+ this.val = [];
172
+ this.lo = [];
173
+ this.hi = [];
174
+ this.ineq = [];
175
+ this.ne = [];
176
+ this.assigned = 0;
177
+ this.ticks = 0;
178
+ this.trailK = [];
179
+ this.trailI = [];
180
+ this.trailP = [];
181
+ this.trailN = 0;
182
+ this.snapStack = [];
183
+ this.snapN = 0;
184
+ this.w1 = [];
185
+ this.w2 = [];
186
+ this.watches = [];
187
+ this.units = [];
188
+ this.emptyClause = false;
189
+ this.propQ = [];
190
+ this.propH = 0;
191
+ this.propN = 0;
192
+ this.scratch = new Binding(this.nsym);
193
+ const truth = new AtomInfo();
194
+ truth.kind = 3;
195
+ this.atoms.push(truth);
196
+ for (let i = 0; i < this.nsym; i++) {
197
+ const sym = spec.symbols[i];
198
+ if (sym.sort.kind === "bool") {
199
+ this.baseLo.push(0);
200
+ this.baseHi.push(1);
201
+ this.isBool.push(true);
202
+ const info = new AtomInfo();
203
+ info.kind = 0;
204
+ info.sym = i;
205
+ this.boolAtom.push(this.atoms.length);
206
+ this.atoms.push(info);
207
+ } else if (sym.sort.kind === "int") {
208
+ this.baseLo.push(sym.sort.min);
209
+ this.baseHi.push(sym.sort.max);
210
+ this.isBool.push(false);
211
+ this.boolAtom.push(-1);
212
+ } else {
213
+ const decl = findEnum(spec, sym.sort.enumName);
214
+ const last = decl === null || decl.variants.length === 0 ? 0 : decl.variants.length - 1;
215
+ this.baseLo.push(0);
216
+ this.baseHi.push(last);
217
+ this.isBool.push(false);
218
+ this.boolAtom.push(-1);
219
+ }
220
+ if (isDriver(sym)) this.drivers.push(i);
221
+ }
222
+ }
223
+
224
+ compile(holds: Requirement[], fails: Requirement[], guard: Term | null, wantGuard: number): boolean {
225
+ this.holds = holds;
226
+ this.fails = fails;
227
+ this.guard = guard;
228
+ this.wantGuard = wantGuard;
229
+ this.clauses.push([this.pos(TRUE_ATOM)]);
230
+ for (let i = 0; i < holds.length; i++) {
231
+ const req = holds[i];
232
+ const body = this.compileBool(req.consequent);
233
+ if (body < 0) return false;
234
+ if (req.antecedent === null) this.clauses.push([body]);
235
+ else {
236
+ const ante = this.compileBool(req.antecedent);
237
+ if (ante < 0) return false;
238
+ this.clauses.push([this.neg(ante), body]);
239
+ }
240
+ }
241
+ if (fails.length > 0) {
242
+ const flags: number[] = [];
243
+ for (let i = 0; i < fails.length; i++) {
244
+ const req = fails[i];
245
+ const body = this.compileBool(req.consequent);
246
+ if (body < 0) return false;
247
+ const flag = this.fresh();
248
+ if (req.antecedent === null) {
249
+ this.clauses.push([this.neg(flag), this.neg(body)]);
250
+ this.clauses.push([flag, body]);
251
+ } else {
252
+ const ante = this.compileBool(req.antecedent);
253
+ if (ante < 0) return false;
254
+ this.clauses.push([this.neg(flag), ante]);
255
+ this.clauses.push([this.neg(flag), this.neg(body)]);
256
+ this.clauses.push([this.neg(ante), body, flag]);
257
+ }
258
+ flags.push(flag);
259
+ }
260
+ this.clauses.push(flags);
261
+ }
262
+ if (wantGuard === 1 && guard !== null) {
263
+ const g = this.compileBool(guard);
264
+ if (g < 0) return false;
265
+ this.clauses.push([g]);
266
+ }
267
+ if (wantGuard === 2 && guard !== null) {
268
+ const g = this.compileBool(guard);
269
+ if (g < 0) return false;
270
+ this.clauses.push([this.neg(g)]);
271
+ }
272
+ this.buildWatches();
273
+ return true;
274
+ }
275
+
276
+ buildWatches(): void {
277
+ let maxLit = 1;
278
+ this.emptyClause = false;
279
+ for (let c = 0; c < this.clauses.length; c++) {
280
+ const clause = this.clauses[c];
281
+ if (clause.length === 0) this.emptyClause = true;
282
+ for (let i = 0; i < clause.length; i++) {
283
+ const lit = clause[i];
284
+ if (lit > maxLit) maxLit = lit;
285
+ const flipped = lit % 2 === 0 ? lit + 1 : lit - 1;
286
+ if (flipped > maxLit) maxLit = flipped;
287
+ }
288
+ }
289
+ this.w1 = [];
290
+ this.w2 = [];
291
+ this.watches = [];
292
+ this.units = [];
293
+ for (let lit = 0; lit <= maxLit; lit++) this.watches.push([]);
294
+ for (let c = 0; c < this.clauses.length; c++) {
295
+ const clause = this.clauses[c];
296
+ if (clause.length === 0) {
297
+ this.w1.push(-1);
298
+ this.w2.push(-1);
299
+ continue;
300
+ }
301
+ const first = clause[0];
302
+ let second = -1;
303
+ for (let i = 1; i < clause.length; i++) {
304
+ if (clause[i] !== first) {
305
+ second = clause[i];
306
+ break;
307
+ }
308
+ }
309
+ this.w1.push(first);
310
+ this.w2.push(second);
311
+ this.watches[first].push(c);
312
+ if (second >= 0) this.watches[second].push(c);
313
+ else this.units.push(first);
314
+ }
315
+ }
316
+
317
+ reset(): void {
318
+ this.trailN = 0;
319
+ this.snapN = 0;
320
+ this.propH = 0;
321
+ this.propN = 0;
322
+ while (this.ineq.length > 0) this.ineq.pop();
323
+ while (this.ne.length > 0) this.ne.pop();
324
+ while (this.val.length < this.atoms.length) this.val.push(-1);
325
+ for (let i = 0; i < this.atoms.length; i++) this.val[i] = -1;
326
+ while (this.lo.length < this.nsym) {
327
+ this.lo.push(0);
328
+ this.hi.push(0);
329
+ }
330
+ for (let i = 0; i < this.nsym; i++) {
331
+ this.lo[i] = this.baseLo[i];
332
+ this.hi[i] = this.baseHi[i];
333
+ }
334
+ this.val[TRUE_ATOM] = 1;
335
+ this.assigned = 1;
336
+ this.ticks = 1;
337
+ }
338
+
339
+ setVal(atom: number, truth: number): void {
340
+ if (this.val[atom] === truth) return;
341
+ this.remember(0, atom, this.val[atom]);
342
+ this.val[atom] = truth;
343
+ this.ticks++;
344
+ }
345
+
346
+ setLo(si: number, value: number): void {
347
+ if (this.lo[si] === value) return;
348
+ this.remember(1, si, this.lo[si]);
349
+ this.lo[si] = value;
350
+ this.ticks++;
351
+ }
352
+
353
+ setHi(si: number, value: number): void {
354
+ if (this.hi[si] === value) return;
355
+ this.remember(2, si, this.hi[si]);
356
+ this.hi[si] = value;
357
+ this.ticks++;
358
+ }
359
+
360
+ remember(kind: number, index: number, prev: number): void {
361
+ const n = this.trailN;
362
+ if (n === this.trailK.length) {
363
+ this.trailK.push(kind);
364
+ this.trailI.push(index);
365
+ this.trailP.push(prev);
366
+ } else {
367
+ this.trailK[n] = kind;
368
+ this.trailI[n] = index;
369
+ this.trailP[n] = prev;
370
+ }
371
+ this.trailN = n + 1;
372
+ }
373
+
374
+ applyPins(pins: Binding, driversOnly: boolean): boolean {
375
+ for (let i = 0; i < this.nsym; i++) {
376
+ const use = driversOnly ? isDriver(this.spec.symbols[i]) : pins.known[i];
377
+ if (!use) continue;
378
+ if (!pins.known[i] && driversOnly) continue;
379
+ const code = pins.code[i];
380
+ if (code < this.lo[i] || code > this.hi[i]) return false;
381
+ this.setLo(i, code);
382
+ this.setHi(i, code);
383
+ if (this.isBool[i]) {
384
+ const lit = code === 1 ? this.pos(this.boolAtom[i]) : this.neg(this.pos(this.boolAtom[i]));
385
+ if (!this.assignLit(lit)) return false;
386
+ }
387
+ }
388
+ return true;
389
+ }
390
+
391
+ search(pos: number, wantMin: boolean): boolean {
392
+ if (!this.propagate()) return false;
393
+ if (pos >= this.nsym) return this.fulfilled();
394
+ if (this.lo[pos] === this.hi[pos]) return this.search(pos + 1, wantMin);
395
+ const lo = this.lo[pos];
396
+ const hi = this.hi[pos];
397
+ if (wantMin && hi - lo > 48 && !this.isBool[pos]) {
398
+ if (!this.anyIn(pos, lo, hi)) return false;
399
+ let low = lo;
400
+ let high = hi;
401
+ while (low < high) {
402
+ const mid = low + Math.floor((high - low) / 2);
403
+ if (this.anyIn(pos, low, mid)) high = mid;
404
+ else low = mid + 1;
405
+ }
406
+ return this.withPoint(pos, low, true);
407
+ }
408
+ if (!wantMin && hi - lo > 48 && !this.isBool[pos]) {
409
+ const mid = lo + Math.floor((hi - lo) / 2);
410
+ if (this.withSpan(pos, lo, mid, false)) return true;
411
+ return this.withSpan(pos, mid + 1, hi, false);
412
+ }
413
+ const step = wantMin ? 1 : -1;
414
+ const start = wantMin ? lo : hi;
415
+ const stop = wantMin ? hi : lo;
416
+ for (let v = start; wantMin ? v <= stop : v >= stop; v += step) {
417
+ if (this.withPoint(pos, v, wantMin)) return true;
418
+ }
419
+ return false;
420
+ }
421
+
422
+ searchBad(pos: number): boolean {
423
+ if (!this.propagate()) {
424
+ this.fillMins(pos);
425
+ return true;
426
+ }
427
+ if (pos >= this.drivers.length) return !this.existsOut();
428
+ const si = this.drivers[pos];
429
+ const domLo = this.baseLo[si];
430
+ const domHi = this.baseHi[si];
431
+ let lo = this.lo[si];
432
+ let hi = this.hi[si];
433
+ if (lo < domLo) lo = domLo;
434
+ if (hi > domHi) hi = domHi;
435
+ if (lo > domLo) {
436
+ this.choiceFill(pos, si, domLo);
437
+ return true;
438
+ }
439
+ if (lo > hi) {
440
+ this.choiceFill(pos, si, domLo);
441
+ return true;
442
+ }
443
+ const span = hi - lo;
444
+ if (span <= 64 || this.isBool[si]) {
445
+ for (let v = lo; v <= hi; v++) {
446
+ if (this.pinBad(pos, si, v)) return true;
447
+ }
448
+ if (hi < domHi) {
449
+ this.choiceFill(pos, si, hi + 1);
450
+ return true;
451
+ }
452
+ return false;
453
+ }
454
+ if (this.uniform(si)) return this.badIntervals(pos, si, lo, hi, domHi);
455
+ if (this.solid(si)) return this.badSolid(pos, si, lo, hi, domHi);
456
+ return this.badSplit(pos, si, lo, hi, domHi);
457
+ }
458
+
459
+ badIntervals(pos: number, si: number, lo: number, hi: number, domHi: number): boolean {
460
+ const pts = this.boundsOf(si, lo, hi);
461
+ for (let i = 0; i < pts.length; i++) {
462
+ const left = pts[i];
463
+ if (this.pinBad(pos, si, left)) return true;
464
+ const right = i + 1 < pts.length ? pts[i + 1] : left;
465
+ if (right > left + 1) {
466
+ if (this.pinBad(pos, si, left + 1)) return true;
467
+ }
468
+ }
469
+ if (hi < domHi) {
470
+ this.choiceFill(pos, si, hi + 1);
471
+ return true;
472
+ }
473
+ return false;
474
+ }
475
+
476
+ badSolid(pos: number, si: number, lo: number, hi: number, domHi: number): boolean {
477
+ if (this.pinBad(pos, si, lo)) return true;
478
+ if (hi < domHi) {
479
+ this.choiceFill(pos, si, hi + 1);
480
+ return true;
481
+ }
482
+ return false;
483
+ }
484
+
485
+ badSplit(pos: number, si: number, lo: number, hi: number, domHi: number): boolean {
486
+ const snap = this.snap();
487
+ if (lo > this.lo[si]) this.setLo(si, lo);
488
+ if (hi < this.hi[si]) this.setHi(si, hi);
489
+ const live = this.propagate();
490
+ this.restore(snap);
491
+ if (!live) {
492
+ this.choiceFill(pos, si, lo);
493
+ return true;
494
+ }
495
+ if (lo === hi) return this.pinBad(pos, si, lo);
496
+ const mid = lo + Math.floor((hi - lo) / 2);
497
+ if (this.badSplit(pos, si, lo, mid, domHi)) return true;
498
+ return this.badSplit(pos, si, mid + 1, hi, domHi);
499
+ }
500
+
501
+ pinBad(pos: number, si: number, value: number): boolean {
502
+ const snap = this.snap();
503
+ if (!this.force(si, value) || !this.propagate()) {
504
+ this.restore(snap);
505
+ this.setLo(si, value);
506
+ this.setHi(si, value);
507
+ this.fillMins(pos + 1);
508
+ return true;
509
+ }
510
+ if (this.searchBad(pos + 1)) {
511
+ this.setLo(si, value);
512
+ this.setHi(si, value);
513
+ return true;
514
+ }
515
+ this.restore(snap);
516
+ return false;
517
+ }
518
+
519
+ choiceFill(pos: number, si: number, value: number): void {
520
+ this.setLo(si, value);
521
+ this.setHi(si, value);
522
+ this.fillMins(pos + 1);
523
+ }
524
+
525
+ fillMins(pos: number): void {
526
+ for (let i = pos; i < this.drivers.length; i++) {
527
+ const si = this.drivers[i];
528
+ this.setLo(si, this.baseLo[si]);
529
+ this.setHi(si, this.baseLo[si]);
530
+ }
531
+ }
532
+
533
+ existsOut(): boolean {
534
+ const snap = this.snap();
535
+ const ok = this.propagate() && !this.blocked() && this.search(0, false);
536
+ this.restore(snap);
537
+ return ok;
538
+ }
539
+
540
+ anyIn(pos: number, lo: number, hi: number): boolean {
541
+ const snap = this.snap();
542
+ if (lo > this.lo[pos]) this.setLo(pos, lo);
543
+ if (hi < this.hi[pos]) this.setHi(pos, hi);
544
+ const ok = this.propagate() && !this.blocked() && this.search(pos, false);
545
+ this.restore(snap);
546
+ return ok;
547
+ }
548
+
549
+ withPoint(pos: number, value: number, wantMin: boolean): boolean {
550
+ const snap = this.snap();
551
+ if (!this.force(pos, value) || !this.search(pos + 1, wantMin)) {
552
+ this.restore(snap);
553
+ return false;
554
+ }
555
+ return true;
556
+ }
557
+
558
+ withSpan(pos: number, lo: number, hi: number, wantMin: boolean): boolean {
559
+ const snap = this.snap();
560
+ if (lo > this.lo[pos]) this.setLo(pos, lo);
561
+ if (hi < this.hi[pos]) this.setHi(pos, hi);
562
+ if (!this.propagate() || this.blocked() || !this.search(pos, wantMin)) {
563
+ this.restore(snap);
564
+ return false;
565
+ }
566
+ return true;
567
+ }
568
+
569
+ force(si: number, value: number): boolean {
570
+ if (value < this.lo[si] || value > this.hi[si]) return false;
571
+ this.setLo(si, value);
572
+ this.setHi(si, value);
573
+ if (this.isBool[si]) {
574
+ const lit = value === 1 ? this.pos(this.boolAtom[si]) : this.neg(this.pos(this.boolAtom[si]));
575
+ return this.assignLit(lit);
576
+ }
577
+ return true;
578
+ }
579
+
580
+ propagate(): boolean {
581
+ if (this.emptyClause) return false;
582
+ for (let i = 0; i < this.units.length; i++) {
583
+ if (!this.assignLit(this.units[i])) return false;
584
+ }
585
+ for (let spin = 0; spin < 10000; spin++) {
586
+ const mark = this.ticks;
587
+ const queued = this.propN;
588
+ if (!this.drain()) return false;
589
+ if (this.ineq.length > 0 || this.ne.length > 0) {
590
+ if (!this.tighten()) return false;
591
+ }
592
+ if (!this.imply()) return false;
593
+ if (this.ticks === mark && this.propN === queued) return true;
594
+ }
595
+ return false;
596
+ }
597
+
598
+ drain(): boolean {
599
+ while (this.propH < this.propN) {
600
+ const lit = this.propQ[this.propH];
601
+ this.propH++;
602
+ if (!this.wake(this.neg(lit))) return false;
603
+ }
604
+ return true;
605
+ }
606
+
607
+ wake(falseLit: number): boolean {
608
+ if (falseLit < 0 || falseLit >= this.watches.length) return true;
609
+ const list = this.watches[falseLit];
610
+ let i = 0;
611
+ while (i < list.length) {
612
+ const ci = list[i];
613
+ const other = this.w1[ci] === falseLit ? this.w2[ci] : this.w1[ci];
614
+ if (other >= 0 && this.litSat(other)) {
615
+ i++;
616
+ continue;
617
+ }
618
+ const repl = this.otherOpen(ci, falseLit, other);
619
+ if (repl >= 0) {
620
+ const last = list.length - 1;
621
+ list[i] = list[last];
622
+ list.pop();
623
+ if (this.w1[ci] === falseLit) this.w1[ci] = repl;
624
+ else this.w2[ci] = repl;
625
+ this.watches[repl].push(ci);
626
+ continue;
627
+ }
628
+ if (other < 0 || this.litFalse(other)) return false;
629
+ if (!this.assignLit(other)) return false;
630
+ i++;
631
+ }
632
+ return true;
633
+ }
634
+
635
+ otherOpen(ci: number, falseLit: number, other: number): number {
636
+ const clause = this.clauses[ci];
637
+ for (let k = 0; k < clause.length; k++) {
638
+ const lit = clause[k];
639
+ if (lit === falseLit || lit === other) continue;
640
+ if (!this.litFalse(lit)) return lit;
641
+ }
642
+ return -1;
643
+ }
644
+
645
+ litSat(lit: number): boolean {
646
+ const want = lit % 2 === 0 ? 1 : 0;
647
+ return this.val[this.atomOf(lit)] === want;
648
+ }
649
+
650
+ litFalse(lit: number): boolean {
651
+ const atom = this.atomOf(lit);
652
+ if (this.val[atom] < 0) return false;
653
+ const want = lit % 2 === 0 ? 1 : 0;
654
+ return this.val[atom] !== want;
655
+ }
656
+
657
+ assignLit(lit: number): boolean {
658
+ const atom = this.atomOf(lit);
659
+ const truth = lit % 2 === 0 ? 1 : 0;
660
+ if (this.val[atom] >= 0) return this.val[atom] === truth;
661
+ this.setVal(atom, truth);
662
+ this.assigned++;
663
+ const info = this.atoms[atom];
664
+ if (info.kind === 0) {
665
+ const bit = truth === 1 ? 1 : 0;
666
+ if (bit < this.lo[info.sym] || bit > this.hi[info.sym]) return false;
667
+ this.setLo(info.sym, bit);
668
+ this.setHi(info.sym, bit);
669
+ } else if (info.kind === 1 && !this.postTheory(info, truth === 1)) return false;
670
+ const n = this.propN;
671
+ if (n === this.propQ.length) this.propQ.push(lit);
672
+ else this.propQ[n] = lit;
673
+ this.propN = n + 1;
674
+ return true;
675
+ }
676
+
677
+ postTheory(info: AtomInfo, truth: boolean): boolean {
678
+ const expr = new Lin();
679
+ expr.vars = info.vars;
680
+ expr.coeffs = info.coeffs;
681
+ expr.constant = info.constant;
682
+ let op = info.op;
683
+ if (!truth) {
684
+ if (op === OP_LE) {
685
+ op = OP_GE;
686
+ expr.constant = expr.constant - 1;
687
+ } else if (op === OP_GE) {
688
+ op = OP_LE;
689
+ expr.constant = expr.constant + 1;
690
+ } else if (op === OP_EQ) op = OP_NE;
691
+ else op = OP_EQ;
692
+ }
693
+ if (expr.vars.length === 0) {
694
+ if (this.constHolds(expr.constant, op)) return true;
695
+ return false;
696
+ }
697
+ if (op === OP_EQ) {
698
+ if (!this.pushLe(expr, 0)) return false;
699
+ return this.pushLe(this.scaled(expr, -1), 0);
700
+ }
701
+ if (op === OP_NE) {
702
+ const row = new Diseq();
703
+ row.vars = expr.vars;
704
+ row.coeffs = expr.coeffs;
705
+ row.rhs = -expr.constant;
706
+ this.ne.push(row);
707
+ return true;
708
+ }
709
+ if (op === OP_GE) return this.pushLe(this.scaled(expr, -1), 0);
710
+ return this.pushLe(expr, 0);
711
+ }
712
+
713
+ pushLe(expr: Lin, slack: number): boolean {
714
+ const row = new Ineq();
715
+ for (let i = 0; i < expr.vars.length; i++) {
716
+ if (expr.coeffs[i] === 0) continue;
717
+ row.vars.push(expr.vars[i]);
718
+ row.coeffs.push(expr.coeffs[i]);
719
+ }
720
+ row.rhs = -expr.constant + slack;
721
+ this.ineq.push(row);
722
+ return true;
723
+ }
724
+
725
+ tighten(): boolean {
726
+ for (let spin = 0; spin < this.nsym + this.ineq.length + 4; spin++) {
727
+ const mark = this.ticks;
728
+ for (let i = 0; i < this.ineq.length; i++) {
729
+ if (!this.tightenOne(this.ineq[i])) return false;
730
+ }
731
+ for (let i = 0; i < this.ne.length; i++) {
732
+ if (!this.tightenNe(this.ne[i])) return false;
733
+ }
734
+ if (this.ticks === mark) return true;
735
+ }
736
+ return true;
737
+ }
738
+
739
+ tightenOne(row: Ineq): boolean {
740
+ for (let i = 0; i < row.vars.length; i++) {
741
+ const si = row.vars[i];
742
+ const a = row.coeffs[i];
743
+ if (a === 0) continue;
744
+ let othersMin = 0;
745
+ for (let j = 0; j < row.vars.length; j++) {
746
+ if (j === i) continue;
747
+ const b = row.coeffs[j];
748
+ const sj = row.vars[j];
749
+ if (b > 0) othersMin += b * this.lo[sj];
750
+ else othersMin += b * this.hi[sj];
751
+ }
752
+ const room = row.rhs - othersMin;
753
+ if (a > 0) {
754
+ const cap = Math.floor(room / a);
755
+ if (cap < this.hi[si]) this.setHi(si, cap);
756
+ } else {
757
+ const floor = Math.ceil(room / a);
758
+ if (floor > this.lo[si]) this.setLo(si, floor);
759
+ }
760
+ if (this.lo[si] > this.hi[si]) return false;
761
+ }
762
+ return true;
763
+ }
764
+
765
+ tightenNe(row: Diseq): boolean {
766
+ let open = -1;
767
+ let value = row.rhs;
768
+ for (let i = 0; i < row.vars.length; i++) {
769
+ const si = row.vars[i];
770
+ const a = row.coeffs[i];
771
+ if (this.lo[si] !== this.hi[si]) {
772
+ if (open >= 0) return true;
773
+ open = i;
774
+ continue;
775
+ }
776
+ value -= a * this.lo[si];
777
+ }
778
+ if (open < 0) return value !== 0;
779
+ const si = row.vars[open];
780
+ const a = row.coeffs[open];
781
+ if (a === 0) return value !== 0;
782
+ if (value % a !== 0) return true;
783
+ const ban = value / a;
784
+ if (ban < this.lo[si] || ban > this.hi[si]) return true;
785
+ if (this.lo[si] === this.hi[si]) return false;
786
+ if (this.lo[si] === ban) this.setLo(si, ban + 1);
787
+ else if (this.hi[si] === ban) this.setHi(si, ban - 1);
788
+ return this.lo[si] <= this.hi[si];
789
+ }
790
+
791
+ imply(): boolean {
792
+ for (let a = 0; a < this.atoms.length; a++) {
793
+ if (this.atoms[a].kind !== 1 || this.val[a] >= 0) continue;
794
+ const forced = this.forced(this.atoms[a]);
795
+ if (forced === 1) {
796
+ if (!this.assignLit(this.pos(a))) return false;
797
+ } else if (forced === 0) {
798
+ if (!this.assignLit(this.neg(this.pos(a)))) return false;
799
+ }
800
+ }
801
+ for (let i = 0; i < this.nsym; i++) {
802
+ if (!this.isBool[i] || this.boolAtom[i] < 0) continue;
803
+ if (this.val[this.boolAtom[i]] >= 0) continue;
804
+ if (this.lo[i] === this.hi[i]) {
805
+ const lit = this.lo[i] === 1 ? this.pos(this.boolAtom[i]) : this.neg(this.pos(this.boolAtom[i]));
806
+ if (!this.assignLit(lit)) return false;
807
+ }
808
+ }
809
+ return true;
810
+ }
811
+
812
+ forced(info: AtomInfo): number {
813
+ let low = info.constant;
814
+ let high = info.constant;
815
+ for (let i = 0; i < info.vars.length; i++) {
816
+ const a = info.coeffs[i];
817
+ const si = info.vars[i];
818
+ if (a > 0) {
819
+ low += a * this.lo[si];
820
+ high += a * this.hi[si];
821
+ } else {
822
+ low += a * this.hi[si];
823
+ high += a * this.lo[si];
824
+ }
825
+ }
826
+ if (info.op === OP_LE) {
827
+ if (high <= 0) return 1;
828
+ if (low > 0) return 0;
829
+ return -1;
830
+ }
831
+ if (info.op === OP_GE) {
832
+ if (low >= 0) return 1;
833
+ if (high < 0) return 0;
834
+ return -1;
835
+ }
836
+ if (info.op === OP_EQ) {
837
+ if (low === 0 && high === 0) return 1;
838
+ if (low > 0 || high < 0) return 0;
839
+ return -1;
840
+ }
841
+ if (low === 0 && high === 0) return 0;
842
+ if (low > 0 || high < 0) return 1;
843
+ return -1;
844
+ }
845
+
846
+ blocked(): boolean {
847
+ const env = this.toBinding(false);
848
+ for (let i = 0; i < this.holds.length; i++) {
849
+ const req = this.holds[i];
850
+ if (evalAssertion(req.antecedent, req.consequent, this.spec, env) === T_FALSE) return true;
851
+ }
852
+ if (this.fails.length === 0) return false;
853
+ let open = false;
854
+ for (let i = 0; i < this.fails.length; i++) {
855
+ const req = this.fails[i];
856
+ const value = evalAssertion(req.antecedent, req.consequent, this.spec, env);
857
+ if (value !== T_TRUE) open = true;
858
+ }
859
+ return !open;
860
+ }
861
+
862
+ fulfilled(): boolean {
863
+ const env = this.toBinding(true);
864
+ for (let i = 0; i < this.holds.length; i++) {
865
+ const req = this.holds[i];
866
+ if (evalAssertion(req.antecedent, req.consequent, this.spec, env) !== T_TRUE) return false;
867
+ }
868
+ if (this.wantGuard === 1 && this.guard !== null && evalBool(this.guard, this.spec, env) !== T_TRUE) return false;
869
+ if (this.wantGuard === 2 && (this.guard === null || evalBool(this.guard, this.spec, env) !== T_FALSE)) return false;
870
+ if (this.fails.length === 0) return true;
871
+ for (let i = 0; i < this.fails.length; i++) {
872
+ const req = this.fails[i];
873
+ if (evalAssertion(req.antecedent, req.consequent, this.spec, env) === T_FALSE) return true;
874
+ }
875
+ return false;
876
+ }
877
+
878
+ toBinding(fill: boolean): Binding {
879
+ const env = this.scratch;
880
+ for (let i = 0; i < this.nsym; i++) {
881
+ if (this.lo[i] === this.hi[i]) {
882
+ env.known[i] = true;
883
+ env.code[i] = this.lo[i];
884
+ } else if (fill) {
885
+ env.known[i] = true;
886
+ env.code[i] = this.lo[i];
887
+ } else {
888
+ env.known[i] = false;
889
+ }
890
+ }
891
+ return env;
892
+ }
893
+
894
+ driverBinding(): Binding {
895
+ const env = new Binding(this.nsym);
896
+ for (let i = 0; i < this.drivers.length; i++) {
897
+ const si = this.drivers[i];
898
+ env.known[si] = true;
899
+ if (si < this.lo.length && this.lo[si] === this.hi[si]) env.code[si] = this.lo[si];
900
+ else env.code[si] = this.baseLo[si];
901
+ }
902
+ return env;
903
+ }
904
+
905
+ uniform(si: number): boolean {
906
+ for (let a = 0; a < this.atoms.length; a++) {
907
+ const info = this.atoms[a];
908
+ if (info.kind !== 1) continue;
909
+ if (this.coupled(info.vars, info.coeffs, si)) return false;
910
+ }
911
+ for (let i = 0; i < this.ne.length; i++) {
912
+ if (this.coupled(this.ne[i].vars, this.ne[i].coeffs, si)) return false;
913
+ }
914
+ return true;
915
+ }
916
+
917
+ coupled(vars: number[], coeffs: number[], si: number): boolean {
918
+ let hit = false;
919
+ let open = false;
920
+ for (let i = 0; i < vars.length; i++) {
921
+ if (coeffs[i] === 0) continue;
922
+ if (vars[i] === si) hit = true;
923
+ else if (this.lo[vars[i]] !== this.hi[vars[i]]) open = true;
924
+ }
925
+ return hit && open;
926
+ }
927
+
928
+ solid(si: number): boolean {
929
+ for (let a = 0; a < this.atoms.length; a++) {
930
+ const info = this.atoms[a];
931
+ if (info.kind !== 1) continue;
932
+ if (!this.atomMentions(info, si)) continue;
933
+ if (this.val[a] < 0) return false;
934
+ for (let i = 0; i < info.coeffs.length; i++) {
935
+ const c = info.coeffs[i];
936
+ if (c !== 1 && c !== -1) return false;
937
+ }
938
+ }
939
+ for (let i = 0; i < this.ne.length; i++) {
940
+ if (this.rowMentions(this.ne[i].vars, this.ne[i].coeffs, si)) return false;
941
+ }
942
+ return true;
943
+ }
944
+
945
+ atomMentions(info: AtomInfo, si: number): boolean {
946
+ return this.rowMentions(info.vars, info.coeffs, si);
947
+ }
948
+
949
+ rowMentions(vars: number[], coeffs: number[], si: number): boolean {
950
+ for (let i = 0; i < vars.length; i++) {
951
+ if (vars[i] === si && coeffs[i] !== 0) return true;
952
+ }
953
+ return false;
954
+ }
955
+
956
+ boundsOf(si: number, lo: number, hi: number): number[] {
957
+ const pts: number[] = [];
958
+ pts.push(lo);
959
+ pts.push(hi);
960
+ for (let a = 0; a < this.atoms.length; a++) {
961
+ const info = this.atoms[a];
962
+ if (info.kind !== 1) continue;
963
+ const root = this.isolate(info.vars, info.coeffs, info.constant, si);
964
+ if (root === null) continue;
965
+ this.addRoot(pts, root, lo, hi);
966
+ }
967
+ for (let i = 0; i < this.ne.length; i++) {
968
+ const row = this.ne[i];
969
+ const root = this.isolate(row.vars, row.coeffs, -row.rhs, si);
970
+ if (root === null) continue;
971
+ this.addRoot(pts, root, lo, hi);
972
+ }
973
+ return this.sortUnique(pts);
974
+ }
975
+
976
+ isolate(vars: number[], coeffs: number[], constant: number, si: number): number | null {
977
+ let coeff = 0;
978
+ let rest = constant;
979
+ for (let i = 0; i < vars.length; i++) {
980
+ if (coeffs[i] === 0) continue;
981
+ if (vars[i] === si) coeff = coeffs[i];
982
+ else if (this.lo[vars[i]] !== this.hi[vars[i]]) return null;
983
+ else rest += coeffs[i] * this.lo[vars[i]];
984
+ }
985
+ if (coeff === 0) return null;
986
+ return -rest / coeff;
987
+ }
988
+
989
+ addRoot(pts: number[], root: number, lo: number, hi: number): void {
990
+ const base = Math.floor(root);
991
+ this.pushPoint(pts, base - 1, lo, hi);
992
+ this.pushPoint(pts, base, lo, hi);
993
+ this.pushPoint(pts, base + 1, lo, hi);
994
+ this.pushPoint(pts, Math.ceil(root), lo, hi);
995
+ }
996
+
997
+ sortUnique(pts: number[]): number[] {
998
+ for (let i = 0; i < pts.length; i++) {
999
+ for (let j = i + 1; j < pts.length; j++) {
1000
+ if (pts[j] < pts[i]) {
1001
+ const tmp = pts[i];
1002
+ pts[i] = pts[j];
1003
+ pts[j] = tmp;
1004
+ }
1005
+ }
1006
+ }
1007
+ const out: number[] = [];
1008
+ for (let i = 0; i < pts.length; i++) {
1009
+ if (i === 0 || pts[i] !== pts[i - 1]) out.push(pts[i]);
1010
+ }
1011
+ return out;
1012
+ }
1013
+
1014
+ pushPoint(pts: number[], value: number, lo: number, hi: number): void {
1015
+ if (value >= lo && value <= hi) pts.push(value);
1016
+ }
1017
+
1018
+ compileBool(term: Term): number {
1019
+ if (term.kind === "bool") return term.value ? this.pos(TRUE_ATOM) : this.neg(this.pos(TRUE_ATOM));
1020
+ if (term.kind === "sym") {
1021
+ if (this.isBool[term.index]) return this.pos(this.boolAtom[term.index]);
1022
+ return this.pos(TRUE_ATOM);
1023
+ }
1024
+ if (term.kind === "not") {
1025
+ const inner = this.compileBool(term.term);
1026
+ if (inner < 0) return -1;
1027
+ return this.neg(inner);
1028
+ }
1029
+ if (term.kind === "and" || term.kind === "or") {
1030
+ const left = this.compileBool(term.left);
1031
+ const right = this.compileBool(term.right);
1032
+ if (left < 0 || right < 0) return -1;
1033
+ const t = this.fresh();
1034
+ if (term.kind === "and") {
1035
+ this.clauses.push([this.neg(t), left]);
1036
+ this.clauses.push([this.neg(t), right]);
1037
+ this.clauses.push([this.neg(left), this.neg(right), t]);
1038
+ } else {
1039
+ this.clauses.push([this.neg(t), left, right]);
1040
+ this.clauses.push([this.neg(left), t]);
1041
+ this.clauses.push([this.neg(right), t]);
1042
+ }
1043
+ return t;
1044
+ }
1045
+ if (
1046
+ term.kind === "eq" ||
1047
+ term.kind === "neq" ||
1048
+ term.kind === "lt" ||
1049
+ term.kind === "le" ||
1050
+ term.kind === "gt" ||
1051
+ term.kind === "ge"
1052
+ ) {
1053
+ if (this.boolCmp(term.left) && this.boolCmp(term.right) && (term.kind === "eq" || term.kind === "neq")) {
1054
+ return this.compileBoolEq(term.left, term.right, term.kind === "eq");
1055
+ }
1056
+ return this.compileCmp(term);
1057
+ }
1058
+ return this.pos(TRUE_ATOM);
1059
+ }
1060
+
1061
+ boolCmp(term: Term): boolean {
1062
+ if (term.kind === "bool") return true;
1063
+ if (term.kind === "sym") return this.isBool[term.index];
1064
+ if (term.kind === "not") return this.boolCmp(term.term);
1065
+ return term.kind === "and" || term.kind === "or";
1066
+ }
1067
+
1068
+ compileBoolEq(left: Term, right: Term, same: boolean): number {
1069
+ const a = this.compileBool(left);
1070
+ const b = this.compileBool(right);
1071
+ if (a < 0 || b < 0) return -1;
1072
+ const t = this.fresh();
1073
+ if (same) {
1074
+ this.clauses.push([this.neg(t), this.neg(a), b]);
1075
+ this.clauses.push([this.neg(t), a, this.neg(b)]);
1076
+ this.clauses.push([t, a, b]);
1077
+ this.clauses.push([t, this.neg(a), this.neg(b)]);
1078
+ } else {
1079
+ this.clauses.push([this.neg(t), a, b]);
1080
+ this.clauses.push([this.neg(t), this.neg(a), this.neg(b)]);
1081
+ this.clauses.push([t, this.neg(a), b]);
1082
+ this.clauses.push([t, a, this.neg(b)]);
1083
+ }
1084
+ return t;
1085
+ }
1086
+
1087
+ compileCmp(term: Term): number {
1088
+ if (
1089
+ term.kind !== "eq" &&
1090
+ term.kind !== "neq" &&
1091
+ term.kind !== "lt" &&
1092
+ term.kind !== "le" &&
1093
+ term.kind !== "gt" &&
1094
+ term.kind !== "ge"
1095
+ ) {
1096
+ return -1;
1097
+ }
1098
+ const left = this.linOf(term.left);
1099
+ const right = this.linOf(term.right);
1100
+ if (left === null || right === null) return this.neg(this.pos(TRUE_ATOM));
1101
+ const expr = new Lin();
1102
+ this.addLin(expr, left, 1);
1103
+ this.addLin(expr, right, -1);
1104
+ let op = OP_EQ;
1105
+ if (term.kind === "neq") op = OP_NE;
1106
+ if (term.kind === "lt") {
1107
+ op = OP_LE;
1108
+ expr.constant = expr.constant + 1;
1109
+ }
1110
+ if (term.kind === "le") op = OP_LE;
1111
+ if (term.kind === "gt") {
1112
+ op = OP_GE;
1113
+ expr.constant = expr.constant - 1;
1114
+ }
1115
+ if (term.kind === "ge") op = OP_GE;
1116
+ if (expr.vars.length === 0) {
1117
+ if (this.constHolds(expr.constant, op)) return this.pos(TRUE_ATOM);
1118
+ return this.neg(this.pos(TRUE_ATOM));
1119
+ }
1120
+ const info = new AtomInfo();
1121
+ info.kind = 1;
1122
+ info.vars = expr.vars;
1123
+ info.coeffs = expr.coeffs;
1124
+ info.constant = expr.constant;
1125
+ info.op = op;
1126
+ const id = this.atoms.length;
1127
+ this.atoms.push(info);
1128
+ this.val.push(-1);
1129
+ return this.pos(id);
1130
+ }
1131
+
1132
+ constHolds(constant: number, op: number): boolean {
1133
+ if (op === OP_LE) return constant <= 0;
1134
+ if (op === OP_GE) return constant >= 0;
1135
+ if (op === OP_EQ) return constant === 0;
1136
+ return constant !== 0;
1137
+ }
1138
+
1139
+ linOf(term: Term): Lin | null {
1140
+ if (term.kind === "int") {
1141
+ const lin = new Lin();
1142
+ lin.constant = term.value;
1143
+ return lin;
1144
+ }
1145
+ if (term.kind === "enum") {
1146
+ const lin = new Lin();
1147
+ lin.constant = term.index;
1148
+ return lin;
1149
+ }
1150
+ if (term.kind === "sym") {
1151
+ if (this.isBool[term.index]) return null;
1152
+ const lin = new Lin();
1153
+ lin.vars.push(term.index);
1154
+ lin.coeffs.push(1);
1155
+ return lin;
1156
+ }
1157
+ if (term.kind === "add" || term.kind === "sub") {
1158
+ const left = this.linOf(term.left);
1159
+ const right = this.linOf(term.right);
1160
+ if (left === null || right === null) return null;
1161
+ const lin = new Lin();
1162
+ this.addLin(lin, left, 1);
1163
+ this.addLin(lin, right, term.kind === "add" ? 1 : -1);
1164
+ return lin;
1165
+ }
1166
+ return null;
1167
+ }
1168
+
1169
+ addLin(into: Lin, extra: Lin, sign: number): void {
1170
+ into.constant += sign * extra.constant;
1171
+ for (let i = 0; i < extra.vars.length; i++) {
1172
+ const v = extra.vars[i];
1173
+ const c = sign * extra.coeffs[i];
1174
+ let found = -1;
1175
+ for (let j = 0; j < into.vars.length; j++) {
1176
+ if (into.vars[j] === v) found = j;
1177
+ }
1178
+ if (found < 0) {
1179
+ if (c !== 0) {
1180
+ into.vars.push(v);
1181
+ into.coeffs.push(c);
1182
+ }
1183
+ } else {
1184
+ into.coeffs[found] += c;
1185
+ if (into.coeffs[found] === 0) {
1186
+ const last = into.vars.length - 1;
1187
+ into.vars[found] = into.vars[last];
1188
+ into.coeffs[found] = into.coeffs[last];
1189
+ into.vars.pop();
1190
+ into.coeffs.pop();
1191
+ }
1192
+ }
1193
+ }
1194
+ }
1195
+
1196
+ scaled(expr: Lin, sign: number): Lin {
1197
+ const out = new Lin();
1198
+ this.addLin(out, expr, sign);
1199
+ return out;
1200
+ }
1201
+
1202
+ fresh(): number {
1203
+ const info = new AtomInfo();
1204
+ info.kind = 2;
1205
+ const id = this.atoms.length;
1206
+ this.atoms.push(info);
1207
+ this.val.push(-1);
1208
+ return this.pos(id);
1209
+ }
1210
+
1211
+ pos(atom: number): number {
1212
+ return atom * 2;
1213
+ }
1214
+
1215
+ neg(litOrAtom: number): number {
1216
+ if (litOrAtom % 2 === 0) return litOrAtom + 1;
1217
+ return litOrAtom - 1;
1218
+ }
1219
+
1220
+ atomOf(lit: number): number {
1221
+ return Math.floor(lit / 2);
1222
+ }
1223
+
1224
+ snap(): number {
1225
+ const n = this.snapN;
1226
+ const base = n * 7;
1227
+ if (base === this.snapStack.length) {
1228
+ this.snapStack.push(this.trailN);
1229
+ this.snapStack.push(this.ineq.length);
1230
+ this.snapStack.push(this.ne.length);
1231
+ this.snapStack.push(this.assigned);
1232
+ this.snapStack.push(this.ticks);
1233
+ this.snapStack.push(this.propH);
1234
+ this.snapStack.push(this.propN);
1235
+ } else {
1236
+ this.snapStack[base] = this.trailN;
1237
+ this.snapStack[base + 1] = this.ineq.length;
1238
+ this.snapStack[base + 2] = this.ne.length;
1239
+ this.snapStack[base + 3] = this.assigned;
1240
+ this.snapStack[base + 4] = this.ticks;
1241
+ this.snapStack[base + 5] = this.propH;
1242
+ this.snapStack[base + 6] = this.propN;
1243
+ }
1244
+ this.snapN = n + 1;
1245
+ return n;
1246
+ }
1247
+
1248
+ restore(level: number): void {
1249
+ const base = level * 7;
1250
+ const trail = this.snapStack[base];
1251
+ const ineqLen = this.snapStack[base + 1];
1252
+ const neLen = this.snapStack[base + 2];
1253
+ while (this.trailN > trail) {
1254
+ this.trailN--;
1255
+ const kind = this.trailK[this.trailN];
1256
+ const index = this.trailI[this.trailN];
1257
+ const prev = this.trailP[this.trailN];
1258
+ if (kind === 0) this.val[index] = prev;
1259
+ else if (kind === 1) this.lo[index] = prev;
1260
+ else this.hi[index] = prev;
1261
+ }
1262
+ while (this.ineq.length > ineqLen) this.ineq.pop();
1263
+ while (this.ne.length > neLen) this.ne.pop();
1264
+ this.assigned = this.snapStack[base + 3];
1265
+ this.ticks = this.snapStack[base + 4];
1266
+ this.propH = this.snapStack[base + 5];
1267
+ this.propN = this.snapStack[base + 6];
1268
+ this.snapN = level;
1269
+ }
1270
+ }
1271
+
1272
+ /**
1273
+ * One compile of a requirement set, reused across driver situations.
1274
+ * Clauses stay; each query resets bounds and boolean assignments.
1275
+ */
1276
+ export class TheoryQuery {
1277
+ engine: Engine;
1278
+ ok: boolean;
1279
+
1280
+ constructor(spec: Spec, holds: Requirement[]) {
1281
+ this.engine = new Engine(spec);
1282
+ this.ok = this.engine.compile(holds, [], null, 0);
1283
+ }
1284
+
1285
+ hasModel(): boolean {
1286
+ if (!this.ok) return false;
1287
+ this.engine.reset();
1288
+ if (!this.engine.propagate()) return false;
1289
+ if (this.engine.blocked()) return false;
1290
+ return this.engine.search(0, false);
1291
+ }
1292
+
1293
+ pinnedSat(drivers: Binding): boolean {
1294
+ if (!this.ok) return false;
1295
+ this.engine.reset();
1296
+ if (!this.engine.applyPins(drivers, true)) return false;
1297
+ if (!this.engine.propagate()) return false;
1298
+ if (this.engine.blocked()) return false;
1299
+ return this.engine.search(0, false);
1300
+ }
1301
+
1302
+ badDrivers(): Binding | null {
1303
+ if (!this.ok) {
1304
+ this.engine.reset();
1305
+ return this.engine.driverBinding();
1306
+ }
1307
+ this.engine.reset();
1308
+ if (!this.engine.propagate()) return this.engine.driverBinding();
1309
+ if (!this.engine.searchBad(0)) return null;
1310
+ return this.engine.driverBinding();
1311
+ }
1312
+ }
1313
+
1314
+ export function driverWide(spec: Spec): boolean {
1315
+ let product = 1;
1316
+ for (let i = 0; i < spec.symbols.length; i++) {
1317
+ if (!isDriver(spec.symbols[i])) continue;
1318
+ const size = domainSize(spec, spec.symbols[i]);
1319
+ if (size > 64) return true;
1320
+ if (product > 8192 / size) return true;
1321
+ product = product * size;
1322
+ }
1323
+ return false;
1324
+ }