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.
- package/.github/workflows/ci.yml +48 -0
- package/README.md +1431 -2
- package/assets/icon.png +0 -0
- package/assets/logo-mark.png +0 -0
- package/assets/logo.png +0 -0
- package/examples/checkout.ears.md +74 -0
- package/examples/order-always.ears.md +3 -0
- package/examples/order-awkward.ears.md +8 -0
- package/examples/order-bad.json +1 -0
- package/examples/order-chain.ears.md +9 -0
- package/examples/order-chosen.ears.md +7 -0
- package/examples/order-clear.ears.md +8 -0
- package/examples/order-combined.ears.md +10 -0
- package/examples/order-conflict.ears.md +8 -0
- package/examples/order-covered.ears.md +9 -0
- package/examples/order-duplicate.ears.md +8 -0
- package/examples/order-fail.json +5 -0
- package/examples/order-misuse.ears.md +7 -0
- package/examples/order-negation.ears.md +7 -0
- package/examples/order-pass.json +5 -0
- package/examples/order-quantity.ears.md +8 -0
- package/examples/order-redundant.ears.md +9 -0
- package/examples/order-repair.ears.md +9 -0
- package/examples/order-repeat.ears.md +13 -0
- package/examples/order-same.ears.md +3 -0
- package/examples/order-status.ears.md +7 -0
- package/examples/order-unanswered.ears.md +8 -0
- package/examples/order-unhandled.ears.md +8 -0
- package/examples/order-unmeasured.ears.md +10 -0
- package/examples/order-unused.ears.md +13 -0
- package/examples/order-vacuous.ears.md +9 -0
- package/examples/order-vague.ears.md +7 -0
- package/examples/order-wide.ears.md +5 -0
- package/examples/order-wording.ears.md +8 -0
- package/examples/order.ears.md +7 -0
- package/examples/payment-if.ears.md +7 -0
- package/examples/plan-to-problems/README.md +54 -0
- package/examples/plan-to-problems/expected-problems.md +25 -0
- package/examples/plan-to-problems/generate.mjs +186 -0
- package/examples/plan-to-problems/generated.ears.md +27 -0
- package/examples/plan-to-problems/lint.json +576 -0
- package/examples/plan-to-problems/plan.md +5 -0
- package/examples/plan-to-problems/planted.txt +5 -0
- package/examples/plan-to-problems/problems.md +112 -0
- package/examples/status-placed.ears.md +3 -0
- package/package.json +25 -4
- package/scripts/install-scriptc.sh +16 -0
- package/skills/plan-verify/SKILL.md +1515 -0
- package/src/check.ts +250 -0
- package/src/decide.ts +1324 -0
- package/src/ears.ts +1520 -0
- package/src/jsonvalue.ts +228 -0
- package/src/lint.ts +1245 -0
- package/src/main.ts +273 -0
- package/src/policy.ts +148 -0
- package/src/repair.ts +567 -0
- package/src/report.ts +329 -0
- package/src/scenarios.ts +149 -0
- package/src/smt.ts +97 -0
- package/src/solver.ts +868 -0
- package/src/spec.ts +307 -0
- package/src/term.ts +331 -0
- package/src/text.ts +95 -0
- package/test/fixtures/malformed.ears.md +3 -0
- package/test/fixtures/unknown-phrase.ears.md +5 -0
- package/test/golden/check-fail-json.exit +1 -0
- package/test/golden/check-fail-json.stderr +0 -0
- package/test/golden/check-fail-json.stdout +13 -0
- package/test/golden/check-fail.exit +1 -0
- package/test/golden/check-fail.stderr +0 -0
- package/test/golden/check-fail.stdout +7 -0
- package/test/golden/check-pass-json.exit +1 -0
- package/test/golden/check-pass-json.stderr +0 -0
- package/test/golden/check-pass-json.stdout +6 -0
- package/test/golden/check-pass.exit +1 -0
- package/test/golden/check-pass.stderr +0 -0
- package/test/golden/check-pass.stdout +3 -0
- package/test/golden/emit-smt-json-usage.exit +1 -0
- package/test/golden/emit-smt-json-usage.stderr +0 -0
- package/test/golden/emit-smt-json-usage.stdout +4 -0
- package/test/golden/emit-smt-json.exit +1 -0
- package/test/golden/emit-smt-json.stderr +0 -0
- package/test/golden/emit-smt-json.stdout +5 -0
- package/test/golden/emit-smt.exit +1 -0
- package/test/golden/emit-smt.stderr +0 -0
- package/test/golden/emit-smt.stdout +10 -0
- package/test/golden/help.exit +1 -0
- package/test/golden/help.stderr +0 -0
- package/test/golden/help.stdout +8 -0
- package/test/golden/lint-awkward.exit +1 -0
- package/test/golden/lint-awkward.stderr +0 -0
- package/test/golden/lint-awkward.stdout +29 -0
- package/test/golden/lint-chain.exit +1 -0
- package/test/golden/lint-chain.stderr +0 -0
- package/test/golden/lint-chain.stdout +56 -0
- package/test/golden/lint-checkout.exit +1 -0
- package/test/golden/lint-checkout.stderr +0 -0
- package/test/golden/lint-checkout.stdout +301 -0
- package/test/golden/lint-clean.exit +1 -0
- package/test/golden/lint-clean.stderr +0 -0
- package/test/golden/lint-clean.stdout +9 -0
- package/test/golden/lint-clear.exit +1 -0
- package/test/golden/lint-clear.stderr +0 -0
- package/test/golden/lint-clear.stdout +9 -0
- package/test/golden/lint-conflict-json.exit +1 -0
- package/test/golden/lint-conflict-json.stderr +0 -0
- package/test/golden/lint-conflict-json.stdout +80 -0
- package/test/golden/lint-conflict.exit +1 -0
- package/test/golden/lint-conflict.stderr +0 -0
- package/test/golden/lint-conflict.stdout +41 -0
- package/test/golden/lint-duplicate.exit +1 -0
- package/test/golden/lint-duplicate.stderr +0 -0
- package/test/golden/lint-duplicate.stdout +14 -0
- package/test/golden/lint-gap.exit +1 -0
- package/test/golden/lint-gap.stderr +0 -0
- package/test/golden/lint-gap.stdout +12 -0
- package/test/golden/lint-misuse.exit +1 -0
- package/test/golden/lint-misuse.stderr +0 -0
- package/test/golden/lint-misuse.stdout +18 -0
- package/test/golden/lint-negation.exit +1 -0
- package/test/golden/lint-negation.stderr +0 -0
- package/test/golden/lint-negation.stdout +13 -0
- package/test/golden/lint-quantity.exit +1 -0
- package/test/golden/lint-quantity.stderr +0 -0
- package/test/golden/lint-quantity.stdout +41 -0
- package/test/golden/lint-redundant.exit +1 -0
- package/test/golden/lint-redundant.stderr +0 -0
- package/test/golden/lint-redundant.stdout +14 -0
- package/test/golden/lint-repair-json.exit +1 -0
- package/test/golden/lint-repair-json.stderr +0 -0
- package/test/golden/lint-repair-json.stdout +127 -0
- package/test/golden/lint-repair.exit +1 -0
- package/test/golden/lint-repair.stderr +0 -0
- package/test/golden/lint-repair.stdout +54 -0
- package/test/golden/lint-repeat.exit +1 -0
- package/test/golden/lint-repeat.stderr +0 -0
- package/test/golden/lint-repeat.stdout +48 -0
- package/test/golden/lint-status.exit +1 -0
- package/test/golden/lint-status.stderr +0 -0
- package/test/golden/lint-status.stdout +44 -0
- package/test/golden/lint-unanswered.exit +1 -0
- package/test/golden/lint-unanswered.stderr +0 -0
- package/test/golden/lint-unanswered.stdout +12 -0
- package/test/golden/lint-unhandled.exit +1 -0
- package/test/golden/lint-unhandled.stderr +0 -0
- package/test/golden/lint-unhandled.stdout +12 -0
- package/test/golden/lint-unmeasured.exit +1 -0
- package/test/golden/lint-unmeasured.stderr +0 -0
- package/test/golden/lint-unmeasured.stdout +13 -0
- package/test/golden/lint-unused-json.exit +1 -0
- package/test/golden/lint-unused-json.stderr +0 -0
- package/test/golden/lint-unused-json.stdout +37 -0
- package/test/golden/lint-unused.exit +1 -0
- package/test/golden/lint-unused.stderr +0 -0
- package/test/golden/lint-unused.stdout +25 -0
- package/test/golden/lint-vacuous-json.exit +1 -0
- package/test/golden/lint-vacuous-json.stderr +0 -0
- package/test/golden/lint-vacuous-json.stdout +27 -0
- package/test/golden/lint-vacuous.exit +1 -0
- package/test/golden/lint-vacuous.stderr +0 -0
- package/test/golden/lint-vacuous.stdout +19 -0
- package/test/golden/lint-vague.exit +1 -0
- package/test/golden/lint-vague.stderr +0 -0
- package/test/golden/lint-vague.stdout +21 -0
- package/test/golden/lint-wide.exit +1 -0
- package/test/golden/lint-wide.stderr +0 -0
- package/test/golden/lint-wide.stdout +9 -0
- package/test/golden/lint-wording.exit +1 -0
- package/test/golden/lint-wording.stderr +0 -0
- package/test/golden/lint-wording.stdout +21 -0
- package/test/golden/malformed-scenario.exit +1 -0
- package/test/golden/malformed-scenario.stderr +1 -0
- package/test/golden/malformed-scenario.stdout +0 -0
- package/test/golden/malformed-spec.exit +1 -0
- package/test/golden/malformed-spec.stderr +1 -0
- package/test/golden/malformed-spec.stdout +0 -0
- package/test/golden/policy-counterexample.exit +1 -0
- package/test/golden/policy-counterexample.stderr +0 -0
- package/test/golden/policy-counterexample.stdout +13 -0
- package/test/golden/policy-covered.exit +1 -0
- package/test/golden/policy-covered.stderr +0 -0
- package/test/golden/policy-covered.stdout +19 -0
- package/test/golden/policy-holds.exit +1 -0
- package/test/golden/policy-holds.stderr +0 -0
- package/test/golden/policy-holds.stdout +3 -0
- package/test/golden/policy-json.exit +1 -0
- package/test/golden/policy-json.stderr +0 -0
- package/test/golden/policy-json.stdout +34 -0
- package/test/golden/policy-status.exit +1 -0
- package/test/golden/policy-status.stderr +0 -0
- package/test/golden/policy-status.stdout +16 -0
- package/test/golden/policy-vacuous.exit +1 -0
- package/test/golden/policy-vacuous.stderr +0 -0
- package/test/golden/policy-vacuous.stdout +3 -0
- package/test/golden/policy-wide.exit +1 -0
- package/test/golden/policy-wide.stderr +0 -0
- package/test/golden/policy-wide.stdout +15 -0
- package/test/golden/policy.exit +1 -0
- package/test/golden/policy.stderr +0 -0
- package/test/golden/policy.stdout +19 -0
- package/test/golden/scenarios-json.exit +1 -0
- package/test/golden/scenarios-json.stderr +0 -0
- package/test/golden/scenarios-json.stdout +75 -0
- package/test/golden/scenarios-status.exit +1 -0
- package/test/golden/scenarios-status.stderr +0 -0
- package/test/golden/scenarios-status.stdout +24 -0
- package/test/golden/scenarios.exit +1 -0
- package/test/golden/scenarios.stderr +0 -0
- package/test/golden/scenarios.stdout +26 -0
- package/test/golden/unit.exit +1 -0
- package/test/golden/unit.stderr +0 -0
- package/test/golden/unit.stdout +2 -0
- package/test/golden/unknown-phrase.exit +1 -0
- package/test/golden/unknown-phrase.stderr +1 -0
- package/test/golden/unknown-phrase.stdout +0 -0
- package/test/golden/usage.exit +1 -0
- package/test/golden/usage.stderr +9 -0
- package/test/golden/usage.stdout +0 -0
- package/test/run-all.ts +499 -0
- package/test/soundness.ts +375 -0
- package/test/unit.ts +1352 -0
- package/tsconfig.json +15 -0
package/src/solver.ts
ADDED
|
@@ -0,0 +1,868 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Realizability for EARS requirements.
|
|
3
|
+
*
|
|
4
|
+
* A set of requirements conflicts when some situation (an assignment of the
|
|
5
|
+
* events, states, and inputs) has no output assignment that satisfies all of
|
|
6
|
+
* them. Whether that output assignment exists is a decision: DPLL on the
|
|
7
|
+
* boolean skeleton, and bounds propagation for linear integer arithmetic.
|
|
8
|
+
* Enum values are integer indexes in declaration order.
|
|
9
|
+
*
|
|
10
|
+
* Models are the lexicographically least assignment: false before true, enums
|
|
11
|
+
* in declaration order, integers from the low end of the range to the high end.
|
|
12
|
+
* Up to 12 requirements, minimal cores are every unrealizable subset. Above
|
|
13
|
+
* that, cores are shrunk by deletion. A wide driver domain drops a later core
|
|
14
|
+
* that would reuse a requirement already reported.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { codeAt, domainSize, isDriver, requirementMentions, type Requirement, type Spec, type Term } from "./spec.ts";
|
|
18
|
+
import { driverWide, firstUnsatDrivers, minimumModel, satisfiable, TheoryQuery } from "./decide.ts";
|
|
19
|
+
import { Binding, evalAssertion, evalBool, T_FALSE, T_TRUE, T_UNKNOWN } from "./term.ts";
|
|
20
|
+
|
|
21
|
+
const PRODUCT_CAP = 9007199254740991;
|
|
22
|
+
const GAP_WALK_LIMIT = 100000;
|
|
23
|
+
|
|
24
|
+
export interface SituationScan {
|
|
25
|
+
unsat: Binding | null;
|
|
26
|
+
sawSat: boolean;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface ConflictGroup {
|
|
30
|
+
indexes: number[];
|
|
31
|
+
minimal: boolean;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
class GapState {
|
|
35
|
+
best: Binding | null;
|
|
36
|
+
|
|
37
|
+
constructor() {
|
|
38
|
+
this.best = null;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
class TheoryCache {
|
|
43
|
+
spec: Spec;
|
|
44
|
+
keys: string[];
|
|
45
|
+
queries: TheoryQuery[];
|
|
46
|
+
|
|
47
|
+
constructor(spec: Spec) {
|
|
48
|
+
this.spec = spec;
|
|
49
|
+
this.keys = [];
|
|
50
|
+
this.queries = [];
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
get(indexes: number[]): TheoryQuery {
|
|
54
|
+
let key = "";
|
|
55
|
+
for (let i = 0; i < indexes.length; i++) {
|
|
56
|
+
if (i > 0) key += ",";
|
|
57
|
+
key += String(indexes[i]);
|
|
58
|
+
}
|
|
59
|
+
for (let i = 0; i < this.keys.length; i++) {
|
|
60
|
+
if (this.keys[i] === key) return this.queries[i];
|
|
61
|
+
}
|
|
62
|
+
const query = new TheoryQuery(this.spec, reqsOf(this.spec, indexes));
|
|
63
|
+
this.keys.push(key);
|
|
64
|
+
this.queries.push(query);
|
|
65
|
+
return query;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
class Ival {
|
|
70
|
+
known: boolean;
|
|
71
|
+
kind: string;
|
|
72
|
+
lo: number;
|
|
73
|
+
hi: number;
|
|
74
|
+
|
|
75
|
+
constructor() {
|
|
76
|
+
this.known = false;
|
|
77
|
+
this.kind = "";
|
|
78
|
+
this.lo = 0;
|
|
79
|
+
this.hi = 0;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
class GapBox {
|
|
84
|
+
spec: Spec;
|
|
85
|
+
outputIndex: number;
|
|
86
|
+
drivers: number[];
|
|
87
|
+
lo: number[];
|
|
88
|
+
hi: number[];
|
|
89
|
+
best: Binding | null;
|
|
90
|
+
|
|
91
|
+
constructor(spec: Spec, outputIndex: number, drivers: number[]) {
|
|
92
|
+
this.spec = spec;
|
|
93
|
+
this.outputIndex = outputIndex;
|
|
94
|
+
this.drivers = drivers;
|
|
95
|
+
this.lo = [];
|
|
96
|
+
this.hi = [];
|
|
97
|
+
this.best = null;
|
|
98
|
+
for (let i = 0; i < spec.symbols.length; i++) {
|
|
99
|
+
this.lo.push(1);
|
|
100
|
+
this.hi.push(0);
|
|
101
|
+
}
|
|
102
|
+
for (let i = 0; i < drivers.length; i++) {
|
|
103
|
+
const si = drivers[i];
|
|
104
|
+
const sym = spec.symbols[si];
|
|
105
|
+
const count = domainSize(spec, sym);
|
|
106
|
+
if (count <= 0) continue;
|
|
107
|
+
this.lo[si] = codeAt(sym, 0);
|
|
108
|
+
this.hi[si] = codeAt(sym, count - 1);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
solve(): Binding | null {
|
|
113
|
+
this.seek(0);
|
|
114
|
+
return this.best;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
seek(pos: number): void {
|
|
118
|
+
const status = boxStatus(this.spec, this.outputIndex, this.lo, this.hi);
|
|
119
|
+
if (status === 0) return;
|
|
120
|
+
if (this.best !== null) {
|
|
121
|
+
const weight = boxWeight(this.spec, this.drivers, this.hi);
|
|
122
|
+
const bestW = gapWeight(this.spec, this.drivers, this.best);
|
|
123
|
+
if (weight < bestW) return;
|
|
124
|
+
if (weight === bestW && !gapBetter(this.spec, this.drivers, this.corner(), this.best)) return;
|
|
125
|
+
}
|
|
126
|
+
if (status === 1) {
|
|
127
|
+
this.consider(this.corner());
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
if (pos >= this.drivers.length) {
|
|
131
|
+
const env = this.pinned();
|
|
132
|
+
if (isGap(this.spec, env, this.outputIndex)) this.consider(env);
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
const si = this.drivers[pos];
|
|
136
|
+
if (this.lo[si] > this.hi[si]) return;
|
|
137
|
+
if (this.lo[si] === this.hi[si]) {
|
|
138
|
+
this.seek(pos + 1);
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
const savedLo = this.lo[si];
|
|
142
|
+
const savedHi = this.hi[si];
|
|
143
|
+
const span = savedHi - savedLo;
|
|
144
|
+
if (span <= 24) {
|
|
145
|
+
for (let v = savedHi; v >= savedLo; v--) {
|
|
146
|
+
this.lo[si] = v;
|
|
147
|
+
this.hi[si] = v;
|
|
148
|
+
this.seek(pos + 1);
|
|
149
|
+
}
|
|
150
|
+
this.lo[si] = savedLo;
|
|
151
|
+
this.hi[si] = savedHi;
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
const mid = savedLo + Math.floor(span / 2);
|
|
155
|
+
this.lo[si] = mid + 1;
|
|
156
|
+
this.hi[si] = savedHi;
|
|
157
|
+
this.seek(pos);
|
|
158
|
+
this.lo[si] = savedLo;
|
|
159
|
+
this.hi[si] = mid;
|
|
160
|
+
this.seek(pos);
|
|
161
|
+
this.lo[si] = savedLo;
|
|
162
|
+
this.hi[si] = savedHi;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
corner(): Binding {
|
|
166
|
+
const env = new Binding(this.spec.symbols.length);
|
|
167
|
+
for (let i = 0; i < this.drivers.length; i++) {
|
|
168
|
+
const si = this.drivers[i];
|
|
169
|
+
env.known[si] = true;
|
|
170
|
+
env.code[si] = this.hi[si];
|
|
171
|
+
}
|
|
172
|
+
return env;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
pinned(): Binding {
|
|
176
|
+
const env = new Binding(this.spec.symbols.length);
|
|
177
|
+
for (let i = 0; i < this.drivers.length; i++) {
|
|
178
|
+
const si = this.drivers[i];
|
|
179
|
+
env.known[si] = true;
|
|
180
|
+
env.code[si] = this.lo[si];
|
|
181
|
+
}
|
|
182
|
+
return env;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
consider(env: Binding): void {
|
|
186
|
+
if (this.best === null || gapBetter(this.spec, this.drivers, env, this.best)) this.best = env;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
export function spaceSize(spec: Spec): number {
|
|
191
|
+
let product = 1;
|
|
192
|
+
for (let i = 0; i < spec.symbols.length; i++) {
|
|
193
|
+
const size = domainSize(spec, spec.symbols[i]);
|
|
194
|
+
if (size <= 0) return -1;
|
|
195
|
+
if (product > PRODUCT_CAP / size) return PRODUCT_CAP;
|
|
196
|
+
product = product * size;
|
|
197
|
+
}
|
|
198
|
+
return product;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
export function scanRequirements(spec: Spec, indexes: number[]): SituationScan {
|
|
202
|
+
const holds = reqsOf(spec, indexes);
|
|
203
|
+
return {
|
|
204
|
+
unsat: firstUnsatDrivers(spec, holds),
|
|
205
|
+
sawSat: satisfiable(spec, holds),
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
export function modelFor(spec: Spec, indexes: number[], situation: Binding): Binding | null {
|
|
210
|
+
return minimumModel(spec, reqsOf(spec, indexes), [], null, 0, situation, true);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
export function conflictingSets(spec: Spec): ConflictGroup[] {
|
|
214
|
+
const n = spec.requirements.length;
|
|
215
|
+
if (n > 12) {
|
|
216
|
+
if (driverWide(spec)) return coresLarge(spec);
|
|
217
|
+
return coresByDeletion(spec);
|
|
218
|
+
}
|
|
219
|
+
let size = 1;
|
|
220
|
+
for (let i = 0; i < n; i++) size = size * 2;
|
|
221
|
+
const cacheKnown: boolean[] = [];
|
|
222
|
+
const cacheReal: boolean[] = [];
|
|
223
|
+
for (let i = 0; i < size; i++) {
|
|
224
|
+
cacheKnown.push(false);
|
|
225
|
+
cacheReal.push(false);
|
|
226
|
+
}
|
|
227
|
+
const found: number[] = [];
|
|
228
|
+
for (let width = 1; width <= n; width++) {
|
|
229
|
+
for (let mask = 1; mask < size; mask++) {
|
|
230
|
+
if (popcount(mask) !== width) continue;
|
|
231
|
+
let skip = false;
|
|
232
|
+
for (let f = 0; f < found.length; f++) {
|
|
233
|
+
if (includesAll(mask, found[f])) {
|
|
234
|
+
skip = true;
|
|
235
|
+
break;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
if (skip) continue;
|
|
239
|
+
if (maskRealizable(spec, mask, n, cacheKnown, cacheReal)) continue;
|
|
240
|
+
let minimal = true;
|
|
241
|
+
for (let bit = 0; bit < n; bit++) {
|
|
242
|
+
if (!hasBit(mask, bit)) continue;
|
|
243
|
+
if (!maskRealizable(spec, mask - pow2(bit), n, cacheKnown, cacheReal)) {
|
|
244
|
+
minimal = false;
|
|
245
|
+
break;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
if (minimal) found.push(mask);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
const groups: ConflictGroup[] = [];
|
|
252
|
+
for (let i = 0; i < found.length; i++) {
|
|
253
|
+
groups.push({ indexes: maskIndexes(found[i], n), minimal: true });
|
|
254
|
+
}
|
|
255
|
+
return groups;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Smallest assignment of every symbol.
|
|
260
|
+
* mode 0: every requirement holds.
|
|
261
|
+
* mode 1: some requirement fails.
|
|
262
|
+
* mode 2: every requirement holds and some extra requirement fails.
|
|
263
|
+
* Values are tried false before true, then low integers before high ones.
|
|
264
|
+
*/
|
|
265
|
+
export function firstScenario(spec: Spec, mode: number, extra: Requirement[]): Binding | null {
|
|
266
|
+
if (mode === 1) return firstMatching(spec, [], spec.requirements, null, 0);
|
|
267
|
+
if (mode === 2) return firstMatching(spec, spec.requirements, extra, null, 0);
|
|
268
|
+
return firstMatching(spec, spec.requirements, [], null, 0);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Smallest assignment where every mustHold requirement holds and, when
|
|
273
|
+
* mustFail is non-empty, at least one of those requirements fails.
|
|
274
|
+
* wantGuard 0 ignores guard, 1 requires it true, 2 requires it false.
|
|
275
|
+
* A null guard is already true, so wantGuard 2 with a null guard finds nothing.
|
|
276
|
+
*/
|
|
277
|
+
export function firstMatching(
|
|
278
|
+
spec: Spec,
|
|
279
|
+
mustHold: Requirement[],
|
|
280
|
+
mustFail: Requirement[],
|
|
281
|
+
guard: Term | null,
|
|
282
|
+
wantGuard: number,
|
|
283
|
+
): Binding | null {
|
|
284
|
+
return minimumModel(spec, mustHold, mustFail, guard, wantGuard, null, false);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* True when this requirement follows from the other eligible requirements
|
|
289
|
+
* and those others are realizable.
|
|
290
|
+
* eligible[i] is set for requirements that are outside every conflict and
|
|
291
|
+
* are not an exact copy of an earlier requirement. A conflict no longer
|
|
292
|
+
* hides a repeat that sits entirely outside it. An unrealizable eligible
|
|
293
|
+
* set has none.
|
|
294
|
+
*/
|
|
295
|
+
export function redundantFlags(spec: Spec, eligible: boolean[]): boolean[] {
|
|
296
|
+
const n = spec.requirements.length;
|
|
297
|
+
const flags: boolean[] = [];
|
|
298
|
+
for (let i = 0; i < n; i++) flags.push(false);
|
|
299
|
+
const pool: number[] = [];
|
|
300
|
+
for (let i = 0; i < n; i++) {
|
|
301
|
+
if (i < eligible.length && eligible[i]) pool.push(i);
|
|
302
|
+
}
|
|
303
|
+
if (pool.length < 2) return flags;
|
|
304
|
+
if (firstUnsatDrivers(spec, reqsOf(spec, pool)) !== null) return flags;
|
|
305
|
+
for (let p = 0; p < pool.length; p++) {
|
|
306
|
+
const i = pool[p];
|
|
307
|
+
const others: Requirement[] = [];
|
|
308
|
+
for (let q = 0; q < pool.length; q++) {
|
|
309
|
+
if (q !== p) others.push(spec.requirements[pool[q]]);
|
|
310
|
+
}
|
|
311
|
+
if (!satisfiable(spec, others)) continue;
|
|
312
|
+
const fails: Requirement[] = [];
|
|
313
|
+
fails.push(spec.requirements[i]);
|
|
314
|
+
if (minimumModel(spec, others, fails, null, 0, null, false) === null) flags[i] = true;
|
|
315
|
+
}
|
|
316
|
+
return flags;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
export function bestGap(spec: Spec, outputIndex: number): Binding | null {
|
|
320
|
+
let mentioned = false;
|
|
321
|
+
for (let r = 0; r < spec.requirements.length; r++) {
|
|
322
|
+
const req = spec.requirements[r];
|
|
323
|
+
if (!requirementMentions(req, outputIndex)) continue;
|
|
324
|
+
mentioned = true;
|
|
325
|
+
if (req.antecedent === null) return null;
|
|
326
|
+
}
|
|
327
|
+
const drivers = driverIndexes(spec);
|
|
328
|
+
if (!mentioned) return maxDriverBinding(spec, drivers);
|
|
329
|
+
if (driverWalkFits(spec, drivers)) {
|
|
330
|
+
const env = new Binding(spec.symbols.length);
|
|
331
|
+
const state = new GapState();
|
|
332
|
+
walkGaps(spec, outputIndex, drivers, outputIndexes(spec), env, 0, state);
|
|
333
|
+
return state.best;
|
|
334
|
+
}
|
|
335
|
+
const box = new GapBox(spec, outputIndex, drivers);
|
|
336
|
+
return box.solve();
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
function reqsOf(spec: Spec, indexes: number[]): Requirement[] {
|
|
340
|
+
const holds: Requirement[] = [];
|
|
341
|
+
for (let i = 0; i < indexes.length; i++) holds.push(spec.requirements[indexes[i]]);
|
|
342
|
+
return holds;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
function maxDriverBinding(spec: Spec, drivers: number[]): Binding {
|
|
346
|
+
const env = new Binding(spec.symbols.length);
|
|
347
|
+
for (let i = 0; i < drivers.length; i++) {
|
|
348
|
+
const si = drivers[i];
|
|
349
|
+
const sym = spec.symbols[si];
|
|
350
|
+
const count = domainSize(spec, sym);
|
|
351
|
+
env.known[si] = true;
|
|
352
|
+
env.code[si] = count <= 0 ? 0 : codeAt(sym, count - 1);
|
|
353
|
+
}
|
|
354
|
+
return env;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
function driverWalkFits(spec: Spec, drivers: number[]): boolean {
|
|
358
|
+
let product = 1;
|
|
359
|
+
for (let i = 0; i < drivers.length; i++) {
|
|
360
|
+
const size = domainSize(spec, spec.symbols[drivers[i]]);
|
|
361
|
+
if (size <= 0 || size > 4096) return false;
|
|
362
|
+
if (product > GAP_WALK_LIMIT / size) return false;
|
|
363
|
+
product = product * size;
|
|
364
|
+
}
|
|
365
|
+
return true;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
function walkGaps(
|
|
369
|
+
spec: Spec,
|
|
370
|
+
outputIndex: number,
|
|
371
|
+
drivers: number[],
|
|
372
|
+
outputs: number[],
|
|
373
|
+
env: Binding,
|
|
374
|
+
pos: number,
|
|
375
|
+
state: GapState,
|
|
376
|
+
): void {
|
|
377
|
+
if (pos === drivers.length) {
|
|
378
|
+
clearKnown(env, outputs);
|
|
379
|
+
if (!isGap(spec, env, outputIndex)) return;
|
|
380
|
+
if (state.best === null || gapBetter(spec, drivers, env, state.best)) {
|
|
381
|
+
const snap = env.copy();
|
|
382
|
+
clearKnown(snap, outputs);
|
|
383
|
+
state.best = snap;
|
|
384
|
+
}
|
|
385
|
+
return;
|
|
386
|
+
}
|
|
387
|
+
const si = drivers[pos];
|
|
388
|
+
const sym = spec.symbols[si];
|
|
389
|
+
const count = domainSize(spec, sym);
|
|
390
|
+
for (let v = 0; v < count; v++) {
|
|
391
|
+
env.known[si] = true;
|
|
392
|
+
env.code[si] = codeAt(sym, v);
|
|
393
|
+
walkGaps(spec, outputIndex, drivers, outputs, env, pos + 1, state);
|
|
394
|
+
}
|
|
395
|
+
env.known[si] = false;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
function isGap(spec: Spec, env: Binding, outputIndex: number): boolean {
|
|
399
|
+
for (let r = 0; r < spec.requirements.length; r++) {
|
|
400
|
+
const req = spec.requirements[r];
|
|
401
|
+
if (!requirementMentions(req, outputIndex)) continue;
|
|
402
|
+
const guard = req.antecedent === null ? T_TRUE : evalBool(req.antecedent, spec, env);
|
|
403
|
+
if (guard !== T_FALSE) return false;
|
|
404
|
+
}
|
|
405
|
+
return true;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
function gapBetter(spec: Spec, drivers: number[], env: Binding, best: Binding): boolean {
|
|
409
|
+
const left = gapWeight(spec, drivers, env);
|
|
410
|
+
const right = gapWeight(spec, drivers, best);
|
|
411
|
+
if (left > right) return true;
|
|
412
|
+
if (left < right) return false;
|
|
413
|
+
for (let i = 0; i < drivers.length; i++) {
|
|
414
|
+
const si = drivers[i];
|
|
415
|
+
if (env.code[si] > best.code[si]) return true;
|
|
416
|
+
if (env.code[si] < best.code[si]) return false;
|
|
417
|
+
}
|
|
418
|
+
return false;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
function gapWeight(spec: Spec, drivers: number[], env: Binding): number {
|
|
422
|
+
let weight = 0;
|
|
423
|
+
for (let i = 0; i < drivers.length; i++) {
|
|
424
|
+
const si = drivers[i];
|
|
425
|
+
const sym = spec.symbols[si];
|
|
426
|
+
if (sym.sort.kind === "int") weight += env.code[si] - sym.sort.min;
|
|
427
|
+
else weight += env.code[si];
|
|
428
|
+
}
|
|
429
|
+
return weight;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
function boxWeight(spec: Spec, drivers: number[], hi: number[]): number {
|
|
433
|
+
let weight = 0;
|
|
434
|
+
for (let i = 0; i < drivers.length; i++) {
|
|
435
|
+
const si = drivers[i];
|
|
436
|
+
const sym = spec.symbols[si];
|
|
437
|
+
if (sym.sort.kind === "int") weight += hi[si] - sym.sort.min;
|
|
438
|
+
else weight += hi[si];
|
|
439
|
+
}
|
|
440
|
+
return weight;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
function boxStatus(spec: Spec, outputIndex: number, lo: number[], hi: number[]): number {
|
|
444
|
+
let mixed = false;
|
|
445
|
+
for (let r = 0; r < spec.requirements.length; r++) {
|
|
446
|
+
const req = spec.requirements[r];
|
|
447
|
+
if (!requirementMentions(req, outputIndex)) continue;
|
|
448
|
+
if (req.antecedent === null) return 0;
|
|
449
|
+
const guard = rangeBool(req.antecedent, spec, lo, hi);
|
|
450
|
+
if (guard === T_TRUE) return 0;
|
|
451
|
+
if (guard === T_FALSE) continue;
|
|
452
|
+
if (!canBeFalse(req.antecedent, spec, lo, hi)) return 0;
|
|
453
|
+
mixed = true;
|
|
454
|
+
}
|
|
455
|
+
if (mixed) return 2;
|
|
456
|
+
return 1;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
function rangeBool(term: Term, spec: Spec, lo: number[], hi: number[]): number {
|
|
460
|
+
if (term.kind === "bool") return term.value ? T_TRUE : T_FALSE;
|
|
461
|
+
if (term.kind === "sym") {
|
|
462
|
+
if (lo[term.index] > hi[term.index]) return T_UNKNOWN;
|
|
463
|
+
const sym = spec.symbols[term.index];
|
|
464
|
+
if (sym.sort.kind !== "bool") return T_UNKNOWN;
|
|
465
|
+
if (lo[term.index] !== hi[term.index]) return T_UNKNOWN;
|
|
466
|
+
return lo[term.index] === 1 ? T_TRUE : T_FALSE;
|
|
467
|
+
}
|
|
468
|
+
if (term.kind === "not") {
|
|
469
|
+
const inner = rangeBool(term.term, spec, lo, hi);
|
|
470
|
+
if (inner === T_UNKNOWN) return T_UNKNOWN;
|
|
471
|
+
return inner === T_TRUE ? T_FALSE : T_TRUE;
|
|
472
|
+
}
|
|
473
|
+
if (term.kind === "and") {
|
|
474
|
+
const left = rangeBool(term.left, spec, lo, hi);
|
|
475
|
+
const right = rangeBool(term.right, spec, lo, hi);
|
|
476
|
+
if (left === T_FALSE || right === T_FALSE) return T_FALSE;
|
|
477
|
+
if (left === T_TRUE && right === T_TRUE) return T_TRUE;
|
|
478
|
+
return T_UNKNOWN;
|
|
479
|
+
}
|
|
480
|
+
if (term.kind === "or") {
|
|
481
|
+
const left = rangeBool(term.left, spec, lo, hi);
|
|
482
|
+
const right = rangeBool(term.right, spec, lo, hi);
|
|
483
|
+
if (left === T_TRUE || right === T_TRUE) return T_TRUE;
|
|
484
|
+
if (left === T_FALSE && right === T_FALSE) return T_FALSE;
|
|
485
|
+
return T_UNKNOWN;
|
|
486
|
+
}
|
|
487
|
+
if (
|
|
488
|
+
term.kind === "eq" ||
|
|
489
|
+
term.kind === "neq" ||
|
|
490
|
+
term.kind === "lt" ||
|
|
491
|
+
term.kind === "le" ||
|
|
492
|
+
term.kind === "gt" ||
|
|
493
|
+
term.kind === "ge"
|
|
494
|
+
) {
|
|
495
|
+
return rangeCmp(term, spec, lo, hi);
|
|
496
|
+
}
|
|
497
|
+
return T_UNKNOWN;
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
function rangeCmp(term: Term, spec: Spec, lo: number[], hi: number[]): number {
|
|
501
|
+
if (
|
|
502
|
+
term.kind !== "eq" &&
|
|
503
|
+
term.kind !== "neq" &&
|
|
504
|
+
term.kind !== "lt" &&
|
|
505
|
+
term.kind !== "le" &&
|
|
506
|
+
term.kind !== "gt" &&
|
|
507
|
+
term.kind !== "ge"
|
|
508
|
+
) {
|
|
509
|
+
return T_UNKNOWN;
|
|
510
|
+
}
|
|
511
|
+
const left = linRange(term.left, spec, lo, hi);
|
|
512
|
+
const right = linRange(term.right, spec, lo, hi);
|
|
513
|
+
if (!left.known || !right.known) return T_UNKNOWN;
|
|
514
|
+
if (left.kind !== right.kind) return T_UNKNOWN;
|
|
515
|
+
const apart = left.hi < right.lo || right.hi < left.lo;
|
|
516
|
+
const points = left.lo === left.hi && right.lo === right.hi;
|
|
517
|
+
const same = points && left.lo === right.lo;
|
|
518
|
+
if (left.kind !== "int") {
|
|
519
|
+
if (term.kind === "eq") {
|
|
520
|
+
if (same) return T_TRUE;
|
|
521
|
+
if (apart) return T_FALSE;
|
|
522
|
+
return T_UNKNOWN;
|
|
523
|
+
}
|
|
524
|
+
if (term.kind === "neq") {
|
|
525
|
+
if (same) return T_FALSE;
|
|
526
|
+
if (apart) return T_TRUE;
|
|
527
|
+
return T_UNKNOWN;
|
|
528
|
+
}
|
|
529
|
+
return T_UNKNOWN;
|
|
530
|
+
}
|
|
531
|
+
if (term.kind === "eq") {
|
|
532
|
+
if (same) return T_TRUE;
|
|
533
|
+
if (apart) return T_FALSE;
|
|
534
|
+
return T_UNKNOWN;
|
|
535
|
+
}
|
|
536
|
+
if (term.kind === "neq") {
|
|
537
|
+
if (same) return T_FALSE;
|
|
538
|
+
if (apart) return T_TRUE;
|
|
539
|
+
return T_UNKNOWN;
|
|
540
|
+
}
|
|
541
|
+
if (term.kind === "lt") {
|
|
542
|
+
if (left.hi < right.lo) return T_TRUE;
|
|
543
|
+
if (left.lo >= right.hi) return T_FALSE;
|
|
544
|
+
return T_UNKNOWN;
|
|
545
|
+
}
|
|
546
|
+
if (term.kind === "le") {
|
|
547
|
+
if (left.hi <= right.lo) return T_TRUE;
|
|
548
|
+
if (left.lo > right.hi) return T_FALSE;
|
|
549
|
+
return T_UNKNOWN;
|
|
550
|
+
}
|
|
551
|
+
if (term.kind === "gt") {
|
|
552
|
+
if (left.lo > right.hi) return T_TRUE;
|
|
553
|
+
if (left.hi <= right.lo) return T_FALSE;
|
|
554
|
+
return T_UNKNOWN;
|
|
555
|
+
}
|
|
556
|
+
if (left.lo >= right.hi) return T_TRUE;
|
|
557
|
+
if (left.hi < right.lo) return T_FALSE;
|
|
558
|
+
return T_UNKNOWN;
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
function linRange(term: Term, spec: Spec, lo: number[], hi: number[]): Ival {
|
|
562
|
+
const out = new Ival();
|
|
563
|
+
if (term.kind === "int") {
|
|
564
|
+
out.known = true;
|
|
565
|
+
out.kind = "int";
|
|
566
|
+
out.lo = term.value;
|
|
567
|
+
out.hi = term.value;
|
|
568
|
+
return out;
|
|
569
|
+
}
|
|
570
|
+
if (term.kind === "enum") {
|
|
571
|
+
out.known = true;
|
|
572
|
+
out.kind = "enum:" + term.enumName;
|
|
573
|
+
out.lo = term.index;
|
|
574
|
+
out.hi = term.index;
|
|
575
|
+
return out;
|
|
576
|
+
}
|
|
577
|
+
if (term.kind === "bool") {
|
|
578
|
+
out.known = true;
|
|
579
|
+
out.kind = "bool";
|
|
580
|
+
out.lo = term.value ? 1 : 0;
|
|
581
|
+
out.hi = out.lo;
|
|
582
|
+
return out;
|
|
583
|
+
}
|
|
584
|
+
if (term.kind === "sym") {
|
|
585
|
+
if (lo[term.index] > hi[term.index]) return out;
|
|
586
|
+
const sym = spec.symbols[term.index];
|
|
587
|
+
out.known = true;
|
|
588
|
+
if (sym.sort.kind === "bool") out.kind = "bool";
|
|
589
|
+
else if (sym.sort.kind === "int") out.kind = "int";
|
|
590
|
+
else out.kind = "enum:" + sym.sort.enumName;
|
|
591
|
+
out.lo = lo[term.index];
|
|
592
|
+
out.hi = hi[term.index];
|
|
593
|
+
return out;
|
|
594
|
+
}
|
|
595
|
+
if (term.kind === "add" || term.kind === "sub") {
|
|
596
|
+
const left = linRange(term.left, spec, lo, hi);
|
|
597
|
+
const right = linRange(term.right, spec, lo, hi);
|
|
598
|
+
if (!left.known || !right.known || left.kind !== "int" || right.kind !== "int") return out;
|
|
599
|
+
out.known = true;
|
|
600
|
+
out.kind = "int";
|
|
601
|
+
if (term.kind === "add") {
|
|
602
|
+
out.lo = left.lo + right.lo;
|
|
603
|
+
out.hi = left.hi + right.hi;
|
|
604
|
+
} else {
|
|
605
|
+
out.lo = left.lo - right.hi;
|
|
606
|
+
out.hi = left.hi - right.lo;
|
|
607
|
+
}
|
|
608
|
+
return out;
|
|
609
|
+
}
|
|
610
|
+
return out;
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
function canBeFalse(term: Term, spec: Spec, lo: number[], hi: number[]): boolean {
|
|
614
|
+
if (term.kind === "bool") return !term.value;
|
|
615
|
+
if (term.kind === "sym") {
|
|
616
|
+
if (lo[term.index] > hi[term.index]) return false;
|
|
617
|
+
if (spec.symbols[term.index].sort.kind !== "bool") return false;
|
|
618
|
+
return lo[term.index] === 0;
|
|
619
|
+
}
|
|
620
|
+
if (term.kind === "not") return canBeTrue(term.term, spec, lo, hi);
|
|
621
|
+
if (term.kind === "and") return canBeFalse(term.left, spec, lo, hi) || canBeFalse(term.right, spec, lo, hi);
|
|
622
|
+
if (term.kind === "or") return canBeFalse(term.left, spec, lo, hi) && canBeFalse(term.right, spec, lo, hi);
|
|
623
|
+
const value = rangeBool(term, spec, lo, hi);
|
|
624
|
+
if (value === T_FALSE) return true;
|
|
625
|
+
if (value === T_TRUE) return false;
|
|
626
|
+
return true;
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
function canBeTrue(term: Term, spec: Spec, lo: number[], hi: number[]): boolean {
|
|
630
|
+
if (term.kind === "bool") return term.value;
|
|
631
|
+
if (term.kind === "sym") {
|
|
632
|
+
if (lo[term.index] > hi[term.index]) return false;
|
|
633
|
+
if (spec.symbols[term.index].sort.kind !== "bool") return false;
|
|
634
|
+
return hi[term.index] === 1;
|
|
635
|
+
}
|
|
636
|
+
if (term.kind === "not") return canBeFalse(term.term, spec, lo, hi);
|
|
637
|
+
if (term.kind === "and") return canBeTrue(term.left, spec, lo, hi) && canBeTrue(term.right, spec, lo, hi);
|
|
638
|
+
if (term.kind === "or") return canBeTrue(term.left, spec, lo, hi) || canBeTrue(term.right, spec, lo, hi);
|
|
639
|
+
const value = rangeBool(term, spec, lo, hi);
|
|
640
|
+
if (value === T_TRUE) return true;
|
|
641
|
+
if (value === T_FALSE) return false;
|
|
642
|
+
return true;
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
function clearKnown(env: Binding, indexes: number[]): void {
|
|
646
|
+
for (let i = 0; i < indexes.length; i++) env.known[indexes[i]] = false;
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
function driverIndexes(spec: Spec): number[] {
|
|
650
|
+
const indexes: number[] = [];
|
|
651
|
+
for (let i = 0; i < spec.symbols.length; i++) {
|
|
652
|
+
if (isDriver(spec.symbols[i])) indexes.push(i);
|
|
653
|
+
}
|
|
654
|
+
return indexes;
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
function outputIndexes(spec: Spec): number[] {
|
|
658
|
+
const indexes: number[] = [];
|
|
659
|
+
for (let i = 0; i < spec.symbols.length; i++) {
|
|
660
|
+
if (!isDriver(spec.symbols[i])) indexes.push(i);
|
|
661
|
+
}
|
|
662
|
+
return indexes;
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
/**
|
|
666
|
+
* More than 12 requirements is too many subsets to list. Walk each driver
|
|
667
|
+
* situation instead, and shrink the requirements that fail there by deletion.
|
|
668
|
+
* Cores that share a requirement and are unsatisfiable in exactly the same
|
|
669
|
+
* situations can collapse to one of those cores.
|
|
670
|
+
*/
|
|
671
|
+
function coresByDeletion(spec: Spec): ConflictGroup[] {
|
|
672
|
+
const cores: ConflictGroup[] = [];
|
|
673
|
+
const drivers = driverIndexes(spec);
|
|
674
|
+
const env = new Binding(spec.symbols.length);
|
|
675
|
+
const all: number[] = [];
|
|
676
|
+
for (let i = 0; i < spec.requirements.length; i++) all.push(i);
|
|
677
|
+
const cache = new TheoryCache(spec);
|
|
678
|
+
walkCores(spec, all, drivers, env, 0, cores, cache);
|
|
679
|
+
return cores;
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
function walkCores(
|
|
683
|
+
spec: Spec,
|
|
684
|
+
all: number[],
|
|
685
|
+
drivers: number[],
|
|
686
|
+
env: Binding,
|
|
687
|
+
pos: number,
|
|
688
|
+
cores: ConflictGroup[],
|
|
689
|
+
cache: TheoryCache,
|
|
690
|
+
): void {
|
|
691
|
+
if (pos === drivers.length) {
|
|
692
|
+
harvestCores(spec, all, env, cores, cache);
|
|
693
|
+
return;
|
|
694
|
+
}
|
|
695
|
+
const si = drivers[pos];
|
|
696
|
+
const sym = spec.symbols[si];
|
|
697
|
+
const count = domainSize(spec, sym);
|
|
698
|
+
for (let v = 0; v < count; v++) {
|
|
699
|
+
env.known[si] = true;
|
|
700
|
+
env.code[si] = codeAt(sym, v);
|
|
701
|
+
walkCores(spec, all, drivers, env, pos + 1, cores, cache);
|
|
702
|
+
}
|
|
703
|
+
env.known[si] = false;
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
function harvestCores(spec: Spec, all: number[], env: Binding, cores: ConflictGroup[], cache: TheoryCache): void {
|
|
707
|
+
let pool = copyIndexes(all);
|
|
708
|
+
while (situationUnsat(pool, env, cache)) {
|
|
709
|
+
let removed = false;
|
|
710
|
+
for (let c = 0; c < cores.length; c++) {
|
|
711
|
+
if (!situationUnsat(cores[c].indexes, env, cache)) continue;
|
|
712
|
+
const next = subtractIndexes(pool, cores[c].indexes);
|
|
713
|
+
if (next.length < pool.length) {
|
|
714
|
+
pool = next;
|
|
715
|
+
removed = true;
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
if (removed) continue;
|
|
719
|
+
const local = minimizeSituation(pool, env, cache);
|
|
720
|
+
const global = minimizeGlobal(local, cache);
|
|
721
|
+
if (global.length === 0) return;
|
|
722
|
+
if (!hasCore(cores, global)) cores.push({ indexes: global, minimal: true });
|
|
723
|
+
const next = subtractIndexes(pool, global);
|
|
724
|
+
if (next.length === pool.length) return;
|
|
725
|
+
pool = next;
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
/**
|
|
730
|
+
* Wide driver domains are not listed. Each core is the requirements that fail
|
|
731
|
+
* in the least bad situation still in the pool, then that core is removed.
|
|
732
|
+
* A later core that needs a requirement from an earlier one is not reported.
|
|
733
|
+
*/
|
|
734
|
+
function coresLarge(spec: Spec): ConflictGroup[] {
|
|
735
|
+
const cores: ConflictGroup[] = [];
|
|
736
|
+
let pool: number[] = [];
|
|
737
|
+
for (let i = 0; i < spec.requirements.length; i++) pool.push(i);
|
|
738
|
+
const cache = new TheoryCache(spec);
|
|
739
|
+
while (pool.length > 0) {
|
|
740
|
+
const bad = cache.get(pool).badDrivers();
|
|
741
|
+
if (bad === null) break;
|
|
742
|
+
const local = minimizeSituation(pool, bad, cache);
|
|
743
|
+
const global = minimizeGlobal(local, cache);
|
|
744
|
+
if (global.length === 0) break;
|
|
745
|
+
if (!hasCore(cores, global)) cores.push({ indexes: global, minimal: true });
|
|
746
|
+
const next = subtractIndexes(pool, global);
|
|
747
|
+
if (next.length === pool.length) break;
|
|
748
|
+
pool = next;
|
|
749
|
+
}
|
|
750
|
+
return cores;
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
function minimizeSituation(indexes: number[], env: Binding, cache: TheoryCache): number[] {
|
|
754
|
+
let current = copyIndexes(indexes);
|
|
755
|
+
let i = 0;
|
|
756
|
+
while (i < current.length) {
|
|
757
|
+
const trial = withoutAt(current, i);
|
|
758
|
+
if (situationUnsat(trial, env, cache)) current = trial;
|
|
759
|
+
else i++;
|
|
760
|
+
}
|
|
761
|
+
return current;
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
function minimizeGlobal(indexes: number[], cache: TheoryCache): number[] {
|
|
765
|
+
let current = copyIndexes(indexes);
|
|
766
|
+
let i = 0;
|
|
767
|
+
while (i < current.length) {
|
|
768
|
+
const trial = withoutAt(current, i);
|
|
769
|
+
const bad = trial.length === 0 ? null : cache.get(trial).badDrivers();
|
|
770
|
+
if (bad !== null) current = trial;
|
|
771
|
+
else i++;
|
|
772
|
+
}
|
|
773
|
+
return current;
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
function situationUnsat(indexes: number[], env: Binding, cache: TheoryCache): boolean {
|
|
777
|
+
if (indexes.length === 0) return false;
|
|
778
|
+
return !cache.get(indexes).pinnedSat(env);
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
function hasCore(cores: ConflictGroup[], indexes: number[]): boolean {
|
|
782
|
+
for (let i = 0; i < cores.length; i++) {
|
|
783
|
+
const known = cores[i].indexes;
|
|
784
|
+
if (known.length !== indexes.length) continue;
|
|
785
|
+
let same = true;
|
|
786
|
+
for (let j = 0; j < known.length; j++) {
|
|
787
|
+
if (known[j] !== indexes[j]) same = false;
|
|
788
|
+
}
|
|
789
|
+
if (same) return true;
|
|
790
|
+
}
|
|
791
|
+
return false;
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
function copyIndexes(indexes: number[]): number[] {
|
|
795
|
+
const out: number[] = [];
|
|
796
|
+
for (let i = 0; i < indexes.length; i++) out.push(indexes[i]);
|
|
797
|
+
return out;
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
function withoutAt(indexes: number[], drop: number): number[] {
|
|
801
|
+
const out: number[] = [];
|
|
802
|
+
for (let i = 0; i < indexes.length; i++) {
|
|
803
|
+
if (i !== drop) out.push(indexes[i]);
|
|
804
|
+
}
|
|
805
|
+
return out;
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
function subtractIndexes(indexes: number[], remove: number[]): number[] {
|
|
809
|
+
const out: number[] = [];
|
|
810
|
+
for (let i = 0; i < indexes.length; i++) {
|
|
811
|
+
let drop = false;
|
|
812
|
+
for (let j = 0; j < remove.length; j++) {
|
|
813
|
+
if (indexes[i] === remove[j]) drop = true;
|
|
814
|
+
}
|
|
815
|
+
if (!drop) out.push(indexes[i]);
|
|
816
|
+
}
|
|
817
|
+
return out;
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
function maskRealizable(spec: Spec, mask: number, n: number, cacheKnown: boolean[], cacheReal: boolean[]): boolean {
|
|
821
|
+
if (mask === 0) return true;
|
|
822
|
+
if (cacheKnown[mask]) return cacheReal[mask];
|
|
823
|
+
const scan = scanRequirements(spec, maskIndexes(mask, n));
|
|
824
|
+
const ok = scan.unsat === null;
|
|
825
|
+
cacheKnown[mask] = true;
|
|
826
|
+
cacheReal[mask] = ok;
|
|
827
|
+
return ok;
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
function maskIndexes(mask: number, n: number): number[] {
|
|
831
|
+
const indexes: number[] = [];
|
|
832
|
+
for (let bit = 0; bit < n; bit++) {
|
|
833
|
+
if (hasBit(mask, bit)) indexes.push(bit);
|
|
834
|
+
}
|
|
835
|
+
return indexes;
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
function includesAll(candidate: number, found: number): boolean {
|
|
839
|
+
let bit = 0;
|
|
840
|
+
let rest = found;
|
|
841
|
+
while (rest > 0) {
|
|
842
|
+
if (rest % 2 === 1 && !hasBit(candidate, bit)) return false;
|
|
843
|
+
rest = Math.trunc(rest / 2);
|
|
844
|
+
bit++;
|
|
845
|
+
}
|
|
846
|
+
return true;
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
function hasBit(mask: number, bit: number): boolean {
|
|
850
|
+
const bitValue = pow2(bit);
|
|
851
|
+
return Math.trunc(mask / bitValue) % 2 === 1;
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
function pow2(bit: number): number {
|
|
855
|
+
let value = 1;
|
|
856
|
+
for (let i = 0; i < bit; i++) value = value * 2;
|
|
857
|
+
return value;
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
function popcount(mask: number): number {
|
|
861
|
+
let count = 0;
|
|
862
|
+
let rest = mask;
|
|
863
|
+
while (rest > 0) {
|
|
864
|
+
if (rest % 2 === 1) count++;
|
|
865
|
+
rest = Math.trunc(rest / 2);
|
|
866
|
+
}
|
|
867
|
+
return count;
|
|
868
|
+
}
|