gdp-wired 0.11.0 → 0.12.1
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 -2
- package/dist/core/compose.js +1 -1
- package/dist/core/dsl.d.ts +1 -1
- package/dist/core/dsl.js +18 -9
- package/dist/core/machine.d.ts +1 -0
- package/dist/core/machine.js +52 -12
- package/dist/core/solver.d.ts +12 -0
- package/dist/core/solver.js +68 -17
- package/dist/kernel/kernel.d.ts +19 -3
- package/dist/kernel/kernel.js +104 -26
- package/dist/kernel/prove.js +80 -38
- package/dist/lean-kernel/emit.js +44 -19
- package/dist/lean-kernel/library.js +6 -6
- package/package.json +2 -2
package/dist/kernel/kernel.js
CHANGED
|
@@ -332,6 +332,9 @@ export function facts(c, path, positive = true) {
|
|
|
332
332
|
}
|
|
333
333
|
// What a leaf's derivation must refute.
|
|
334
334
|
export function leafConstraints(goal, path) { return leafRows(goal, path, h => facts(h, path)); }
|
|
335
|
+
// The same rows, for the prover's search: each hypothesis' facts as they were at an earlier path of the goal's where
|
|
336
|
+
// every decision they read was the same (facts read the path only through those decisions), and found afresh, with
|
|
337
|
+
// what they read recorded, otherwise. The kernel's check never uses it: it rebuilds every row as leafConstraints does.
|
|
335
338
|
const factsSeen = new WeakMap();
|
|
336
339
|
export function leafConstraintsReusing(goal, path) {
|
|
337
340
|
let byHyp = factsSeen.get(goal);
|
|
@@ -340,26 +343,56 @@ export function leafConstraintsReusing(goal, path) {
|
|
|
340
343
|
return leafRows(goal, path, h => {
|
|
341
344
|
let seen = byHyp.get(h);
|
|
342
345
|
if (!seen)
|
|
343
|
-
byHyp.set(h, (seen =
|
|
346
|
+
byHyp.set(h, (seen = {}));
|
|
344
347
|
return reusing(seen, path, p => facts(h, p));
|
|
345
348
|
});
|
|
346
349
|
}
|
|
350
|
+
// Past `reuseKeep` values a trie starts afresh: a long search would otherwise keep every value it ever found.
|
|
351
|
+
const reuseKeep = 16;
|
|
352
|
+
function remember(seen, reads, out) {
|
|
353
|
+
if ((seen.size = (seen.size ?? 0) + 1) > reuseKeep) {
|
|
354
|
+
seen.root = undefined;
|
|
355
|
+
seen.size = 1;
|
|
356
|
+
}
|
|
357
|
+
const leaf = { reads, out };
|
|
358
|
+
let node = seen.root, parent, via;
|
|
359
|
+
while (node && 'key' in node) {
|
|
360
|
+
// A value that read otherwise than the trie says (none does: the decisions are deterministic) is not kept.
|
|
361
|
+
if (reads[node.at]?.[0] !== node.key)
|
|
362
|
+
return;
|
|
363
|
+
parent = node;
|
|
364
|
+
via = reads[node.at][1];
|
|
365
|
+
node = node.next.get(via);
|
|
366
|
+
}
|
|
367
|
+
const put = (n) => { if (parent)
|
|
368
|
+
parent.next.set(via, n);
|
|
369
|
+
else
|
|
370
|
+
seen.root = n; };
|
|
371
|
+
if (!node) {
|
|
372
|
+
put(leaf);
|
|
373
|
+
return;
|
|
374
|
+
}
|
|
375
|
+
const i = node.reads.findIndex((d, j) => reads[j]?.[0] !== d[0] || reads[j][1] !== d[1]);
|
|
376
|
+
if (i < 0 || reads[i]?.[0] !== node.reads[i][0])
|
|
377
|
+
return;
|
|
378
|
+
put({ at: i, key: node.reads[i][0], next: new Map([[node.reads[i][1], node], [reads[i][1], leaf]]) });
|
|
379
|
+
}
|
|
347
380
|
export function reusing(seen, path, f) {
|
|
348
|
-
|
|
349
|
-
|
|
381
|
+
let node = seen.root;
|
|
382
|
+
while (node && 'key' in node)
|
|
383
|
+
node = node.next.get(path.get(node.key)?.holds);
|
|
384
|
+
if (node && node.reads.every(([key, holds]) => path.get(key)?.holds === holds)) {
|
|
350
385
|
if (recording)
|
|
351
|
-
for (const [key, holds] of
|
|
386
|
+
for (const [key, holds] of node.reads)
|
|
352
387
|
recording.set(key, holds);
|
|
353
|
-
return
|
|
388
|
+
return node.out;
|
|
354
389
|
}
|
|
355
390
|
const outer = recording, mode = reuse;
|
|
356
391
|
recording = new Map();
|
|
357
392
|
reuse = true;
|
|
358
393
|
try {
|
|
359
394
|
const out = f(new Map(path));
|
|
360
|
-
seen
|
|
361
|
-
if (seen.length > 16)
|
|
362
|
-
seen.pop();
|
|
395
|
+
remember(seen, [...recording], out);
|
|
363
396
|
return out;
|
|
364
397
|
}
|
|
365
398
|
finally {
|
|
@@ -377,12 +410,12 @@ export function reusing(seen, path, f) {
|
|
|
377
410
|
let reuse = false;
|
|
378
411
|
const linearSeen = new WeakMap(), comparisonSeen = new WeakMap(), truthSeen = new WeakMap();
|
|
379
412
|
const seenOf = (m, k) => { let s = m.get(k); if (!s)
|
|
380
|
-
m.set(k, (s =
|
|
413
|
+
m.set(k, (s = {})); return s; };
|
|
381
414
|
function leafRows(goal, path, factsOf) {
|
|
382
|
-
const rows = [...path.values()].map(a => atomOf(a.lin, a.holds));
|
|
383
|
-
for (const h of goal.hyps)
|
|
415
|
+
const rows = [...path.values()].map((a, i) => ({ ...atomOf(a.lin, a.holds), src: { atom: i } }));
|
|
416
|
+
for (const [i, h] of goal.hyps.entries())
|
|
384
417
|
for (const lin of factsOf(h))
|
|
385
|
-
rows.push({ lin, label: '' });
|
|
418
|
+
rows.push({ lin, label: '', src: { fact: i } });
|
|
386
419
|
const names = new Set(rows.flatMap(r => Object.keys(r.lin).filter(key => key !== '1')));
|
|
387
420
|
// Two reads of one map whose keys the path proves equal are equal.
|
|
388
421
|
const cells = [...names].filter(name => name.includes('[')).map(name => ({ name, map: name.slice(0, name.indexOf('[')), key: keyOf(name) }));
|
|
@@ -392,7 +425,7 @@ function leafRows(goal, path, factsOf) {
|
|
|
392
425
|
continue;
|
|
393
426
|
const d = sub(p.key, q.key);
|
|
394
427
|
if (decide(d, path) === true && decide(scale(d, -1), path) === true)
|
|
395
|
-
rows.push({ lin: sub(v(p.name), v(q.name)), label: '' }, { lin: sub(v(q.name), v(p.name)), label: '' });
|
|
428
|
+
rows.push({ lin: sub(v(p.name), v(q.name)), label: '', src: { pair: [p.name, q.name] } }, { lin: sub(v(q.name), v(p.name)), label: '', src: { pair: [p.name, q.name] } });
|
|
396
429
|
}
|
|
397
430
|
// Each term column's rows, and the columns those rows bring in turn.
|
|
398
431
|
const seen = new Set(), todo = [...names].filter(n => n.startsWith('⟦'));
|
|
@@ -402,7 +435,7 @@ function leafRows(goal, path, factsOf) {
|
|
|
402
435
|
continue;
|
|
403
436
|
seen.add(name);
|
|
404
437
|
for (const lin of termRows(name, path)) {
|
|
405
|
-
rows.push({ lin, label: '' });
|
|
438
|
+
rows.push({ lin, label: '', src: { term: name } });
|
|
406
439
|
for (const key of Object.keys(lin))
|
|
407
440
|
if (key !== '1') {
|
|
408
441
|
names.add(key);
|
|
@@ -412,7 +445,7 @@ function leafRows(goal, path, factsOf) {
|
|
|
412
445
|
}
|
|
413
446
|
}
|
|
414
447
|
for (const name of names)
|
|
415
|
-
rows.push({ lin: { [name]: -1 }, label: '' });
|
|
448
|
+
rows.push({ lin: { [name]: -1 }, label: '', src: { nat: name } });
|
|
416
449
|
return rows;
|
|
417
450
|
}
|
|
418
451
|
const keyOf = (cell) => {
|
|
@@ -504,8 +537,8 @@ export function substC(c, s) {
|
|
|
504
537
|
if (c.op === 'not')
|
|
505
538
|
return { op: 'not', x: substC(c.x, s) };
|
|
506
539
|
if (c.op === 'all') {
|
|
507
|
-
const
|
|
508
|
-
return {
|
|
540
|
+
const keys = boundOf(c), vars = Object.fromEntries(Object.entries(s.vars).filter(([n]) => !keys.includes(n)));
|
|
541
|
+
return { ...c, c: substC(c.c, { ...s, vars }) };
|
|
509
542
|
}
|
|
510
543
|
if (c.op === 'and' || c.op === 'or')
|
|
511
544
|
return { op: c.op, xs: c.xs.map(y => substC(y, s)) };
|
|
@@ -567,6 +600,29 @@ const flagsOf = (m) => {
|
|
|
567
600
|
return out;
|
|
568
601
|
};
|
|
569
602
|
const quantified = (m) => Object.entries(m.step.flags ?? {}).filter((e) => e[1].op === 'all');
|
|
603
|
+
// The keys a quantifier binds: its key, and a quantified flag's further keys (a condition at every tuple of keys).
|
|
604
|
+
export const boundOf = (c) => [c.key, ...(c.keys ?? [])];
|
|
605
|
+
// A quantified flag's witnesses, one per key: w?f, then w2?f, w3?f.
|
|
606
|
+
export const witnessesOf = (f, q) => boundOf(q).map((_, i) => (i ? `w${i + 1}?${f}` : `w?${f}`));
|
|
607
|
+
// A comparison between two of a flag's keys, plainly (i < j, i ≤ j, i = j).
|
|
608
|
+
const crossing = (c, keys) => (c.op === '<' || c.op === '<=' || c.op === '=') && c.a.op === 'var' && c.b.op === 'var' && c.a.name !== c.b.name && keys.includes(c.a.name) && keys.includes(c.b.name);
|
|
609
|
+
// A condition over several keys with each comparison between two of them set aside (as 0 ≤ 0): what reads each key on
|
|
610
|
+
// its own. (Kept per condition and keys: the runtimes ask at every step.)
|
|
611
|
+
const crossCache = new WeakMap();
|
|
612
|
+
export const crossFree = (c, keys) => {
|
|
613
|
+
const id = keys.join(','), hit = crossCache.get(c)?.get(id);
|
|
614
|
+
if (hit)
|
|
615
|
+
return hit;
|
|
616
|
+
const zero = { op: 'const', value: 0 };
|
|
617
|
+
const e = (t) => (t.op === 'if' ? { ...t, c: crossFree(t.c, keys), then: e(t.then), else: e(t.else) } : t.op === 'get' ? { ...t, key: e(t.key) } : t.op === 'mul' ? { ...t, a: e(t.a) }
|
|
618
|
+
: t.op === 'add' || t.op === 'sub' || t.op === 'min' || t.op === 'max' || t.op === 'prod' ? { ...t, a: e(t.a), b: e(t.b) } : t);
|
|
619
|
+
const out = crossing(c, keys) ? { op: '<=', a: zero, b: zero } : c.op === 'not' ? { op: 'not', x: crossFree(c.x, keys) } : c.op === 'and' || c.op === 'or' ? { op: c.op, xs: c.xs.map(y => crossFree(y, keys)) }
|
|
620
|
+
: c.op === 'all' ? { ...c, c: crossFree(c.c, keys) } : c.op === 'flag' ? c : { ...c, a: e(c.a), b: e(c.b) };
|
|
621
|
+
if (!crossCache.has(c))
|
|
622
|
+
crossCache.set(c, new Map());
|
|
623
|
+
crossCache.get(c).set(id, out);
|
|
624
|
+
return out;
|
|
625
|
+
};
|
|
570
626
|
// A machine's step as an update, its flags inlined.
|
|
571
627
|
const updateOf = (m) => {
|
|
572
628
|
const flags = { vars: {}, maps: {}, flags: flagsOf(m) };
|
|
@@ -579,15 +635,17 @@ const inlineFlags = (m, c) => substC(c, { vars: {}, maps: {}, flags: flagsOf(m)
|
|
|
579
635
|
// A quantified flag ?f, closed: it is 0 or 1; where it is 1, its condition holds at each key the proof names (instances
|
|
580
636
|
// under ?f); where it is 0, the condition fails at a fresh witness w?f. A real step decides ?f by the condition, and if
|
|
581
637
|
// that fails some key fails it, so these hold of every real step: a goal that refutes them refutes the machine.
|
|
638
|
+
// Over several keys, the instances are tuples (flattened, as Instances keeps them) and there is a witness per key.
|
|
582
639
|
const closing = (m, instances) => quantified(m).flatMap(([f, q]) => {
|
|
583
|
-
const v = { op: 'var', name: `?${f}` }, zero = { op: 'const', value: 0 }, one = { op: 'const', value: 1 };
|
|
584
|
-
const body = (
|
|
585
|
-
|
|
586
|
-
|
|
640
|
+
const v = { op: 'var', name: `?${f}` }, zero = { op: 'const', value: 0 }, one = { op: 'const', value: 1 }, names = boundOf(q), n = names.length;
|
|
641
|
+
const body = (keys) => substC(q.c, { vars: Object.fromEntries(names.map((key, i) => [key, keys[i]])), maps: {}, flags: flagsOf(m) });
|
|
642
|
+
const inst = instances[`?${f}`] ?? [];
|
|
643
|
+
return [{ op: '<=', a: v, b: one }, ...Array.from({ length: Math.floor(inst.length / n) }, (_, t) => ({ op: 'or', xs: [{ op: '=', a: v, b: zero }, body(inst.slice(t * n, (t + 1) * n))] })),
|
|
644
|
+
{ op: 'or', xs: [{ op: '=', a: v, b: one }, { op: 'not', x: body(witnessesOf(f, q).map((name) => ({ op: 'var', name }))) }] }];
|
|
587
645
|
});
|
|
588
646
|
// The names a later step (j >= 2) reads its request by: inputs, quantified flags and their witnesses, and carried maps.
|
|
589
647
|
const laterNames = (m, j) => ({
|
|
590
|
-
vars: Object.fromEntries([...m.inputs, ...quantified(m).flatMap(([f]) => [`?${f}`,
|
|
648
|
+
vars: Object.fromEntries([...m.inputs, ...quantified(m).flatMap(([f, q]) => [`?${f}`, ...witnessesOf(f, q)])].map(name => [name, { op: 'var', name: `${name}@${j}` }])),
|
|
591
649
|
maps: Object.fromEntries((m.inputMaps ?? []).map(map => [map, `${map}@${j}`])),
|
|
592
650
|
});
|
|
593
651
|
// The laws assumed where the run is: each plain law, and each every-key law at the keys the proof names.
|
|
@@ -619,7 +677,8 @@ function reads(t, out = { vars: new Set(), maps: new Set(), flags: new Set() })
|
|
|
619
677
|
break;
|
|
620
678
|
case 'all': {
|
|
621
679
|
const inner = reads(t.c);
|
|
622
|
-
|
|
680
|
+
for (const key of boundOf(t))
|
|
681
|
+
inner.vars.delete(key);
|
|
623
682
|
for (const k of ['vars', 'maps', 'flags'])
|
|
624
683
|
for (const name of inner[k])
|
|
625
684
|
out[k].add(name);
|
|
@@ -709,9 +768,14 @@ export function scope(m, laws = []) {
|
|
|
709
768
|
const c = m.step.flags[f], what = `${m.name}'s flag ${f}`;
|
|
710
769
|
// A quantified flag's body reads no flag (the twin decides quantified flags before the plain ones).
|
|
711
770
|
if (c.op === 'all') {
|
|
712
|
-
|
|
771
|
+
const keys = boundOf(c), body = crossFree(c.c, keys);
|
|
772
|
+
distinct(what, keys);
|
|
773
|
+
for (const key of keys) {
|
|
774
|
+
bound(what, key, body);
|
|
775
|
+
apart(what, body, key, keys);
|
|
776
|
+
}
|
|
713
777
|
unquantified(what, c.c);
|
|
714
|
-
inScope(what, [c.c], [...names,
|
|
778
|
+
inScope(what, [c.c], [...names, ...keys], readable, []);
|
|
715
779
|
}
|
|
716
780
|
else {
|
|
717
781
|
unquantified(what, c);
|
|
@@ -825,6 +889,20 @@ function keyOnly(what, t, key, fns = []) {
|
|
|
825
889
|
};
|
|
826
890
|
walk(t);
|
|
827
891
|
}
|
|
892
|
+
// Over several keys, each read apart from the others: no offset of its key terms, and no value it is compared with, reads
|
|
893
|
+
// another (the comparisons between two of them, plainly, are set aside before: crossFree).
|
|
894
|
+
function apart(what, t, key, keys) {
|
|
895
|
+
const others = keys.filter(k => k !== key), other = (e) => others.some(k => readsKey(e, k));
|
|
896
|
+
const walk = (u) => {
|
|
897
|
+
if ((u.op === 'get' && keyTerm(u.key, key) && other(u.key))
|
|
898
|
+
|| ((u.op === '<' || u.op === '<=' || u.op === '=') && ((keyTerm(u.a, key) && (other(u.a) || other(u.b))) || (keyTerm(u.b, key) && (other(u.a) || other(u.b))))))
|
|
899
|
+
throw new Error(`${what}: the keys ${keys.join(', ')} are each read on their own, and compared with one another only plainly (i < j, i <= j, i = j)`);
|
|
900
|
+
for (const x of Object.values(u))
|
|
901
|
+
if (x && typeof x === 'object')
|
|
902
|
+
(Array.isArray(x) ? x : [x]).forEach(y => walk(y));
|
|
903
|
+
};
|
|
904
|
+
walk(t);
|
|
905
|
+
}
|
|
828
906
|
// Whether the key is read only as a map's key, plainly (what the GPU searches decide: every cell, and one past them).
|
|
829
907
|
export const plainKey = (t, key) => {
|
|
830
908
|
const walk = (u) => {
|
|
@@ -1042,7 +1120,7 @@ export function stepLawObligations(machine, laws, instances, sl) {
|
|
|
1042
1120
|
const m = machine.history ? historyMachine(machine).machine : machine;
|
|
1043
1121
|
scope(m, laws);
|
|
1044
1122
|
const k = sl.steps ?? 1;
|
|
1045
|
-
// Past 64 the cost is the bound (proving grows about as k³: 64 steps in about
|
|
1123
|
+
// Past 64 the cost is the bound (proving grows about as k³: 64 steps in about 17 s on Modal; Lean checks 32 in about 25 s, 64 in about 3 minutes).
|
|
1046
1124
|
if (!Number.isInteger(k) || k < 1 || k > 64)
|
|
1047
1125
|
throw new Error(`step law ${sl.name}: steps is a whole number from 1 to 64`);
|
|
1048
1126
|
const whens = sl.when === undefined ? [] : Array.isArray(sl.when) ? sl.when : [sl.when];
|
package/dist/kernel/prove.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { add, k, refuteFast, scale, solve, sub, v } from "../core/solver.js";
|
|
2
|
-
import { assumed, canon, canonFlip, columnTerm, decided, stepZone, check, checkLaws, checkStepLaws, historyMachine, leafConstraints, leafConstraintsReusing, reusing, linear, livenessHelpful, livenessParts, obligations, plainKey, progressObligations, stepLawObligations, trueC, truth, unreachable, withProducts } from "./kernel.js";
|
|
2
|
+
import { assumed, boundOf, canon, canonFlip, columnTerm, decided, stepZone, check, checkLaws, checkStepLaws, historyMachine, leafConstraints, leafConstraintsReusing, reusing, linear, livenessHelpful, livenessParts, obligations, plainKey, progressObligations, stepLawObligations, trueC, truth, unreachable, witnessesOf, 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) {
|
|
@@ -183,11 +183,17 @@ function growPlain(goal, path, budget) {
|
|
|
183
183
|
if (!byHyp)
|
|
184
184
|
openSeen.set(goal, (byHyp = new Map()));
|
|
185
185
|
const openOf = (h) => { let seen = byHyp.get(h); if (!seen)
|
|
186
|
-
byHyp.set(h, (seen =
|
|
186
|
+
byHyp.set(h, (seen = {})); return reusing(seen, path, p => { const out = []; openH(h, p, out); return out; }); };
|
|
187
|
+
// Then the hypotheses about only the names the conclusion reads (an assumption about another key, a witness, can
|
|
188
|
+
// wait: a refutation seldom needs it), then the rest, each group in the goal's order.
|
|
187
189
|
if (last >= 0)
|
|
188
190
|
open.push(...openOf(hyps[last]));
|
|
191
|
+
const near = nearOf(goal);
|
|
189
192
|
for (const [i, h] of hyps.entries())
|
|
190
|
-
if (i !== last)
|
|
193
|
+
if (i !== last && near[i])
|
|
194
|
+
open.push(...openOf(h));
|
|
195
|
+
for (const [i, h] of hyps.entries())
|
|
196
|
+
if (i !== last && !near[i])
|
|
191
197
|
open.push(...openOf(h));
|
|
192
198
|
open.push(...openCells(rows, path));
|
|
193
199
|
const next = open.find(lin => Object.keys(lin).some(key => key !== '1') && !path.has(canon(lin)) && !path.has(canonFlip(lin)));
|
|
@@ -204,6 +210,13 @@ function growPlain(goal, path, budget) {
|
|
|
204
210
|
const verdict = next && rows.length > 60 ? solveP(rows, 2 * rows.length) : solveP(rows);
|
|
205
211
|
if (verdict.kind === 'refuted')
|
|
206
212
|
return { tree: leafOf(verdict.steps), nodes: budget.nodes };
|
|
213
|
+
// Before a split, the rows the plain search sets aside (those reading a term column) by their Farkas rows: an open
|
|
214
|
+
// choice's bounds (min(a, c) ≤ c) often refute the case whichever way the choice goes, so its cases need no split.
|
|
215
|
+
if (next && !columns && full.length > rows.length) {
|
|
216
|
+
const fast = timed(() => refuteFast(full), ms => { profile.solves++; profile.solveMs += ms; });
|
|
217
|
+
if (fast?.kind === 'refuted')
|
|
218
|
+
return { tree: { leaf: fast.steps }, nodes: budget.nodes };
|
|
219
|
+
}
|
|
207
220
|
let split = next ?? (verdict.kind === 'unknown' ? bisect(rows, path) ?? fractional(verdict.point, path) : undefined);
|
|
208
221
|
// Products: with monomials about, try every pair of rows multiplied; failing that, split a factor at zero.
|
|
209
222
|
const monomials = rows.some(r => Object.keys(r.lin).some(key => key.includes('*')));
|
|
@@ -308,6 +321,17 @@ function lift(goal, tree, from, to) {
|
|
|
308
321
|
};
|
|
309
322
|
return go(tree, from, to);
|
|
310
323
|
}
|
|
324
|
+
// Whether each hypothesis reads only names the goal's conclusion (its last hypothesis) reads.
|
|
325
|
+
const nearSeen = new WeakMap();
|
|
326
|
+
const namesOf = (t) => (t === undefined ? [] : [...JSON.stringify(t).matchAll(/"(?:name|map)":"([^"]+)"/g)].map(m => m[1]));
|
|
327
|
+
function nearOf(goal) {
|
|
328
|
+
let out = nearSeen.get(goal);
|
|
329
|
+
if (!out) {
|
|
330
|
+
const theirs = new Set(namesOf(goal.hyps.at(-1)));
|
|
331
|
+
nearSeen.set(goal, (out = goal.hyps.map(h => namesOf(h).every(n => theirs.has(n)))));
|
|
332
|
+
}
|
|
333
|
+
return out;
|
|
334
|
+
}
|
|
311
335
|
// Each hypothesis' open atoms, as at an earlier path that decided the same atoms it reads (kernel.ts's reusing).
|
|
312
336
|
const openSeen = new WeakMap();
|
|
313
337
|
// What ran out, for a goal left unknown: its size, how deep the case splits went, and the two shapes that usually cause it.
|
|
@@ -446,7 +470,7 @@ function fractional(point, path) {
|
|
|
446
470
|
// A key bound by a quantified flag or a write at every key isn't one: those reads are at every key already.
|
|
447
471
|
function readKeys(m, laws) {
|
|
448
472
|
const out = new Map();
|
|
449
|
-
const bound = new Set([...Object.values(m.step.flags ?? {}).flatMap(c => (c.op === 'all' ?
|
|
473
|
+
const bound = new Set([...Object.values(m.step.flags ?? {}).flatMap(c => (c.op === 'all' ? boundOf(c) : [])), ...Object.values(m.step.maps ?? {}).flatMap(w => ('each' in w ? [w.each] : []))]);
|
|
450
474
|
const free = (e) => !JSON.stringify(e).match(/"name":"([^"]+)"/g)?.some(n => bound.has(n.slice(8, -1)));
|
|
451
475
|
const inE = (e) => {
|
|
452
476
|
if (e.op === 'get') {
|
|
@@ -503,30 +527,35 @@ function mapKeys(goals) {
|
|
|
503
527
|
const withReads = (laws, inst, keys, focus) => ({ ...inst, ...Object.fromEntries(laws.filter(l => l.forall).map(l => {
|
|
504
528
|
const had = inst[l.name] ?? [];
|
|
505
529
|
if (l.keys?.length) {
|
|
506
|
-
const n = l.keys.length + 1;
|
|
507
|
-
const reads = (e, x) => x !== undefined && JSON.stringify(e).includes(`"name":${JSON.stringify(x)}`);
|
|
508
|
-
const own = (i) => { const f = focus[i] ?? focus[0]; return keys.filter(e => reads(e, f) && focus.every(g => g === f || !reads(e, g))); };
|
|
509
|
-
const R = keys.filter(e => !focus.some(f => reads(e, f)));
|
|
510
|
-
const out = new Map();
|
|
530
|
+
const n = l.keys.length + 1, out = new Map();
|
|
511
531
|
for (let i = 0; i + n <= had.length; i += n)
|
|
512
532
|
out.set(JSON.stringify(had.slice(i, i + n)), had.slice(i, i + n));
|
|
513
|
-
const
|
|
514
|
-
|
|
515
|
-
out.set(JSON.stringify(tuple), tuple);
|
|
516
|
-
return;
|
|
517
|
-
}
|
|
518
|
-
for (const e of own(i))
|
|
519
|
-
go(i + 1, [...tuple, e], rest);
|
|
520
|
-
if (!rest)
|
|
521
|
-
for (const e of R)
|
|
522
|
-
go(i + 1, [...tuple, e], true);
|
|
523
|
-
};
|
|
524
|
-
go(0, [], false);
|
|
533
|
+
for (const tuple of related(keys, focus, n))
|
|
534
|
+
out.set(JSON.stringify(tuple), tuple);
|
|
525
535
|
return [l.name, [...out.values()].flat()];
|
|
526
536
|
}
|
|
527
537
|
const seen = new Set(had.map(e => JSON.stringify(e)));
|
|
528
538
|
return [l.name, [...had, ...keys.filter(e => { const id = JSON.stringify(e); return seen.has(id) ? false : (seen.add(id), true); })]];
|
|
529
539
|
})) });
|
|
540
|
+
// The n-tuples of the keys that relate the focus (withReads).
|
|
541
|
+
const related = (keys, focus, n) => {
|
|
542
|
+
const reads = (e, x) => x !== undefined && JSON.stringify(e).includes(`"name":${JSON.stringify(x)}`);
|
|
543
|
+
const own = (i) => { const f = focus[i] ?? focus[0]; return keys.filter(e => reads(e, f) && focus.every(g => g === f || !reads(e, g))); };
|
|
544
|
+
const R = keys.filter(e => !focus.some(f => reads(e, f))), out = [];
|
|
545
|
+
const go = (i, tuple, rest) => {
|
|
546
|
+
if (i === n) {
|
|
547
|
+
out.push(tuple);
|
|
548
|
+
return;
|
|
549
|
+
}
|
|
550
|
+
for (const e of own(i))
|
|
551
|
+
go(i + 1, [...tuple, e], rest);
|
|
552
|
+
if (!rest)
|
|
553
|
+
for (const e of R)
|
|
554
|
+
go(i + 1, [...tuple, e], true);
|
|
555
|
+
};
|
|
556
|
+
go(0, [], false);
|
|
557
|
+
return out;
|
|
558
|
+
};
|
|
530
559
|
// A law about every n keys at every n-tuple of the keys (flattened, as Instances keeps tuples).
|
|
531
560
|
const tuples = (keys, n) => (n === 0 ? [] : n === 1 ? keys : keys.flatMap(a => { const rest = tuples(keys, n - 1); return Array.from({ length: rest.length / (n - 1) }, (_, t) => [a, ...rest.slice(t * (n - 1), (t + 1) * (n - 1))]).flat(); }));
|
|
532
561
|
// The keys some goals read a function at.
|
|
@@ -615,43 +644,56 @@ function match(pattern, term, x) {
|
|
|
615
644
|
};
|
|
616
645
|
return go(pattern, term) ? bound : undefined;
|
|
617
646
|
}
|
|
618
|
-
// Each quantified flag's witness w?f: a key where its condition fails, when it does;
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
647
|
+
// Each quantified flag's witness w?f (one per key, over several): a key where its condition fails, when it does;
|
|
648
|
+
// every-key facts are wanted there too.
|
|
649
|
+
const witnesses = (m) => Object.entries(m.step.flags ?? {}).flatMap(([f, c]) => (c.op === 'all' ? witnessesOf(f, c).map((name) => ({ op: 'var', name })) : []));
|
|
650
|
+
// The keys each quantified flag is assumed at (instances under ?f): over several keys, the tuples that relate the
|
|
651
|
+
// goals' own keys as a law about several keys takes them (every tuple, without them).
|
|
652
|
+
const flagInstances = (m, keys, focus = []) => Object.fromEntries(Object.entries(m.step.flags ?? {}).flatMap(([f, c]) => {
|
|
653
|
+
if (c.op !== 'all')
|
|
654
|
+
return [];
|
|
655
|
+
const n = boundOf(c).length;
|
|
656
|
+
return [[`?${f}`, n === 1 ? keys : focus.length ? related(keys, focus, n).flat() : tuples(keys, n)]];
|
|
657
|
+
}));
|
|
658
|
+
const pairFlags = (m) => Object.values(m.step.flags ?? {}).some(c => c.op === 'all' && !!c.keys?.length);
|
|
659
|
+
// How many keys each instance of a name takes: a law's, or a quantified flag's (?f).
|
|
660
|
+
const arity = (m, laws, n) => {
|
|
661
|
+
const q = n.startsWith('?') ? m?.step.flags?.[n.slice(1)] : undefined;
|
|
662
|
+
return q?.op === 'all' ? boundOf(q).length : (laws.find(l => l.name === n)?.keys?.length ?? 0) + 1;
|
|
663
|
+
};
|
|
622
664
|
// The instances some goals need, for every entry point: the every-key laws and the quantified flags at `seed` (the keys
|
|
623
665
|
// the step and the laws read, when known before any goal is built), then twice round: the facts at every key the goals
|
|
624
666
|
// built so far read a function at, and, where the seed doesn't cover them (no seed, positions that shift, a law about
|
|
625
667
|
// several keys), the every-key laws and flags at every key they read a map at (a law about several keys at tuples
|
|
626
668
|
// relating `focus`, the goals' own keys).
|
|
627
669
|
function instancesFor(m, laws, build, opts = {}) {
|
|
628
|
-
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) } : {});
|
|
629
|
-
const reads = opts.reads ?? (!opts.seed || shifts(m) || laws.some(l => l.keys?.length));
|
|
670
|
+
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, opts.focus) } : {});
|
|
671
|
+
const reads = opts.reads ?? (!opts.seed || shifts(m) || laws.some(l => l.keys?.length) || pairFlags(m));
|
|
630
672
|
for (let round = 0; round < 2; round++) {
|
|
631
673
|
if (m.facts?.length)
|
|
632
674
|
inst = withFacts(m, inst, fnKeys(m, build(inst)));
|
|
633
675
|
if (reads) {
|
|
634
676
|
const keys = mapKeys(build(inst));
|
|
635
|
-
inst = union(withReads(laws, inst, keys, opts.focus ?? []), flagInstances(m, keys));
|
|
677
|
+
inst = union(withReads(laws, inst, keys, opts.focus ?? []), flagInstances(m, keys, opts.focus), m);
|
|
636
678
|
}
|
|
637
679
|
}
|
|
638
680
|
return inst;
|
|
639
681
|
}
|
|
640
682
|
// Instances without the keys that read any of these names (a law about several keys loses each tuple that does).
|
|
641
|
-
const avoiding = (inst, names, laws) => {
|
|
683
|
+
const avoiding = (inst, names, laws, m) => {
|
|
642
684
|
const bad = (e) => names.some(n => JSON.stringify(e).includes(`"name":${JSON.stringify(n)}`));
|
|
643
685
|
return Object.fromEntries(Object.entries(inst).map(([n, keys]) => {
|
|
644
|
-
const
|
|
645
|
-
|
|
646
|
-
return [n, tuples.filter(tuple => !tuple.some(bad)).flat()];
|
|
686
|
+
const a = arity(m, laws, n), groups = Array.from({ length: Math.floor(keys.length / a) }, (_, t) => keys.slice(t * a, (t + 1) * a));
|
|
687
|
+
return [n, groups.filter(tuple => !tuple.some(bad)).flat()];
|
|
647
688
|
}));
|
|
648
689
|
};
|
|
649
|
-
// Two sets of instances together: each name at the keys of both.
|
|
650
|
-
const union = (a, b) => {
|
|
690
|
+
// Two sets of instances together: each name at the keys (or tuples) of both.
|
|
691
|
+
const union = (a, b, m) => {
|
|
651
692
|
const out = { ...a };
|
|
652
693
|
for (const [n, keys] of Object.entries(b)) {
|
|
653
|
-
const
|
|
654
|
-
|
|
694
|
+
const k = arity(m, [], n), groups = (ks) => Array.from({ length: Math.floor(ks.length / k) }, (_, t) => ks.slice(t * k, (t + 1) * k));
|
|
695
|
+
const seen = new Set(groups(out[n] ?? []).map(g => JSON.stringify(g)));
|
|
696
|
+
out[n] = [...(out[n] ?? []), ...groups(keys).filter(g => !seen.has(JSON.stringify(g))).flat()];
|
|
655
697
|
}
|
|
656
698
|
return out;
|
|
657
699
|
};
|
|
@@ -763,12 +805,12 @@ function proveStepLawsAsGiven(m, laws, stepLaws, base) {
|
|
|
763
805
|
// law's later frames read keys no other step law has); where positions shift or a law is about several keys, the
|
|
764
806
|
// keys its goal reads a map at too. A frame's hypotheses read no name the run introduces later (kernel.ts's
|
|
765
807
|
// stepZone), so a k-step law drops the keys that read one.
|
|
766
|
-
const shared = [...readKeys(plain, laws), ...witnesses(plain)], named = [...laws, ...(plain.facts ?? [])], reads = shifts(plain) || laws.some(l => l.keys?.length);
|
|
808
|
+
const shared = [...readKeys(plain, laws), ...witnesses(plain)], named = [...laws, ...(plain.facts ?? [])], reads = shifts(plain) || laws.some(l => l.keys?.length) || pairFlags(plain);
|
|
767
809
|
const own = (sl) => [...[sl.then, ...(sl.when === undefined ? [] : Array.isArray(sl.when) ? sl.when : [sl.when])].flatMap(readKeysC),
|
|
768
810
|
...[sl.forall, ...(sl.keys ?? [])].flatMap(key => (key ? [{ op: 'var', name: key }] : []))];
|
|
769
811
|
const perStep = {};
|
|
770
812
|
for (const sl of stepLaws) {
|
|
771
|
-
const zone = stepZone(plain, sl.steps ?? 1), keep = (inst) => avoiding(inst, zone, named);
|
|
813
|
+
const zone = stepZone(plain, sl.steps ?? 1), keep = (inst) => avoiding(inst, zone, named, plain);
|
|
772
814
|
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 }));
|
|
773
815
|
}
|
|
774
816
|
const instances = perStep[stepLaws[0]?.name ?? ''] ?? {};
|
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, columnTerm, stateName, termColumn, historyMachine, foldObligations, leafConstraints, loopLiveness, loopMachine, linear, livenessHelpful, livenessParts, livenessRanks, loopObligations, trueC, obligations, progressObligations, stepLawObligations, substC, withProducts } from "../kernel/kernel.js";
|
|
3
|
+
import { boundOf, canon, columnTerm, stateName, termColumn, historyMachine, foldObligations, leafConstraints, loopLiveness, loopMachine, linear, livenessHelpful, livenessParts, livenessRanks, loopObligations, trueC, obligations, progressObligations, stepLawObligations, substC, witnessesOf, 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 { leanSurface, leanTerms } from "./lean-terms.js";
|
|
@@ -137,7 +137,9 @@ export function encode(ex, tamper = false, dropStep = true, globalVars, globalMa
|
|
|
137
137
|
coefs.pop();
|
|
138
138
|
return coefs;
|
|
139
139
|
};
|
|
140
|
-
|
|
140
|
+
// Rows by their nonzero coefficients (Cert.lean's rows hold them so): most of a wide row is zeros.
|
|
141
|
+
const sparse = (coefs) => list(coefs.flatMap((x, j) => (x === 0 ? [] : [`(${j}, ${int(x)})`])));
|
|
142
|
+
const row = (lin) => `(sp ${sparse(dense(lin))} ${int(lin['1'] ?? 0)})`;
|
|
141
143
|
const { e, c } = leanTerms(vars, maps, ex.name);
|
|
142
144
|
const tab = cols.map(name => (name.includes('[') ? `.cell ${maps.indexOf(cellMap(name))} ${row(cellKey(name))}`
|
|
143
145
|
: name.startsWith('⟦') ? `.term (${e(columnTerm(name))})`
|
|
@@ -171,22 +173,35 @@ export function encode(ex, tamper = false, dropStep = true, globalVars, globalMa
|
|
|
171
173
|
const q = Object.fromEntries(Object.entries(from).filter(([key]) => key !== '1').map(([key, v]) => [key, v / s.g]));
|
|
172
174
|
const cc = Math.ceil((from['1'] ?? 0) / s.g);
|
|
173
175
|
replay.push({ ...q, 1: cc });
|
|
174
|
-
return `.cut ${s.of} ${int(s.g)} ${
|
|
176
|
+
return `.cut ${s.of} ${int(s.g)} ${sparse(dense(q))} ${int(cc)}`;
|
|
175
177
|
});
|
|
176
178
|
if (tamper && dropStep && leafNo++ === 0)
|
|
177
179
|
steps = steps.slice(0, -1); // the first leaf loses its last step
|
|
178
180
|
// Cell pairs the path proves equal (Lean re-checks each).
|
|
179
|
-
const eqs = [];
|
|
181
|
+
const eqs = [], pairs = [];
|
|
180
182
|
const cellCols = cols.map((name, j) => ({ name, j })).filter(c2 => c2.name.includes('['));
|
|
181
183
|
for (const [a, p] of cellCols.entries())
|
|
182
184
|
for (const q of cellCols.slice(a + 1)) {
|
|
183
185
|
if (cellMap(p.name) !== cellMap(q.name))
|
|
184
186
|
continue;
|
|
185
187
|
const d = sub(cellKey(p.name), cellKey(q.name));
|
|
186
|
-
if (decide(d, path) === true && decide(scale(d, -1), path) === true)
|
|
188
|
+
if (decide(d, path) === true && decide(scale(d, -1), path) === true) {
|
|
187
189
|
eqs.push(`(${p.j}, ${q.j})`);
|
|
190
|
+
pairs.push([p.name, q.name].sort().join('\n'));
|
|
191
|
+
}
|
|
188
192
|
}
|
|
189
|
-
|
|
193
|
+
// Each row with where Lean finds it (a hint: missed, Lean looks among all of the leaf's rows).
|
|
194
|
+
const hint = (src) => {
|
|
195
|
+
if (!src)
|
|
196
|
+
return '.any';
|
|
197
|
+
if ('atom' in src)
|
|
198
|
+
return `.atom ${path.size - 1 - src.atom}`; // Lean's path has the latest decision first
|
|
199
|
+
if ('fact' in src)
|
|
200
|
+
return `.fact ${src.fact}`;
|
|
201
|
+
const j = 'pair' in src ? pairs.indexOf([...src.pair].sort().join('\n')) : col.get('nat' in src ? src.nat : src.term) ?? -1;
|
|
202
|
+
return j < 0 ? '.any' : 'pair' in src ? `.eq ${j}` : 'nat' in src ? `.nat ${j}` : `.term ${j}`;
|
|
203
|
+
};
|
|
204
|
+
return `.leafAt ${list(used.map(u => `(${row(rows[u])}, ${hint(base[u].src)})`))} ${list(steps)} ${list(eqs)} ${list(prods.map(([i, j]) => `(${idx(i)}, ${idx(j)})`))}`;
|
|
190
205
|
};
|
|
191
206
|
return { tab: list(tab), hyps: list(goal.hyps.map(c)), tree: lean(tree, new Map()), cols, c, e };
|
|
192
207
|
}
|
|
@@ -474,7 +489,7 @@ const flagsOfM = (m) => {
|
|
|
474
489
|
// Sets, or functions with facts: both go through Sets.lean.
|
|
475
490
|
export const hasSets = (m) => !!m.inputMaps?.length || Object.values(m.step.flags ?? {}).some(c => c.op === 'all') || Object.values(m.step.maps ?? {}).some(w => 'each' in w)
|
|
476
491
|
|| !!Object.keys(m.fns ?? {}).length || !!m.facts?.length;
|
|
477
|
-
const boundKeys = (m) => [...Object.values(m.step.flags ?? {}).flatMap(c => (c.op === 'all' ?
|
|
492
|
+
const boundKeys = (m) => [...Object.values(m.step.flags ?? {}).flatMap(c => (c.op === 'all' ? boundOf(c) : [])), ...Object.values(m.step.maps ?? {}).flatMap(w => ('each' in w ? [w.each] : []))];
|
|
478
493
|
// A machine with a history goes as its plain machine (kernel.ts's historyMachine: sums as state, queries as inputs,
|
|
479
494
|
// the relations assumed), built in Lean from the machine and its history (History.lean's HMachine.plain), so every claim
|
|
480
495
|
// below is about it; with the relations' record goals, its laws hold over histories too (hmachine_sound).
|
|
@@ -505,7 +520,7 @@ def ${id}_step${j} : SKStep := ⟨⟨${list(Array.from({ length: kk }, (_, s2) =
|
|
|
505
520
|
${list(finsts(inst))},
|
|
506
521
|
${list(pinsts(inst))},
|
|
507
522
|
${list(pfinsts(inst))},
|
|
508
|
-
|
|
523
|
+
${pr.hyps},
|
|
509
524
|
${list(Array.from({ length: kk - 1 }, (_, s2) => list(Object.keys(m.state).map(n2 => `(${ix(n2)}, ${ix(stateName(n2, s2 + 1))})`))))}⟩
|
|
510
525
|
theorem ${id}_step${j}_holds : SKStepHolds ${id}_machine ${id}_step${j} :=
|
|
511
526
|
skstep_sound ${id}_machine ${id}_proof ${id}_step${j} ${id}_check (by decide +kernel)`;
|
|
@@ -580,9 +595,10 @@ theorem ${id}_orig_laws : ∀ ρ M, SReach ${id}_ghosted.orig ρ M → ∀ e ∈
|
|
|
580
595
|
throw new Error(`${id}: not proven`);
|
|
581
596
|
const lp = r.proof.laws, si = r.proof.instances;
|
|
582
597
|
const flags = Object.entries(m.step.flags ?? {}), quantified = flags.filter(([, c]) => c.op === 'all').map(([f]) => f);
|
|
598
|
+
const qwits = (f) => witnessesOf(f, m.step.flags[f]);
|
|
583
599
|
const carried = m.inputMaps ?? [], facts = m.facts ?? [], keysOf = [...new Set([...laws, ...facts].flatMap(l => [l.forall, ...(l.keys ?? [])].filter((k) => !!k)))];
|
|
584
600
|
const k = Math.max(1, ...allSteps.map(sl => sl.steps ?? 1));
|
|
585
|
-
const ext = [...m.inputs, ...quantified.flatMap(f => [`?${f}`,
|
|
601
|
+
const ext = [...m.inputs, ...quantified.flatMap(f => [`?${f}`, ...qwits(f)])];
|
|
586
602
|
const later = Array.from({ length: k - 1 }, (_, j) => ext.map(v => `${v}@${j + 2}`)).flat();
|
|
587
603
|
const laterMaps = Array.from({ length: k - 1 }, (_, j) => carried.map(mp => `${mp}@${j + 2}`)).flat();
|
|
588
604
|
const primes = allSteps.length ? Object.keys(m.state).map(s2 => `${s2}'`) : [];
|
|
@@ -635,9 +651,14 @@ theorem ${id}_orig_laws : ∀ ρ M, SReach ${id}_ghosted.orig ρ M → ∀ e ∈
|
|
|
635
651
|
return Array.from({ length: Math.floor(keys.length / k) }, (_, t) => sub1(sub1(lw.holds, Object.fromEntries(lw.keys.map((kn, i) => [kn, keys[t * k + 1 + i]]))), { [lw.forall]: keys[t * k] }));
|
|
636
652
|
}),
|
|
637
653
|
w,
|
|
638
|
-
...allFlags.flatMap(([f, fc]) =>
|
|
639
|
-
|
|
640
|
-
|
|
654
|
+
...allFlags.flatMap(([f, fc]) => {
|
|
655
|
+
if (fc.op !== 'all')
|
|
656
|
+
return [];
|
|
657
|
+
const names = boundOf(fc), n = names.length, at = (ts) => sub1(fc.c, Object.fromEntries(names.map((key, i) => [key, ts[i]]))), qi = inst[`?${f}`] ?? [];
|
|
658
|
+
return [{ op: '<=', a: { op: 'var', name: `?${f}` }, b: zero1(1) },
|
|
659
|
+
...Array.from({ length: Math.floor(qi.length / n) }, (_, t) => ({ op: 'or', xs: [eqC({ op: 'var', name: `?${f}` }, zero1(0)), at(qi.slice(t * n, (t + 1) * n))] })),
|
|
660
|
+
{ op: 'or', xs: [eqC({ op: 'var', name: `?${f}` }, zero1(1)), { op: 'not', x: at(qwits(f).map((name) => ({ op: 'var', name }))) }] }];
|
|
661
|
+
})];
|
|
641
662
|
const last = { op: 'not', x: sInline(m.assume ?? trueC) }, zkeys = carriedKeys([...base, last]);
|
|
642
663
|
return { name: 'allowed', hyps: [...base, ...(m.inputMaps ?? []).flatMap(mp => zkeys.map(t => eqC({ op: 'get', map: mp, key: t }, zero1(0)))), last], zkeys };
|
|
643
664
|
};
|
|
@@ -647,7 +668,7 @@ theorem ${id}_orig_laws : ∀ ρ M, SReach ${id}_ghosted.orig ρ M → ∀ e ∈
|
|
|
647
668
|
})]));
|
|
648
669
|
const allowGoals = [...allowed.values()].flatMap(a => (a ? [a.out.goal] : []));
|
|
649
670
|
const vars = [...new Set([...m.params, ...m.inputs, ...Object.keys(m.state), ...keysOf, ...keysOf.map(k2 => `${k2}#`), '#', ...flags.map(([f]) => `?${f}`),
|
|
650
|
-
...quantified.
|
|
671
|
+
...quantified.flatMap(qwits), ...boundKeys(m), ...(opts.tamper ? boundKeys(opts.tamper.machine) : []), ...stepLaws.flatMap(sl => (sl.forall ? [sl.forall] : [])), ...(opts.free ?? []),
|
|
651
672
|
...primes, ...later, ...Array.from({ length: k - 1 }, (_, j) => Object.keys(m.state).map(n2 => stateName(n2, j + 1))).flat(),
|
|
652
673
|
...(hm ? hm.history.record.map(f => `r.${f}`) : []), ...[...goalsL, ...goalsS, ...allowGoals, ...(hplain?.goals ?? [])].flatMap(g => g.hyps.flatMap(varsOf))])];
|
|
653
674
|
const baseMaps = [...new Set([...Object.keys(m.maps ?? {}), ...Object.keys(m.step.maps ?? {}), ...Object.keys(m.fns ?? {})])];
|
|
@@ -672,8 +693,8 @@ theorem ${id}_orig_laws : ∀ ρ M, SReach ${id}_ghosted.orig ρ M → ∀ e ∈
|
|
|
672
693
|
${list(fs.flatMap(([f, c2]) => (c2.op === 'all' ? [] : [`(${ix(`?${f}`)}, ${c(fl[f])})`])))}⟩,
|
|
673
694
|
${list((mm.inputMaps ?? []).map(mp => String(mx(mp))))},
|
|
674
695
|
${list(ws.flatMap(([name, w]) => ('each' in w ? [`(${mx(name)}, ${ix(w.each)}, ${e(w.value)})`] : [])))},
|
|
675
|
-
${list(fs.flatMap(([f, c2]) => (c2.op === 'all' ? [`(${ix(`?${f}`)}, ${
|
|
676
|
-
${list(fs.flatMap(([f, c2]) => (c2.op === 'all' ? [`(${ix(`?${f}`)}, ${ix(
|
|
696
|
+
${list(fs.flatMap(([f, c2]) => (c2.op === 'all' ? [`(${ix(`?${f}`)}, ${list(boundOf(c2).map(key => String(ix(key))))}, ${c(c2.c)})`] : [])))},
|
|
697
|
+
${list(fs.flatMap(([f, c2]) => (c2.op === 'all' ? [`(${ix(`?${f}`)}, ${list(witnessesOf(f, c2).map(w => String(ix(w))))})`] : [])))},
|
|
677
698
|
${list((mm.facts ?? []).filter(l => !l.keys?.length).map(l => `⟨${c(l.holds)}, ${l.forall ? `some ${ix(l.forall)}` : 'none'}⟩`))},
|
|
678
699
|
${list((mm.facts ?? []).filter(l => l.keys?.length).map(l => `(⟨${c(l.holds)}, some ${ix(l.forall)}⟩, ${list(l.keys.map(k2 => String(ix(k2))))})`))}⟩`;
|
|
679
700
|
};
|
|
@@ -727,20 +748,24 @@ ${readable(id, laws, vars, mapNames, { binders: '(ρ : Nat → Nat) (μ : Nat
|
|
|
727
748
|
const n = factsN[j].keys.length + 1;
|
|
728
749
|
return Array.from({ length: Math.floor(keys.length / n) }, (_, t) => `(${j}, ${e(keys[t * n])}, ${list(keys.slice(t * n + 1, t * n + n).map(e))})`);
|
|
729
750
|
});
|
|
730
|
-
|
|
751
|
+
// Each quantified flag at its tuples (one key each, over one key).
|
|
752
|
+
const qinsts = (inst) => Object.entries(inst).filter(([name]) => name.startsWith('?')).flatMap(([f, keys]) => {
|
|
753
|
+
const q = m.step.flags?.[f.slice(1)], n = q?.op === 'all' ? boundOf(q).length : 1;
|
|
754
|
+
return Array.from({ length: Math.floor(keys.length / n) }, (_, t) => `(${ix(f)}, ${list(keys.slice(t * n, (t + 1) * n).map(e))})`);
|
|
755
|
+
});
|
|
731
756
|
const entries = laws.map((law, j) => {
|
|
732
757
|
const g0 = goalsL[2 * j], g1 = goalsL[2 * j + 1], a = enc(g0, lp.trees[g0.name]), b = enc(g1, lp.trees[g1.name]), inst = lp.instances[law.name] ?? {};
|
|
733
758
|
return `def ${id}_l${j} : MLaw := ⟨${c(law.holds)}, ${law.forall ? `some ${ix(law.forall)}` : 'none'}⟩
|
|
734
759
|
def ${id}_e${j} : SEntry := ⟨${id}_l${j}, ${ix(law.forall ? `${law.forall}#` : '#')}, ${list((law.keys ?? []).map(key => String(ix(key))))}, ${list(insts(inst))}, ${list(qinsts(inst))}, ${list(finsts(inst))}, ${list(pinsts(inst))}, ${list(pfinsts(inst))},
|
|
735
|
-
(${a.tab},
|
|
736
|
-
(${b.tab},
|
|
760
|
+
(${a.tab}, ${a.hyps}, ${a.tree}),
|
|
761
|
+
(${b.tab}, ${b.hyps}, ${b.tree})⟩`;
|
|
737
762
|
});
|
|
738
763
|
// A ghosted machine (compose.ts's ghost): the original and its ghosts, the machine Lean checks built from them
|
|
739
764
|
// (Ghost.lean), so the laws that read no ghost are laws of the original.
|
|
740
765
|
const orig = { ...m, state: Object.fromEntries(Object.entries(m.state).filter(([n2]) => !gv.includes(n2))), step: { ...m.step, state: Object.fromEntries(Object.entries(m.step.state ?? {}).filter(([n2]) => !gv.includes(n2))) } };
|
|
741
766
|
const h = {
|
|
742
767
|
step: sl => { const g = goalsS[allSteps.indexOf(sl)]; return { pr: enc(g, r.proof.trees[g.name]), inst: instOf(sl) }; },
|
|
743
|
-
sproof: sl => { const { pr, inst } = h.step(sl); return `(${list(insts(inst))}, ${list(qinsts(inst))}, ${list(finsts(inst))}, ${list(pinsts(inst))}, ${list(pfinsts(inst))}, (${pr.tab},
|
|
768
|
+
sproof: sl => { const { pr, inst } = h.step(sl); return `(${list(insts(inst))}, ${list(qinsts(inst))}, ${list(finsts(inst))}, ${list(pinsts(inst))}, ${list(pfinsts(inst))}, (${pr.tab}, ${pr.hyps}, ${pr.tree}))`; },
|
|
744
769
|
allow: a => {
|
|
745
770
|
const f2 = allowed.get(a);
|
|
746
771
|
if (!f2)
|