gdp-wired 0.4.0 → 0.5.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/README.md +4 -1
- package/dist/core/dsl.d.ts +31 -14
- package/dist/core/dsl.js +143 -103
- package/dist/kernel/kernel.d.ts +6 -0
- package/dist/kernel/kernel.js +120 -10
- package/dist/kernel/prove.js +174 -50
- package/dist/lean-kernel/emit.js +34 -10
- package/dist/lean-kernel/library.js +4 -4
- package/package.json +2 -2
package/dist/kernel/kernel.js
CHANGED
|
@@ -35,11 +35,24 @@ function decide(lin, path) {
|
|
|
35
35
|
const flipped = path.get(canon(add(k(1), scale(lin, -1))))?.holds;
|
|
36
36
|
return flipped === undefined ? undefined : !flipped;
|
|
37
37
|
}
|
|
38
|
-
// A term's linear form under the path,
|
|
39
|
-
//
|
|
38
|
+
// A term's linear form under the path. Reads become cells, named by map and key: `M[key]`; a choice the path leaves
|
|
39
|
+
// open (∸, min, max, if) becomes a column of its own, `⟦n⟧`, standing for the term's value (see termRows).
|
|
40
|
+
// Remembered per path and term (a path is never changed once built; a new decision is a new path), so a term shared
|
|
41
|
+
// across hypotheses, or nested in a k-step run's later states, is read once per path.
|
|
42
|
+
const linearMemo = new WeakMap();
|
|
40
43
|
export function linear(e, path) {
|
|
44
|
+
let memo = linearMemo.get(path);
|
|
45
|
+
if (!memo)
|
|
46
|
+
linearMemo.set(path, (memo = new WeakMap()));
|
|
47
|
+
if (memo.has(e))
|
|
48
|
+
return memo.get(e);
|
|
49
|
+
const out = linearOf(e, path);
|
|
50
|
+
memo.set(e, out);
|
|
51
|
+
return out;
|
|
52
|
+
}
|
|
53
|
+
function linearOf(e, path) {
|
|
41
54
|
switch (e.op) {
|
|
42
|
-
case 'var': return e.name === '1' || /[*[\]
|
|
55
|
+
case 'var': return e.name === '1' || /[*[\],:⟦]/.test(e.name) ? undefined : v(e.name); // those characters build cells, monomials and term columns
|
|
43
56
|
case 'const': return k(e.value);
|
|
44
57
|
case 'mul': {
|
|
45
58
|
const a = linear(e.a, path);
|
|
@@ -61,14 +74,14 @@ export function linear(e, path) {
|
|
|
61
74
|
return undefined;
|
|
62
75
|
const aLeB = decide(sub(a, b), path), bLeA = decide(sub(b, a), path);
|
|
63
76
|
if (e.op === 'sub')
|
|
64
|
-
return bLeA === true ? sub(a, b) : bLeA === false ? k(0) :
|
|
77
|
+
return bLeA === true ? sub(a, b) : bLeA === false ? k(0) : v(termColumn(e));
|
|
65
78
|
if (aLeB === undefined)
|
|
66
|
-
return
|
|
79
|
+
return v(termColumn(e));
|
|
67
80
|
return (e.op === 'min') === aLeB ? a : b;
|
|
68
81
|
}
|
|
69
82
|
case 'if': {
|
|
70
83
|
const c = truth(e.c, path);
|
|
71
|
-
return c === undefined ?
|
|
84
|
+
return c === undefined ? v(termColumn(e)) : linear(c ? e.then : e.else, path);
|
|
72
85
|
}
|
|
73
86
|
case 'get': {
|
|
74
87
|
const key = linear(e.key, path);
|
|
@@ -76,6 +89,61 @@ export function linear(e, path) {
|
|
|
76
89
|
}
|
|
77
90
|
}
|
|
78
91
|
}
|
|
92
|
+
// --- term columns -------------------------------------------------------------------------------------------------------
|
|
93
|
+
// A choice the path leaves open is a column ⟦n⟧ for the n-th term met (one table per process: the name only has to be
|
|
94
|
+
// the same for the same term). A leaf reads it through rows that hold whichever way the choice goes (termRows), so a
|
|
95
|
+
// fact about the choice's value can be split on, and proven, before the choice is decided.
|
|
96
|
+
// Terms are hash-consed: a term's id comes from its operator, its fields and its parts' ids, remembered per object, so a
|
|
97
|
+
// deep term (a k-step run nests k steps) is read once, not once per lookup.
|
|
98
|
+
const termIds = new Map(), termTable = [], memo = new WeakMap();
|
|
99
|
+
function termId(t) {
|
|
100
|
+
const had = memo.get(t);
|
|
101
|
+
if (had !== undefined)
|
|
102
|
+
return had;
|
|
103
|
+
const parts = Object.entries(t).sort(([x], [y]) => (x < y ? -1 : x > y ? 1 : 0)).map(([key, val]) => (Array.isArray(val) ? `${key}=[${val.map(x => termId(x)).join(',')}]` : typeof val === 'object' && val !== null ? `${key}=#${termId(val)}` : `${key}=${JSON.stringify(val)}`));
|
|
104
|
+
const key = parts.join(';');
|
|
105
|
+
let id = termIds.get(key);
|
|
106
|
+
if (id === undefined) {
|
|
107
|
+
id = termTable.length;
|
|
108
|
+
termIds.set(key, id);
|
|
109
|
+
termTable.push(t);
|
|
110
|
+
}
|
|
111
|
+
memo.set(t, id);
|
|
112
|
+
return id;
|
|
113
|
+
}
|
|
114
|
+
export const termColumn = (e) => `⟦${termId(e)}⟧`;
|
|
115
|
+
export const columnTerm = (name) => { const m = /^⟦(\d+)⟧$/.exec(name); return m ? termTable[Number(m[1])] : undefined; };
|
|
116
|
+
// Whether the path decides a choice's top (an if's condition, which side of min, max or ∸).
|
|
117
|
+
export function decided(e, path) {
|
|
118
|
+
if (e.op === 'if')
|
|
119
|
+
return truth(e.c, path) !== undefined;
|
|
120
|
+
if (e.op !== 'sub' && e.op !== 'min' && e.op !== 'max')
|
|
121
|
+
return true;
|
|
122
|
+
const a = linear(e.a, path), b = linear(e.b, path);
|
|
123
|
+
return !!a && !!b && decide(e.op === 'sub' ? sub(b, a) : sub(a, b), path) !== undefined;
|
|
124
|
+
}
|
|
125
|
+
// The rows a term column brings: its bounds, true whichever way the choice goes (min(a, b) ≤ a and ≤ b; max(a, b) ≥ a
|
|
126
|
+
// and ≥ b; a ∸ b ≥ a − b and ≤ a), and, once the path decides it, its equality to the branch taken.
|
|
127
|
+
export function termRows(name, path) {
|
|
128
|
+
const e = columnTerm(name), col = v(name), out = [];
|
|
129
|
+
if (e.op === 'min' || e.op === 'max' || e.op === 'sub') {
|
|
130
|
+
const a = linear(e.a, path), b = linear(e.b, path);
|
|
131
|
+
if (a && b) {
|
|
132
|
+
if (e.op === 'min')
|
|
133
|
+
out.push(sub(col, a), sub(col, b));
|
|
134
|
+
if (e.op === 'max')
|
|
135
|
+
out.push(sub(a, col), sub(b, col));
|
|
136
|
+
if (e.op === 'sub')
|
|
137
|
+
out.push(sub(sub(a, b), col), sub(col, a));
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
if (decided(e, path)) {
|
|
141
|
+
const r = linear(e, path);
|
|
142
|
+
if (r)
|
|
143
|
+
out.push(sub(col, r), sub(r, col));
|
|
144
|
+
}
|
|
145
|
+
return out;
|
|
146
|
+
}
|
|
79
147
|
// Products: multiply out, each monomial (variables joined by '*', sorted) one more natural variable. Forgetting how
|
|
80
148
|
// monomials relate (x*x to x, say) only weakens what a leaf may use, so it stays sound; distributing is what proves
|
|
81
149
|
// spend <= m·count over a node from the same law at its children. Map reads can't be factors.
|
|
@@ -83,8 +151,8 @@ function times(a, b) {
|
|
|
83
151
|
const out = [];
|
|
84
152
|
for (const [p, cp] of Object.entries(a))
|
|
85
153
|
for (const [q, cq] of Object.entries(b)) {
|
|
86
|
-
if (p.includes('[') || q.includes('['))
|
|
87
|
-
return undefined;
|
|
154
|
+
if (p.includes('[') || q.includes('[') || p.includes('⟦') || q.includes('⟦'))
|
|
155
|
+
return undefined; // factors are variables (and monomials of them)
|
|
88
156
|
const factors = [...(p === '1' ? [] : p.split('*')), ...(q === '1' ? [] : q.split('*'))].sort();
|
|
89
157
|
out.push({ [factors.length ? factors.join('*') : '1']: cp * cq });
|
|
90
158
|
}
|
|
@@ -172,6 +240,23 @@ export function leafConstraints(goal, path) {
|
|
|
172
240
|
if (decide(d, path) === true && decide(scale(d, -1), path) === true)
|
|
173
241
|
rows.push({ lin: sub(v(p.name), v(q.name)), label: '' }, { lin: sub(v(q.name), v(p.name)), label: '' });
|
|
174
242
|
}
|
|
243
|
+
// Each term column's rows, and the columns those rows bring in turn.
|
|
244
|
+
const seen = new Set(), todo = [...names].filter(n => n.startsWith('⟦'));
|
|
245
|
+
while (todo.length) {
|
|
246
|
+
const name = todo.pop();
|
|
247
|
+
if (seen.has(name))
|
|
248
|
+
continue;
|
|
249
|
+
seen.add(name);
|
|
250
|
+
for (const lin of termRows(name, path)) {
|
|
251
|
+
rows.push({ lin, label: '' });
|
|
252
|
+
for (const key of Object.keys(lin))
|
|
253
|
+
if (key !== '1') {
|
|
254
|
+
names.add(key);
|
|
255
|
+
if (key.startsWith('⟦') && !seen.has(key))
|
|
256
|
+
todo.push(key);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
}
|
|
175
260
|
for (const name of names)
|
|
176
261
|
rows.push({ lin: { [name]: -1 }, label: '' });
|
|
177
262
|
return rows;
|
|
@@ -300,6 +385,14 @@ const step = (r, u) => {
|
|
|
300
385
|
flags: at.flags,
|
|
301
386
|
} };
|
|
302
387
|
};
|
|
388
|
+
// The state after a step, named: each state variable a fresh name n~j equal to its term there, which later frames read,
|
|
389
|
+
// so a run of k steps is k small frames rather than a term nesting each state in the next (which grows as 3^k when a
|
|
390
|
+
// step reads its state thrice). The names are new to the goal; each equation holds of the run that took the steps.
|
|
391
|
+
const nameState = (r, state, j) => ({
|
|
392
|
+
hyps: [...r.hyps, ...state.map(n => ({ op: '=', a: { op: 'var', name: stateName(n, j) }, b: r.at.vars[n] ?? { op: 'var', name: n } }))],
|
|
393
|
+
at: { ...r.at, vars: { ...r.at.vars, ...Object.fromEntries(state.map(n => [n, { op: 'var', name: stateName(n, j) }])) } },
|
|
394
|
+
});
|
|
395
|
+
export const stateName = (n, j) => `${n}~${j}`;
|
|
303
396
|
// The conclusion read at the run's end: plain names at the last frame.
|
|
304
397
|
const atEnd = (r, c) => substC(c, r.at);
|
|
305
398
|
// The conclusion read across the run: plain names at the first frame, primed names (s', and M'[k] as a read of the map
|
|
@@ -799,8 +892,9 @@ export function stepLawObligations(machine, laws, instances, sl) {
|
|
|
799
892
|
const primes = [...state, ...maps].map(n => `${n}'`);
|
|
800
893
|
const laters = Array.from({ length: k - 1 }, (_, i) => laterNames(m, i + 2));
|
|
801
894
|
const later = laters.flatMap(l => Object.values(l.vars).map(e => e.name)), laterMaps = laters.flatMap(l => Object.values(l.maps));
|
|
895
|
+
const names = Array.from({ length: k - 1 }, (_, i) => state.map(n => stateName(n, i + 1))).flat();
|
|
802
896
|
const taken = [...m.params, ...m.inputs, ...state, ...maps, ...carried];
|
|
803
|
-
for (const p of [...primes, ...later, ...laterMaps])
|
|
897
|
+
for (const p of [...primes, ...later, ...laterMaps, ...names])
|
|
804
898
|
if (taken.includes(p))
|
|
805
899
|
throw new Error(`${m.name}: ${p} is a name of the machine and a name a step law reads`);
|
|
806
900
|
for (const key of [sl.forall, ...(sl.keys ?? [])])
|
|
@@ -822,14 +916,30 @@ export function stepLawObligations(machine, laws, instances, sl) {
|
|
|
822
916
|
if (j > 1)
|
|
823
917
|
run = reading(run, laters[j - 2].vars, laters[j - 2].maps);
|
|
824
918
|
run = step(holds(run, [...assumed(laws, instances), ...assume, ...(whens[j - 1] ? [inlineFlags(m, whens[j - 1])] : []), ...closing(m, instances)]), u);
|
|
919
|
+
if (j < k)
|
|
920
|
+
run = nameState(run, state, j);
|
|
825
921
|
}
|
|
826
922
|
return [goal(k === 1 ? `${sl.name} @step law` : `${sl.name} @${k} steps`, run, across(run, sl.then, state, maps, { inputs: {}, flags: flagsOf(m) }))];
|
|
827
923
|
}
|
|
828
924
|
// The laws' proof and every step law's tree: all must check.
|
|
829
925
|
// `perStep`: a step law's own instances, where it has them (else `instances`).
|
|
926
|
+
// Each frame's hypotheses read no name a later step's request is read by: the condition the Lean twin's proof needs
|
|
927
|
+
// (Sets.lean's checkSKStep, `avoids (szone s sl)`; plain flags are inlined here, so their names never occur), checked
|
|
928
|
+
// here alike.
|
|
830
929
|
export function checkStepLaws(m, laws, stepLaws, proof) {
|
|
831
|
-
return checkLaws(m, laws, proof.laws) && stepLaws.every(sl =>
|
|
930
|
+
return checkLaws(m, laws, proof.laws) && stepLaws.every(sl => {
|
|
931
|
+
const inst = proof.perStep?.[sl.name] ?? proof.instances, plain = m.history ? historyMachine(m).machine : m;
|
|
932
|
+
const zone = stepZone(plain, sl.steps ?? 1);
|
|
933
|
+
return [...assumed(laws, inst), ...assumed(plain.facts ?? [], inst), ...closing(plain, inst)].every(c => !mentions(c, zone))
|
|
934
|
+
&& stepLawObligations(m, laws, inst, sl).every(g => proof.trees[g.name] !== undefined && check(g, proof.trees[g.name]));
|
|
935
|
+
});
|
|
832
936
|
}
|
|
937
|
+
// The names a k-step law's run introduces past its first frame: each later step's inputs, quantified flags and their
|
|
938
|
+
// witnesses, and the state after each step but the last (stateName).
|
|
939
|
+
export const stepZone = (m, k) => [
|
|
940
|
+
...Array.from({ length: k - 1 }, (_, i) => Object.values(laterNames(m, i + 2).vars).map(e => e.name)).flat(),
|
|
941
|
+
...Array.from({ length: k - 1 }, (_, i) => Object.keys(m.state).map(n => stateName(n, i + 1))).flat(),
|
|
942
|
+
];
|
|
833
943
|
export function foldObligations(f, laws) {
|
|
834
944
|
if (![...Object.values(f.leaf), ...Object.values(f.node)].every(natural) || !laws.every(law => !law.forall && !law.keys?.length && naturalC(law.holds) && !mentions(law.holds, f.inputs)))
|
|
835
945
|
throw new Error('outside the kernel');
|
package/dist/kernel/prove.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { add, k, scale, solve, sub } from "../core/solver.js";
|
|
2
|
-
import { assumed, canon, check, checkLaws, checkStepLaws, historyMachine, leafConstraints, linear, livenessHelpful, livenessParts, obligations, plainKey, progressObligations, stepLawObligations, trueC, truth, unreachable, withProducts } from "./kernel.js";
|
|
1
|
+
import { add, k, scale, solve, sub, v } from "../core/solver.js";
|
|
2
|
+
import { assumed, canon, columnTerm, decided, stepZone, check, checkLaws, checkStepLaws, historyMachine, leafConstraints, linear, livenessHelpful, livenessParts, obligations, plainKey, progressObligations, stepLawObligations, trueC, truth, unreachable, withProducts } from "./kernel.js";
|
|
3
3
|
// The atoms the path leaves open, in evaluation order: deciding any of them makes progress.
|
|
4
4
|
function openE(e, path, out) {
|
|
5
5
|
switch (e.op) {
|
|
@@ -22,7 +22,7 @@ function openE(e, path, out) {
|
|
|
22
22
|
case 'max': {
|
|
23
23
|
openE(e.a, path, out), openE(e.b, path, out);
|
|
24
24
|
const a = linear(e.a, path), b = linear(e.b, path);
|
|
25
|
-
if (a && b &&
|
|
25
|
+
if (a && b && !decided(e, path))
|
|
26
26
|
out.push(e.op === 'sub' ? sub(b, a) : sub(a, b));
|
|
27
27
|
return;
|
|
28
28
|
}
|
|
@@ -116,12 +116,34 @@ function parse(text) {
|
|
|
116
116
|
// The budget: 200,000 nodes and, by default, a second per hypothesis (at least 20 seconds) per goal; past either the goal
|
|
117
117
|
// is left `unknown`. A goal with many instances (a law about two keys at its pairs) has that many more rows per leaf.
|
|
118
118
|
export function grow(goal, path = new Map(), budget = { nodes: 0, until: performance.now() + Math.max(20_000, 1_000 * goal.hyps.length) }) {
|
|
119
|
+
// At the root: plainly first; where the cases multiply past a budget, through cuts. The plain budget halves with each
|
|
120
|
+
// two open choices (what a plain search's cases grow by), so a long run's steps reach the cuts early.
|
|
121
|
+
if (path.size === 0 && budget.nodes === 0) {
|
|
122
|
+
const open = new Set(leafConstraints(goal, path).flatMap(r => Object.keys(r.lin)).filter(n => n.startsWith('⟦') && columnTerm(n)?.op === 'if')).size;
|
|
123
|
+
const plain = growPlain(goal, path, { nodes: 0, until: budget.until, cap: Math.max(32, Math.round(4_000 / 2 ** (open / 2))) });
|
|
124
|
+
if ('tree' in plain || plain.stuck === 'sat' || plain.why === undefined)
|
|
125
|
+
return plain;
|
|
126
|
+
const cut = withCuts(goal, path, budget);
|
|
127
|
+
if (cut)
|
|
128
|
+
return cut;
|
|
129
|
+
}
|
|
130
|
+
return growPlain(goal, path, budget);
|
|
131
|
+
}
|
|
132
|
+
function growPlain(goal, path, budget) {
|
|
133
|
+
if (budget.cap !== undefined && budget.nodes >= budget.cap)
|
|
134
|
+
return { stuck: 'unknown', path, why: 'cap' };
|
|
119
135
|
if (++budget.nodes > 200_000 || (budget.until !== undefined && performance.now() > budget.until))
|
|
120
136
|
return { stuck: 'unknown', path, why: outOfBudget(goal, budget.nodes > 200_000 ? 'nodes' : 'time', path.size) };
|
|
121
|
-
|
|
137
|
+
// Rows that read a term column only once a cut has put one on the path; before, the rows a choice-free search uses
|
|
138
|
+
// (what it saw before term columns), renumbered to the whole leaf's when a leaf is found.
|
|
139
|
+
const full = leafConstraints(goal, path);
|
|
140
|
+
const columns = [...path.values()].some(a => Object.keys(a.lin).some(key => key.startsWith('⟦')));
|
|
141
|
+
const idx = full.flatMap((r, i) => (columns || !Object.keys(r.lin).some(key => key.startsWith('⟦')) ? [i] : []));
|
|
142
|
+
const rows = idx.map(i => full[i]), at = (i) => (i < rows.length ? idx[i] : full.length + i - rows.length);
|
|
143
|
+
const leafOf = (steps, products) => ({ leaf: steps.map(st => (st.kind === 'input' ? { ...st, index: at(st.index) } : st)), ...(products ? { products: products.map(([i, j]) => [at(i), at(j)]) } : {}) });
|
|
122
144
|
const verdict = solve(rows);
|
|
123
145
|
if (verdict.kind === 'refuted')
|
|
124
|
-
return { tree:
|
|
146
|
+
return { tree: leafOf(verdict.steps), nodes: budget.nodes };
|
|
125
147
|
const open = [];
|
|
126
148
|
for (const h of goal.hyps)
|
|
127
149
|
openH(h, path, open);
|
|
@@ -148,7 +170,7 @@ export function grow(goal, path = new Map(), budget = { nodes: 0, until: perform
|
|
|
148
170
|
pairs.splice(300);
|
|
149
171
|
const product = solve(withProducts(rows, pairs));
|
|
150
172
|
if (product.kind === 'refuted')
|
|
151
|
-
return { tree:
|
|
173
|
+
return { tree: leafOf(product.steps, pairs), nodes: budget.nodes };
|
|
152
174
|
// Degree three: each row times each product of two variables (x·y >= 0 is the pair of -x <= 0 and -y <= 0).
|
|
153
175
|
const degree = Math.max(...rows.flatMap(r => Object.keys(r.lin).map(key => (key === '1' ? 0 : key.split('*').length))));
|
|
154
176
|
if (degree >= 3) {
|
|
@@ -159,7 +181,7 @@ export function grow(goal, path = new Map(), budget = { nodes: 0, until: perform
|
|
|
159
181
|
cubes.push([rows.length + s, i]);
|
|
160
182
|
const third = solve(withProducts(rows, [...sq, ...cubes]));
|
|
161
183
|
if (third.kind === 'refuted')
|
|
162
|
-
return { tree:
|
|
184
|
+
return { tree: leafOf(third.steps, [...sq, ...cubes]), nodes: budget.nodes };
|
|
163
185
|
}
|
|
164
186
|
const factors = [...new Set(rows.flatMap(r => Object.keys(r.lin).filter(key => key.includes('*')).flatMap(key => key.split('*'))))];
|
|
165
187
|
const fresh = (lin) => !path.has(canon(lin)) && !path.has(canon(add(k(1), scale(lin, -1))));
|
|
@@ -174,10 +196,10 @@ export function grow(goal, path = new Map(), budget = { nodes: 0, until: perform
|
|
|
174
196
|
if (!split)
|
|
175
197
|
return { stuck: verdict.kind === 'sat' ? 'sat' : 'unknown', ...(verdict.kind === 'sat' ? { witness: verdict.witness } : {}), path };
|
|
176
198
|
const id = canon(split);
|
|
177
|
-
const yes =
|
|
199
|
+
const yes = growPlain(goal, new Map(path).set(id, { lin: split, holds: true }), budget);
|
|
178
200
|
if (!('tree' in yes))
|
|
179
201
|
return yes;
|
|
180
|
-
const no =
|
|
202
|
+
const no = growPlain(goal, new Map(path).set(id, { lin: split, holds: false }), budget);
|
|
181
203
|
if (!('tree' in no))
|
|
182
204
|
return no;
|
|
183
205
|
return { tree: { split, yes: yes.tree, no: no.tree }, nodes: budget.nodes };
|
|
@@ -189,6 +211,90 @@ function outOfBudget(goal, limit, depth) {
|
|
|
189
211
|
`Each case a step decides multiplies the cases to refute, so ${steps > 1 ? `a law over ${steps} steps` : 'many interacting cases'} can outgrow it: ` +
|
|
190
212
|
'state the law over fewer steps (a one-step law and an invariant often give the same), or split it into laws about fewer conditions';
|
|
191
213
|
}
|
|
214
|
+
// Cuts: where each of many choices (a step's case, over a run of k steps) multiplies the cases, a fact about each
|
|
215
|
+
// choice's value that holds whichever way it goes is split on first: its failing side is refuted by deciding that
|
|
216
|
+
// choice alone, and the rest of the proof assumes it, so the tree is a chain of small refutations instead of every
|
|
217
|
+
// combination. For a term column t = if c then A else B the candidates are t ≤ A, t ≤ B, t ≥ A and t ≥ B, innermost
|
|
218
|
+
// choices first; a cut is only a split, so the kernel checks it like any other.
|
|
219
|
+
function withCuts(goal, path, budget) {
|
|
220
|
+
const rows = leafConstraints(goal, path);
|
|
221
|
+
const columns = [...new Set(rows.flatMap(r => Object.keys(r.lin)).filter(n => n.startsWith('⟦')))]
|
|
222
|
+
.map(n => ({ n, e: columnTerm(n) })).filter(x => x.e.op === 'if' && !decided(x.e, path))
|
|
223
|
+
.sort((a, b) => Number(a.n.slice(1, -1)) - Number(b.n.slice(1, -1))); // a term's parts get their ids before it: innermost first
|
|
224
|
+
const chain = [];
|
|
225
|
+
let at = new Map(path);
|
|
226
|
+
for (const { n, e } of columns) {
|
|
227
|
+
if (e.op !== 'if')
|
|
228
|
+
continue;
|
|
229
|
+
const a = linear(e.then, at), b = linear(e.else, at), focus = topAtoms(e.c, at);
|
|
230
|
+
if (!a || !b || !focus.length)
|
|
231
|
+
continue;
|
|
232
|
+
for (const lemma of [sub(v(n), a), sub(v(n), b), sub(a, v(n)), sub(b, v(n))]) {
|
|
233
|
+
const id = canon(lemma);
|
|
234
|
+
if (at.has(id) || Object.keys(lemma).every(key => key === '1'))
|
|
235
|
+
continue;
|
|
236
|
+
const no = growFocused(goal, new Map(at).set(id, { lin: lemma, holds: false }), focus, { nodes: 0 });
|
|
237
|
+
if ('tree' in no) {
|
|
238
|
+
chain.push({ split: lemma, no: no.tree });
|
|
239
|
+
at = new Map(at).set(id, { lin: lemma, holds: true });
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
if (!chain.length)
|
|
244
|
+
return undefined;
|
|
245
|
+
const rest = growPlain(goal, at, budget);
|
|
246
|
+
if (!('tree' in rest))
|
|
247
|
+
return rest;
|
|
248
|
+
return { tree: chain.reduceRight((acc, c) => ({ split: c.split, yes: acc, no: c.no }), rest.tree), nodes: budget.nodes };
|
|
249
|
+
}
|
|
250
|
+
// A condition's own comparisons, each a split atom, read with term columns for whatever choices are inside it.
|
|
251
|
+
function topAtoms(c, path) {
|
|
252
|
+
if (c.op === 'not')
|
|
253
|
+
return topAtoms(c.x, path);
|
|
254
|
+
if (c.op === 'and' || c.op === 'or')
|
|
255
|
+
return c.xs.flatMap(y => topAtoms(y, path));
|
|
256
|
+
if (c.op === 'flag' || c.op === 'all')
|
|
257
|
+
return [];
|
|
258
|
+
const a = linear(c.a, path), b = linear(c.b, path);
|
|
259
|
+
if (!a || !b)
|
|
260
|
+
return [];
|
|
261
|
+
const atoms = c.op === '<=' ? [sub(a, b)] : c.op === '<' ? [add(sub(a, b), k(1))] : [sub(a, b), sub(b, a)];
|
|
262
|
+
return atoms.filter(l => Object.keys(l).some(key => key !== '1') && !path.has(canon(l)) && !path.has(canon(add(k(1), scale(l, -1)))));
|
|
263
|
+
}
|
|
264
|
+
// A refutation that splits only on these atoms (a cut's failing side), within a small budget.
|
|
265
|
+
function growFocused(goal, path, focus, budget, near = focus) {
|
|
266
|
+
if (++budget.nodes > 64)
|
|
267
|
+
return { stuck: 'unknown', path };
|
|
268
|
+
const verdict = solveNear(leafConstraints(goal, path), [...near, ...[...path.values()].slice(-1).map(a => a.lin)]);
|
|
269
|
+
if (verdict.kind === 'refuted')
|
|
270
|
+
return { tree: { leaf: verdict.steps }, nodes: budget.nodes };
|
|
271
|
+
const split = focus.find(l => !path.has(canon(l)) && !path.has(canon(add(k(1), scale(l, -1)))));
|
|
272
|
+
if (!split)
|
|
273
|
+
return { stuck: verdict.kind === 'sat' ? 'sat' : 'unknown', path };
|
|
274
|
+
const id = canon(split);
|
|
275
|
+
const yes = growFocused(goal, new Map(path).set(id, { lin: split, holds: true }), focus, budget, near);
|
|
276
|
+
if (!('tree' in yes))
|
|
277
|
+
return yes;
|
|
278
|
+
const no = growFocused(goal, new Map(path).set(id, { lin: split, holds: false }), focus, budget, near);
|
|
279
|
+
if (!('tree' in no))
|
|
280
|
+
return no;
|
|
281
|
+
return { tree: { split, yes: yes.tree, no: no.tree }, nodes: budget.nodes };
|
|
282
|
+
}
|
|
283
|
+
// A refutation from the rows near some facts (the rows reachable from their variables in two hops, each row joining the
|
|
284
|
+
// variables it reads), its inputs renumbered to the whole leaf's. A cut about one step of a long run needs that step's
|
|
285
|
+
// rows, not the run's; a cut it misses is only a cut not made (the plain search still follows).
|
|
286
|
+
function solveNear(rows, seeds) {
|
|
287
|
+
const reach = new Set(seeds.flatMap(l => Object.keys(l).filter(key => key !== '1')));
|
|
288
|
+
for (let hop = 0; hop < 2; hop++)
|
|
289
|
+
for (const r of rows) {
|
|
290
|
+
const keys = Object.keys(r.lin).filter(key => key !== '1');
|
|
291
|
+
if (keys.some(key => reach.has(key)))
|
|
292
|
+
keys.forEach(key => reach.add(key));
|
|
293
|
+
}
|
|
294
|
+
const picked = rows.flatMap((r, i) => (Object.keys(r.lin).every(key => key === '1' || reach.has(key)) ? [i] : []));
|
|
295
|
+
const near = solve(picked.map(i => rows[i]));
|
|
296
|
+
return near.kind === 'refuted' ? { kind: 'refuted', steps: near.steps.map(st => (st.kind === 'input' ? { ...st, index: picked[st.index] } : st)) } : near;
|
|
297
|
+
}
|
|
192
298
|
// Integer completeness for bounded systems: when elimination is inexact and finds no witness, halve a variable's range
|
|
193
299
|
// (x <= mid or x >= mid + 1). Each half is a split like any other, so the kernel checks it the same way; a variable
|
|
194
300
|
// pinned to one value becomes an equality, which the solver substitutes exactly.
|
|
@@ -370,6 +476,42 @@ function match(pattern, term, x) {
|
|
|
370
476
|
const witnesses = (m) => Object.entries(m.step.flags ?? {}).flatMap(([f, c]) => (c.op === 'all' ? [{ op: 'var', name: `w?${f}` }] : []));
|
|
371
477
|
// The keys each quantified flag is assumed at (instances under ?f).
|
|
372
478
|
const flagInstances = (m, keys) => Object.fromEntries(Object.entries(m.step.flags ?? {}).flatMap(([f, c]) => (c.op === 'all' ? [[`?${f}`, keys]] : [])));
|
|
479
|
+
// The instances some goals need, for every entry point: the every-key laws and the quantified flags at `seed` (the keys
|
|
480
|
+
// the step and the laws read, when known before any goal is built), then twice round: the facts at every key the goals
|
|
481
|
+
// built so far read a function at, and, where the seed doesn't cover them (no seed, positions that shift, a law about
|
|
482
|
+
// several keys), the every-key laws and flags at every key they read a map at (a law about several keys at tuples
|
|
483
|
+
// relating `focus`, the goals' own keys).
|
|
484
|
+
function instancesFor(m, laws, build, opts = {}) {
|
|
485
|
+
let inst = opts.start ?? (opts.seed ? { ...Object.fromEntries(laws.filter(l => l.forall).map(l => [l.name, l.keys?.length ? [] : opts.seed])), ...flagInstances(m, opts.seed) } : {});
|
|
486
|
+
const reads = opts.reads ?? (!opts.seed || shifts(m) || laws.some(l => l.keys?.length));
|
|
487
|
+
for (let round = 0; round < 2; round++) {
|
|
488
|
+
if (m.facts?.length)
|
|
489
|
+
inst = withFacts(m, inst, fnKeys(m, build(inst)));
|
|
490
|
+
if (reads) {
|
|
491
|
+
const keys = mapKeys(build(inst));
|
|
492
|
+
inst = union(withReads(laws, inst, keys, opts.focus ?? []), flagInstances(m, keys));
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
return inst;
|
|
496
|
+
}
|
|
497
|
+
// Instances without the keys that read any of these names (a law about several keys loses each tuple that does).
|
|
498
|
+
const avoiding = (inst, names, laws) => {
|
|
499
|
+
const bad = (e) => names.some(n => JSON.stringify(e).includes(`"name":${JSON.stringify(n)}`));
|
|
500
|
+
return Object.fromEntries(Object.entries(inst).map(([n, keys]) => {
|
|
501
|
+
const arity = (laws.find(l => l.name === n)?.keys?.length ?? 0) + 1;
|
|
502
|
+
const tuples = Array.from({ length: Math.floor(keys.length / arity) }, (_, t) => keys.slice(t * arity, (t + 1) * arity));
|
|
503
|
+
return [n, tuples.filter(tuple => !tuple.some(bad)).flat()];
|
|
504
|
+
}));
|
|
505
|
+
};
|
|
506
|
+
// Two sets of instances together: each name at the keys of both.
|
|
507
|
+
const union = (a, b) => {
|
|
508
|
+
const out = { ...a };
|
|
509
|
+
for (const [n, keys] of Object.entries(b)) {
|
|
510
|
+
const seen = new Set((out[n] ?? []).map(e => JSON.stringify(e)));
|
|
511
|
+
out[n] = [...(out[n] ?? []), ...keys.filter(e => !seen.has(JSON.stringify(e)))];
|
|
512
|
+
}
|
|
513
|
+
return out;
|
|
514
|
+
};
|
|
373
515
|
const someKeys = (c, positive = true) => c.op === 'all' ? [...(positive ? [] : [c]), ...someKeys(c.c, positive)] : c.op === 'not' ? someKeys(c.x, !positive) : c.op === 'and' || c.op === 'or' ? c.xs.flatMap(y => someKeys(y, positive)) : [];
|
|
374
516
|
function atWitness(c, picks, positive = true) {
|
|
375
517
|
switch (c.op) {
|
|
@@ -434,23 +576,14 @@ export function proveLaws(m, laws) {
|
|
|
434
576
|
return { ...proveLawsAsGiven(m, out.items), laws: out.items };
|
|
435
577
|
}
|
|
436
578
|
function proveLawsAsGiven(m, laws) {
|
|
437
|
-
|
|
438
|
-
const instances = {};
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
const goals = obligations(m, laws, instances);
|
|
446
|
-
laws.forEach((law, i) => { instances[law.name] = withFacts(m, instances[law.name], fnKeys(m, goals.slice(2 * i, 2 * i + 2))); });
|
|
447
|
-
}
|
|
448
|
-
// Where positions shift (a write at every key reading its key at k ∸ 1 or k + 1) or a law is about two keys, the
|
|
449
|
-
// every-key laws also at each key the goals read a map at, once round.
|
|
450
|
-
if (shifts(m) || laws.some(l => l.keys?.length)) {
|
|
451
|
-
const goals = obligations(m, laws, instances);
|
|
452
|
-
laws.forEach((law, i) => { instances[law.name] = withReads(laws, instances[law.name], mapKeys(goals.slice(2 * i, 2 * i + 2)), [...(law.forall ? [`${law.forall}#`] : []), ...(law.keys ?? [])]); });
|
|
453
|
-
}
|
|
579
|
+
// Each law's two goals (start and step) get their own instances, seeded with its own keys.
|
|
580
|
+
const keys = [...readKeys(m, laws), ...witnesses(m)], instances = {};
|
|
581
|
+
laws.forEach((law, i) => {
|
|
582
|
+
instances[law.name] = instancesFor(m, laws, inst => obligations(m, laws, { ...instances, [law.name]: inst }).slice(2 * i, 2 * i + 2), {
|
|
583
|
+
seed: [...keys, ...(law.forall ? [{ op: 'var', name: `${law.forall}#` }] : []), ...(law.keys ?? []).map((name) => ({ op: 'var', name }))],
|
|
584
|
+
focus: [...(law.forall ? [`${law.forall}#`] : []), ...(law.keys ?? [])],
|
|
585
|
+
});
|
|
586
|
+
});
|
|
454
587
|
const trees = {};
|
|
455
588
|
let nodes = 0, leaves = 0;
|
|
456
589
|
for (const goal of obligations(m, laws, instances)) {
|
|
@@ -483,16 +616,19 @@ function proveStepLawsAsGiven(m, laws, stepLaws, base) {
|
|
|
483
616
|
if (!base.proof)
|
|
484
617
|
return { proven: false, nodes: base.nodes, leaves: base.leaves, checked: false };
|
|
485
618
|
const plain = m.history ? historyMachine(m).machine : m;
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
619
|
+
// Each step law its own instances, from its own goal: the keys the step and the laws read, and its own (a k-step
|
|
620
|
+
// law's later frames read keys no other step law has); where positions shift or a law is about several keys, the
|
|
621
|
+
// keys its goal reads a map at too. A frame's hypotheses read no name the run introduces later (kernel.ts's
|
|
622
|
+
// stepZone), so a k-step law drops the keys that read one.
|
|
623
|
+
const shared = [...readKeys(plain, laws), ...witnesses(plain)], named = [...laws, ...(plain.facts ?? [])], reads = shifts(plain) || laws.some(l => l.keys?.length);
|
|
624
|
+
const own = (sl) => [...[sl.then, ...(sl.when === undefined ? [] : Array.isArray(sl.when) ? sl.when : [sl.when])].flatMap(readKeysC),
|
|
625
|
+
...[sl.forall, ...(sl.keys ?? [])].flatMap(key => (key ? [{ op: 'var', name: key }] : []))];
|
|
492
626
|
const perStep = {};
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
627
|
+
for (const sl of stepLaws) {
|
|
628
|
+
const zone = stepZone(plain, sl.steps ?? 1), keep = (inst) => avoiding(inst, zone, named);
|
|
629
|
+
perStep[sl.name] = keep(instancesFor(plain, laws, inst => stepLawObligations(m, laws, keep(inst), sl), { seed: [...shared, ...own(sl)], focus: [sl.forall, ...(sl.keys ?? [])].flatMap(k => (k ? [k] : [])), reads }));
|
|
630
|
+
}
|
|
631
|
+
const instances = perStep[stepLaws[0]?.name ?? ''] ?? {};
|
|
496
632
|
const trees = {};
|
|
497
633
|
let nodes = base.nodes, leaves = base.leaves;
|
|
498
634
|
for (const sl of stepLaws)
|
|
@@ -514,10 +650,7 @@ export function proveProgress(m, laws, p) {
|
|
|
514
650
|
if (!base.proven || !base.checked)
|
|
515
651
|
return { proven: false, nodes: base.nodes, leaves: base.leaves, ...(base.failed ? { failed: base.failed } : {}), checked: false };
|
|
516
652
|
const ls = base.laws ?? laws;
|
|
517
|
-
|
|
518
|
-
instances = { ...instances, ...withReads(ls, instances, mapKeys(progressObligations(m, ls, instances, p)), []) };
|
|
519
|
-
if (m.facts?.length)
|
|
520
|
-
instances = withFacts(m, instances, fnKeys(m, progressObligations(m, ls, instances, p)));
|
|
653
|
+
const instances = instancesFor(m, ls, inst => progressObligations(m, ls, inst, p));
|
|
521
654
|
const trees = {};
|
|
522
655
|
let nodes = base.nodes, leaves = base.leaves;
|
|
523
656
|
for (const goal of progressObligations(m, ls, instances, p)) {
|
|
@@ -614,21 +747,12 @@ export function concretize(goal, bound = 12) {
|
|
|
614
747
|
// The facts a goal needs, as hypotheses: each fact about every key at the keys the goal reads a function at (and at
|
|
615
748
|
// the keys those facts read, a second round), each plain fact as it is.
|
|
616
749
|
export function factsFor(m, goal) {
|
|
617
|
-
|
|
618
|
-
for (let round = 0; round < 2; round++)
|
|
619
|
-
instances = withFacts(m, instances, fnKeys(m, [{ ...goal, hyps: [...assumed(m.facts ?? [], instances), ...goal.hyps] }]));
|
|
620
|
-
return assumed(m.facts ?? [], instances);
|
|
750
|
+
return assumed(m.facts ?? [], instancesFor(m, [], inst => [{ ...goal, hyps: [...assumed(m.facts ?? [], inst), ...goal.hyps] }]));
|
|
621
751
|
}
|
|
622
752
|
// A goal built from its instances (an exporter's goal, shaped as its Lean twin builds it): the every-key laws, the
|
|
623
753
|
// quantified flags and the facts assumed at every key it reads a map or function at, then a tree, checked.
|
|
624
754
|
export function proveBuilt(m, laws, build) {
|
|
625
|
-
|
|
626
|
-
for (let round = 0; round < 2; round++) {
|
|
627
|
-
const g = build(instances), keys = mapKeys([g]);
|
|
628
|
-
instances = { ...withReads(laws, instances, keys, []), ...flagInstances(m, keys) };
|
|
629
|
-
if (m.facts?.length)
|
|
630
|
-
instances = withFacts(m, instances, fnKeys(m, [build(instances)]));
|
|
631
|
-
}
|
|
755
|
+
const instances = instancesFor(m, laws, inst => [build(inst)]);
|
|
632
756
|
const goal = build(instances), out = grow(goal);
|
|
633
757
|
if (!('tree' in out) || !check(goal, out.tree))
|
|
634
758
|
return { proven: false };
|
package/dist/lean-kernel/emit.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { atMost, n, not, pointWrite } from "../core/machine.js";
|
|
2
2
|
import { add, k, scale, sub } from "../core/solver.js";
|
|
3
|
-
import { canon, historyMachine, foldObligations, leafConstraints, linear, livenessHelpful, livenessParts, livenessRanks, loopObligations, trueC, obligations, progressObligations, stepLawObligations, substC, withProducts } from "../kernel/kernel.js";
|
|
3
|
+
import { canon, columnTerm, stateName, termColumn, historyMachine, foldObligations, leafConstraints, linear, livenessHelpful, livenessParts, livenessRanks, loopObligations, trueC, obligations, progressObligations, stepLawObligations, substC, withProducts } from "../kernel/kernel.js";
|
|
4
4
|
import { grow, proveBuilt, proveLaws, proveStepLaws } from "../kernel/prove.js";
|
|
5
5
|
import { refinement, refinementSides } from "../core/compose.js";
|
|
6
6
|
import { leanTerms } from "./lean-terms.js";
|
|
@@ -52,11 +52,13 @@ export function encode(ex, tamper = false, dropStep = true, globalVars, globalMa
|
|
|
52
52
|
// Columns: every variable of the hypotheses, then every cell any leaf uses (and the cells inside their keys),
|
|
53
53
|
// shallower cells first so a key reads only earlier columns.
|
|
54
54
|
const vars = globalVars ?? [...new Set(goal.hyps.flatMap(varsOf))];
|
|
55
|
-
const cells = new Set();
|
|
55
|
+
const cells = new Set(), terms = new Set();
|
|
56
56
|
const addCell = (name) => { if (cells.has(name))
|
|
57
57
|
return; cells.add(name); for (const key of Object.keys(cellKey(name)))
|
|
58
58
|
if (key.includes('['))
|
|
59
|
-
addCell(key);
|
|
59
|
+
addCell(key);
|
|
60
|
+
else if (key.startsWith('⟦'))
|
|
61
|
+
terms.add(key); };
|
|
60
62
|
// Monomials: every one a leaf's rows or products use, or that a product term has under the leaf's path.
|
|
61
63
|
const monos = new Set();
|
|
62
64
|
const subterms = (c) => {
|
|
@@ -77,11 +79,29 @@ export function encode(ex, tamper = false, dropStep = true, globalVars, globalMa
|
|
|
77
79
|
wc(c);
|
|
78
80
|
return out2;
|
|
79
81
|
};
|
|
82
|
+
// Every choice in the hypotheses has its column, whether or not a leaf's rows keep it (a column can cancel, T − T,
|
|
83
|
+
// yet Lean's linear form of T still needs it): each distinct subterm once, as a run's states are shared.
|
|
84
|
+
const seenTerms = new WeakSet();
|
|
85
|
+
const choices = (t) => {
|
|
86
|
+
if (seenTerms.has(t))
|
|
87
|
+
return;
|
|
88
|
+
seenTerms.add(t);
|
|
89
|
+
if (t.op === 'sub' || t.op === 'min' || t.op === 'max' || t.op === 'if')
|
|
90
|
+
terms.add(termColumn(t));
|
|
91
|
+
for (const val of Object.values(t))
|
|
92
|
+
if (Array.isArray(val))
|
|
93
|
+
val.forEach(x => choices(x));
|
|
94
|
+
else if (typeof val === 'object' && val !== null)
|
|
95
|
+
choices(val);
|
|
96
|
+
};
|
|
97
|
+
goal.hyps.forEach(choices);
|
|
80
98
|
leaves(tree, new Map(), (leaf, path) => {
|
|
81
99
|
for (const r of withProducts(leafConstraints(goal, path), leaf.products ?? []))
|
|
82
100
|
for (const key of Object.keys(r.lin)) {
|
|
83
101
|
if (key.includes('['))
|
|
84
102
|
addCell(key);
|
|
103
|
+
else if (key.startsWith('⟦'))
|
|
104
|
+
terms.add(key);
|
|
85
105
|
else if (key.includes('*'))
|
|
86
106
|
monos.add(key);
|
|
87
107
|
}
|
|
@@ -93,7 +113,8 @@ export function encode(ex, tamper = false, dropStep = true, globalVars, globalMa
|
|
|
93
113
|
monos.add(key);
|
|
94
114
|
});
|
|
95
115
|
const depth = (name) => name.split('[').length;
|
|
96
|
-
|
|
116
|
+
// Term columns (open choices, each standing for its term's value) before the cells, whose keys may read them.
|
|
117
|
+
const cols = [...vars, ...[...monos].sort((a, b) => a.split('*').length - b.split('*').length), ...[...terms].sort((a, b) => Number(a.slice(1, -1)) - Number(b.slice(1, -1))), ...[...cells].sort((a, b) => depth(a) - depth(b))];
|
|
97
118
|
const col = new Map(cols.map((name, j) => [name, j]));
|
|
98
119
|
const maps = globalMaps ?? [...new Set([...[...cells].map(cellMap), ...goal.hyps.flatMap(h => [...JSON.stringify(h).matchAll(/"op":"get","map":"([^"]+)"/g)].map(m => m[1]))])];
|
|
99
120
|
const dense = (lin) => {
|
|
@@ -110,11 +131,12 @@ export function encode(ex, tamper = false, dropStep = true, globalVars, globalMa
|
|
|
110
131
|
return coefs;
|
|
111
132
|
};
|
|
112
133
|
const row = (lin) => `⟨${list(dense(lin).map(int))}, ${int(lin['1'] ?? 0)}⟩`;
|
|
113
|
-
const tab = cols.map(name => (name.includes('[') ? `.cell ${maps.indexOf(cellMap(name))} ${row(cellKey(name))}`
|
|
114
|
-
: name.includes('*') ? `.mono ${list(name.split('*').map(f => { const j = vars.indexOf(f); if (j < 0)
|
|
115
|
-
throw new Error(`${ex.name}: factor ${f}`); return j; }).sort((a, b) => a - b).map(String))}`
|
|
116
|
-
: `.var ${vars.indexOf(name)}`));
|
|
117
134
|
const { e, c } = leanTerms(vars, maps, ex.name);
|
|
135
|
+
const tab = cols.map(name => (name.includes('[') ? `.cell ${maps.indexOf(cellMap(name))} ${row(cellKey(name))}`
|
|
136
|
+
: name.startsWith('⟦') ? `.term (${e(columnTerm(name))})`
|
|
137
|
+
: name.includes('*') ? `.mono ${list(name.split('*').map(f => { const j = vars.indexOf(f); if (j < 0)
|
|
138
|
+
throw new Error(`${ex.name}: factor ${f}`); return j; }).sort((a, b) => a - b).map(String))}`
|
|
139
|
+
: `.var ${vars.indexOf(name)}`));
|
|
118
140
|
let leafNo = 0;
|
|
119
141
|
const lean = (t, path) => {
|
|
120
142
|
if (!('leaf' in t)) {
|
|
@@ -504,7 +526,8 @@ def ${id}_step${j} : SKStep := ⟨⟨${list(Array.from({ length: kk }, (_, s2) =
|
|
|
504
526
|
${list(qinsts(inst))},
|
|
505
527
|
${list(finsts(inst))},
|
|
506
528
|
${list(pinsts(inst))},
|
|
507
|
-
[]
|
|
529
|
+
[],
|
|
530
|
+
${list(Array.from({ length: kk - 1 }, (_, s2) => list(Object.keys(m.state).map(n2 => `(${ix(n2)}, ${ix(stateName(n2, s2 + 1))})`))))}⟩
|
|
508
531
|
theorem ${id}_step${j}_holds : SKStepHolds ${id}_machine ${id}_step${j} :=
|
|
509
532
|
skstep_sound ${id}_machine ${id}_proof ${id}_step${j} ${id}_check (by decide +kernel)`;
|
|
510
533
|
} }));
|
|
@@ -625,7 +648,8 @@ theorem ${id}_orig_laws : ∀ ρ M, SReach ${id}_ghosted.orig ρ M → ∀ e ∈
|
|
|
625
648
|
const allowGoals = [...allowed.values()].flatMap(a => (a ? [a.out.goal] : []));
|
|
626
649
|
const vars = [...new Set([...m.params, ...m.inputs, ...Object.keys(m.state), ...keysOf, ...keysOf.map(k2 => `${k2}#`), '#', ...flags.map(([f]) => `?${f}`),
|
|
627
650
|
...quantified.map(f => `w?${f}`), ...boundKeys(m), ...(opts.tamper ? boundKeys(opts.tamper.machine) : []), ...stepLaws.flatMap(sl => (sl.forall ? [sl.forall] : [])), ...(opts.free ?? []),
|
|
628
|
-
...primes, ...later, ...
|
|
651
|
+
...primes, ...later, ...Array.from({ length: k - 1 }, (_, j) => Object.keys(m.state).map(n2 => stateName(n2, j + 1))).flat(),
|
|
652
|
+
...[...goalsL, ...goalsS, ...allowGoals].flatMap(g => g.hyps.flatMap(varsOf))])];
|
|
629
653
|
const baseMaps = [...new Set([...Object.keys(m.maps ?? {}), ...Object.keys(m.step.maps ?? {}), ...Object.keys(m.fns ?? {})])];
|
|
630
654
|
const mapNames = [...new Set([...baseMaps, ...carried, ...(allSteps.length ? Object.keys(m.maps ?? {}).map(mp => `${mp}'`) : []), ...laterMaps,
|
|
631
655
|
...[...JSON.stringify([...goalsL, ...goalsS, ...allowGoals]).matchAll(/"op":"get","map":"([^"]+)"/g)].map(x2 => x2[1])])];
|