gdp-wired 0.18.0 → 0.19.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/dist/core/cyclic.d.ts +90 -6
- package/dist/core/cyclic.js +668 -27
- package/dist/core/dsl.js +25 -9
- package/dist/core/translate.d.ts +30 -0
- package/dist/core/translate.js +572 -0
- package/dist/kernel/prove.d.ts +13 -0
- package/dist/kernel/prove.js +112 -10
- package/dist/lean-kernel/emit.d.ts +34 -0
- package/dist/lean-kernel/emit.js +406 -50
- package/dist/lean-kernel/library.js +2 -1
- package/package.json +1 -1
package/dist/core/cyclic.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { substC, substE } from "../kernel/kernel.js";
|
|
1
|
+
import { assumed, boundOf, foldObligations, obligations, substC, substE, witnessesOf } from "../kernel/kernel.js";
|
|
2
2
|
import { baseHyps, descentGoal, instHyps, mainGoal, premiseGoal, refuseCyclic } from "../kernel/cyclic.js";
|
|
3
3
|
import { cellProducts, grow, profile, usedHyps, widen } from "../kernel/prove.js";
|
|
4
|
+
import { carry, decide, readsFirst, renamer, translate, tstats } from "./translate.js";
|
|
4
5
|
const v = (name) => ({ op: 'var', name });
|
|
5
6
|
const k = (value) => ({ op: 'const', value });
|
|
6
7
|
const add = (a, b) => ({ op: 'add', a, b });
|
|
@@ -21,7 +22,7 @@ export const pair = (u, key) => add(u, { op: 'prod', a: key, b: omega });
|
|
|
21
22
|
// descent, the kernel's check): ms, goals, hypotheses, solver and row ms.
|
|
22
23
|
export const cyclicProf = {};
|
|
23
24
|
const profOn = () => env()?.GDP_CYCLIC_PROF === '1';
|
|
24
|
-
function timedKind(kind, hyps, f) {
|
|
25
|
+
export function timedKind(kind, hyps, f) {
|
|
25
26
|
if (!profOn())
|
|
26
27
|
return f();
|
|
27
28
|
const was = profile.on, s0 = profile.solveMs, r0 = profile.rowsMs, t0 = performance.now();
|
|
@@ -141,17 +142,24 @@ export function slice(g, whole, hypsOnly = false) {
|
|
|
141
142
|
}
|
|
142
143
|
return { name: g.name, hyps, from };
|
|
143
144
|
}
|
|
145
|
+
const withProducts = (f) => { const was = cellProducts.on; cellProducts.on = true; try {
|
|
146
|
+
return f();
|
|
147
|
+
}
|
|
148
|
+
finally {
|
|
149
|
+
cellProducts.on = was;
|
|
150
|
+
} };
|
|
144
151
|
let sliceNow = false;
|
|
145
152
|
const sliceOn = () => sliceNow && env()?.GDP_CYCLIC_SLICE !== '0';
|
|
146
153
|
const afterOn = () => env()?.GDP_CYCLIC_AFTER !== '0';
|
|
147
154
|
const stepNodesOn = () => env()?.GDP_CYCLIC_STEP !== '0';
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
const grownBefore = new Map();
|
|
155
|
+
const searched = new Map();
|
|
156
|
+
export const translatedCache = new Map();
|
|
151
157
|
// `order`: the order the nodes are grown in, by default those with a weak variant first (a variant that uses a weak node
|
|
152
158
|
// is passed over).
|
|
153
|
-
|
|
154
|
-
|
|
159
|
+
// `made`: the first nodes already made (a proof of the same facts whose nodes these specs begin with), taken as they are.
|
|
160
|
+
export function proveCyclic(facts, specs, order0, grownBefore = searched, st = tstats(), made = []) {
|
|
161
|
+
const order = (order0 ?? [...specs.keys()].sort((i, j) => Number(!specs[i].variants?.some(x => x.weak)) - Number(!specs[j].variants?.some(x => x.weak)))).filter(i => i >= made.length);
|
|
162
|
+
const nodes = [...made], shapes = [...specs], weak = new Set();
|
|
155
163
|
let later, refuted = false;
|
|
156
164
|
// A goal's tree, and the hypotheses its leaves read (of the whole goal, a weaker goal's read through where they came from).
|
|
157
165
|
const grown = (g, node, own) => {
|
|
@@ -179,12 +187,6 @@ export function proveCyclic(facts, specs, order = [...specs.keys()].sort((i, j)
|
|
|
179
187
|
cellProducts.on = was;
|
|
180
188
|
}
|
|
181
189
|
};
|
|
182
|
-
const withProducts = (f) => { const was = cellProducts.on; cellProducts.on = true; try {
|
|
183
|
-
return f();
|
|
184
|
-
}
|
|
185
|
-
finally {
|
|
186
|
-
cellProducts.on = was;
|
|
187
|
-
} };
|
|
188
190
|
// The tree made the goal's own (a weaker goal's widened), or grown on the goal itself where that fails.
|
|
189
191
|
const own = (r, g) => (r.from === g ? r.tree : timedKind('widen', 0, () => widen(r.from, g, r.tree, opaqueOf.get(r.from))));
|
|
190
192
|
const whole = (g, node, mine) => {
|
|
@@ -222,15 +224,23 @@ export function proveCyclic(facts, specs, order = [...specs.keys()].sort((i, j)
|
|
|
222
224
|
};
|
|
223
225
|
// A node's goal grown; then the node keeps only the facts and uses its tree reads (the fewer, the fewer hypotheses its
|
|
224
226
|
// goal has for the kernel to read at every leaf, and the fewer uses to prove below); then each use's goals.
|
|
225
|
-
const attempt = ({ opaque, variants: _v, weak: _w, slice: sl, ...n0 }) => {
|
|
227
|
+
const attempt = ({ opaque, variants: _v, weak: _w, slice: sl, main: given0, carry: made, ...n0 }) => {
|
|
226
228
|
hidden = opaque ?? [];
|
|
227
229
|
sliceNow = !!sl;
|
|
228
230
|
// A goal's own hypotheses: the node's, and those after its facts and uses.
|
|
229
231
|
const n1 = { ...n0, insts: n0.insts.filter(p => facts[p.fact]) };
|
|
232
|
+
const given = given0 ?? (made ? timedKind('carry', 0, () => withProducts(() => carry(mainGoal(facts, shapes, n1), n1.insts.length, n1.uses.length, st, performance.now() + 2_000))) : undefined);
|
|
233
|
+
if (made && !given0) {
|
|
234
|
+
if (given)
|
|
235
|
+
st.carried++;
|
|
236
|
+
else
|
|
237
|
+
st.uncarried++;
|
|
238
|
+
}
|
|
230
239
|
const base = baseHyps(facts, n1).length, mine = (tail) => (i) => i < n1.hyps.length || i >= base + tail;
|
|
231
|
-
const g = mainGoal(facts, shapes, n1), key = JSON.stringify(g.hyps), before = grownBefore.get(key);
|
|
232
|
-
let n = n1, main, src;
|
|
233
|
-
if (
|
|
240
|
+
const g = given ? undefined : mainGoal(facts, shapes, n1), key = g ? JSON.stringify(g.hyps) : '', before = g && grownBefore.get(key);
|
|
241
|
+
let n = n1, main = given, src;
|
|
242
|
+
if (!g) { /* the tree given */ }
|
|
243
|
+
else if (before) {
|
|
234
244
|
n = { ...n1, insts: before.insts?.map(i => n1.insts[i]) ?? n1.insts, uses: before.uses?.map(j => n1.uses[j]) ?? n1.uses };
|
|
235
245
|
main = before.tree;
|
|
236
246
|
src = { tree: main, from: mainGoal(facts, shapes, n) };
|
|
@@ -333,7 +343,9 @@ export function proveCyclic(facts, specs, order = [...specs.keys()].sort((i, j)
|
|
|
333
343
|
const sliced = (specs) => specs.map(sp => ({ ...sp, slice: true, ...(sp.variants ? { variants: sp.variants.map(x => ({ ...x, slice: true })) } : {}) }));
|
|
334
344
|
// The facts of a machine's functions (laws about every key of them), as cyclic facts.
|
|
335
345
|
const factOf = (l) => ({ name: l.name, keys: [...(l.forall ? [l.forall] : []), ...(l.keys ?? [])], holds: l.holds });
|
|
336
|
-
|
|
346
|
+
// `usual`: the usual proofs of the lemmas (the recurrence run as a machine from x = 0) and of the laws between (y from x),
|
|
347
|
+
// translated (`translatedRecur` below), or no nodes with why not.
|
|
348
|
+
export function recurNodes(r, usual, st = tstats()) {
|
|
337
349
|
const f = `${r.name}$`, x = v('x'), y = v('y'), kk = v('k$');
|
|
338
350
|
const facts = [
|
|
339
351
|
{ name: `${r.name}(0)`, keys: [], holds: eq(get(f, k(0)), r.zero) },
|
|
@@ -379,6 +391,8 @@ export function recurNodes(r) {
|
|
|
379
391
|
variants: [{ ...sp, rank: [...sp.rank, k(1)] }] };
|
|
380
392
|
return a;
|
|
381
393
|
});
|
|
394
|
+
if (usual)
|
|
395
|
+
return { facts, ...translatedRecur() };
|
|
382
396
|
const all = withReads(facts, [...own, ...lemmaAfter, ...betweenAfter]);
|
|
383
397
|
for (const [i, a] of all.slice(0, n + b).entries()) {
|
|
384
398
|
const o = shared(facts, a, all[n + b + i].concl);
|
|
@@ -386,6 +400,106 @@ export function recurNodes(r) {
|
|
|
386
400
|
all[i] = { ...a, opaque: o };
|
|
387
401
|
}
|
|
388
402
|
return { facts, specs: sliced(all.map(sp => (sp.variants ? { ...sp, variants: withReads(facts, sp.variants, all) } : sp))) };
|
|
403
|
+
// The usual proofs translated, as a machine's are: each lemma and law between three nodes. At x (at y), as above, its
|
|
404
|
+
// goal grown: from f(0) or the lemma at the start (x <= 0), or after a step to x (1 <= x). After a step, rank (0, x, 0):
|
|
405
|
+
// the usual step goal with x as x − 1 and fx as f(x − 1), the lemmas at x − 1 its uses; a law between likewise at y − 1
|
|
406
|
+
// (rank (1, y, 0)), fx as f(x), its uses the lemmas at x (the usual machine's facts) and at y − 1 and the laws between
|
|
407
|
+
// at y − 1 (each antecedent x <= y − 1 split first). At the start, rank (0, 0, 0) and (1, 0, 0): the usual start goal,
|
|
408
|
+
// fx0 as f(0) (a start that reads a function), fx as f(x).
|
|
409
|
+
function translatedRecur() {
|
|
410
|
+
const lemmaAfterT = [], betweenAfterT = [], lemmaStart = [], betweenStart = [];
|
|
411
|
+
const made = [], N = 2 * n + 2 * b;
|
|
412
|
+
const factIx = (name) => facts.findIndex((q, i) => i >= 2 && q.name === name);
|
|
413
|
+
// A usual goal's hypotheses: the machine's facts at the instances, its precondition and assumption, the laws assumed.
|
|
414
|
+
const layout = (u, i) => {
|
|
415
|
+
const g = obligations(u.m, u.laws, u.proof.instances, i), inst = u.proof.instances[u.laws[i].name] ?? {};
|
|
416
|
+
const fs = (u.m.facts ?? []).flatMap(q => { const ks = [...(q.forall ? [q.forall] : []), ...(q.keys ?? [])], flat = inst[q.name] ?? []; return ks.length ? Array.from({ length: Math.floor(flat.length / ks.length) }, (_, j) => ({ fact: q, at: flat.slice(j * ks.length, (j + 1) * ks.length) })) : [{ fact: q, at: [] }]; });
|
|
417
|
+
const np = u.m.pre ? 1 : 0, na = u.m.assume ? 1 : 0;
|
|
418
|
+
return { g0: g[0], g1: g[1], t0: u.proof.trees[g[0].name], t1: u.proof.trees[g[1].name], fs, np, na, ok: g[0].hyps.length === fs.length + np + 1 && g[1].hyps.length === fs.length + np + na + u.laws.length + 1 };
|
|
419
|
+
};
|
|
420
|
+
const last = (g) => g.hyps.at(-1).x;
|
|
421
|
+
if (n) {
|
|
422
|
+
const u = usual.lemmas;
|
|
423
|
+
if (!u || u.laws.map(l => l.name).join('\0') !== r.lemmas.map(l => l.name).join('\0') || u.laws.some(l => l.forall))
|
|
424
|
+
return { specs: [], why: 'lemmas: no usual proof of these lemmas' };
|
|
425
|
+
const sigma = vars({ x: less1(x), fx: get(f, less1(x)) }), sigma0 = vars({ fx0: get(f, k(0)) }), ren = renamer({ s: sigma }), ren0 = renamer({ s: sigma0 });
|
|
426
|
+
for (const [i] of r.lemmas.entries()) {
|
|
427
|
+
const lay = layout(u, i);
|
|
428
|
+
if (!lay.ok || !lay.t0 || !lay.t1)
|
|
429
|
+
return { specs: [], why: `${r.lemmas[i].name}: a usual goal of another shape` };
|
|
430
|
+
const used1 = usedHyps(lay.g1, lay.t1), used0 = usedHyps(lay.g0, lay.t0), insts1 = [], insts0 = [];
|
|
431
|
+
for (const [q, p] of lay.fs.entries()) {
|
|
432
|
+
const zeroFact = p.fact.name.startsWith('fx0 is '), ix = zeroFact ? 0 : factIx(p.fact.name);
|
|
433
|
+
for (const [used, out, s] of [[used1, insts1, sigma], [used0, insts0, sigma0]]) {
|
|
434
|
+
if (!used.has(q))
|
|
435
|
+
continue;
|
|
436
|
+
if (ix < 0 || (zeroFact && s === sigma))
|
|
437
|
+
return { specs: [], why: `${p.fact.name}: no fact of the recurrence's` };
|
|
438
|
+
out.push({ fact: ix, at: p.at.map(e => substE(e, s)) });
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
const pre = u.m.pre ? [substC(u.m.pre, sigma)] : [];
|
|
442
|
+
lemmaAfterT.push({ name: `${r.name}: ${r.lemmas[i].name}, after a step`, hyps: [le(k(1), x), ...pre, ...(u.m.assume ? [substC(u.m.assume, sigma)] : [])], concl: substC(last(lay.g1), sigma), rank: [k(0), x, k(0)],
|
|
443
|
+
insts: insts1, uses: lemmaUses(less1(x), yes).filter((_, j) => used1.has(lay.fs.length + lay.np + lay.na + j)) });
|
|
444
|
+
lemmaStart.push({ name: `${r.name}: ${r.lemmas[i].name}, at the start`, hyps: u.m.pre ? [u.m.pre] : [], concl: substC(last(lay.g0), sigma0), rank: [k(0), k(0), k(0)], insts: insts0, uses: [] });
|
|
445
|
+
made.push({ g: lay.g1, tree: lay.t1, node: n + b + i, ren }, { g: lay.g0, tree: lay.t0, node: N + i, ren: ren0 });
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
if (b) {
|
|
449
|
+
const u = usual.between, lemmaAt = (name) => r.lemmas.findIndex(l => `${l.name} at x` === name);
|
|
450
|
+
if (!u || u.laws.slice(n).map(l => l.name).join('\0') !== r.between.map(l => l.name).join('\0') || u.laws.some(l => l.forall))
|
|
451
|
+
return { specs: [], why: 'between: no usual proof of these laws' };
|
|
452
|
+
const sigma = vars({ y: less1(y), fy: get(f, less1(y)), fx: get(f, x) }), sigma0 = vars({ fx: get(f, x) }), ren = renamer({ s: sigma }), ren0 = renamer({ s: sigma0 });
|
|
453
|
+
for (const [i] of r.between.entries()) {
|
|
454
|
+
const lay = layout(u, n + i);
|
|
455
|
+
if (!lay.ok || !lay.t0 || !lay.t1)
|
|
456
|
+
return { specs: [], why: `${r.between[i].name}: a usual goal of another shape` };
|
|
457
|
+
const used1 = usedHyps(lay.g1, lay.t1), used0 = usedHyps(lay.g0, lay.t0);
|
|
458
|
+
const parts = (used, s, after) => {
|
|
459
|
+
const insts = [], uses = [];
|
|
460
|
+
for (const [q, p] of lay.fs.entries()) {
|
|
461
|
+
if (!used.has(q))
|
|
462
|
+
continue;
|
|
463
|
+
const j = lemmaAt(p.fact.name), ix = factIx(p.fact.name);
|
|
464
|
+
if (j >= 0)
|
|
465
|
+
uses.push({ node: j, subst: { x }, guard: yes });
|
|
466
|
+
else if (ix >= 0)
|
|
467
|
+
insts.push({ fact: ix, at: p.at.map(e => substE(e, s)) });
|
|
468
|
+
else
|
|
469
|
+
return undefined;
|
|
470
|
+
}
|
|
471
|
+
if (after)
|
|
472
|
+
for (const [j] of u.laws.entries())
|
|
473
|
+
if (used.has(lay.fs.length + lay.np + lay.na + j))
|
|
474
|
+
uses.push(j < n ? { node: j, subst: { x: less1(y) }, guard: yes } : { node: j, subst: { y: less1(y) }, guard: yes });
|
|
475
|
+
return { insts, uses };
|
|
476
|
+
};
|
|
477
|
+
const p1 = parts(used1, sigma, true), p0 = parts(used0, sigma0, false);
|
|
478
|
+
if (!p1 || !p0)
|
|
479
|
+
return { specs: [], why: `${r.between[i].name}: a fact of no recurrence's` };
|
|
480
|
+
const pre = u.m.pre ? [substC(u.m.pre, sigma)] : [];
|
|
481
|
+
betweenAfterT.push({ name: `${r.name}: ${r.between[i].name}, after a step`, hyps: [le(add(x, k(1)), y), ...pre, ...(u.m.assume ? [substC(u.m.assume, sigma)] : [])], concl: substC(last(lay.g1), sigma), rank: [k(1), y, k(0)], ...p1 });
|
|
482
|
+
betweenStart.push({ name: `${r.name}: ${r.between[i].name}, at the start`, hyps: u.m.pre ? [u.m.pre] : [], concl: substC(last(lay.g0), sigma0), rank: [k(1), k(0), k(0)], ...p0 });
|
|
483
|
+
made.push({ g: lay.g1, tree: lay.t1, node: 2 * n + b + i, ren, decide: p1.uses.flatMap(us => (us.node >= n && us.node < n + b ? [le(x, less1(y))] : [])) }, { g: lay.g0, tree: lay.t0, node: N + n + i, ren: ren0 });
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
const carried = own.map((sp, i) => ({ ...sp, variants: undefined, carry: true, uses: [...sp.uses, i < n ? { node: N + i, subst: {}, guard: le(x, k(0)) } : { node: N + i, subst: {}, guard: le(y, x) }] }));
|
|
487
|
+
const rest = [...lemmaAfterT, ...betweenAfterT, ...lemmaStart, ...betweenStart], all = [...withReads(facts, carried, [...carried, ...rest]), ...rest];
|
|
488
|
+
for (const [i, a] of all.slice(0, n + b).entries()) {
|
|
489
|
+
const o = [...new Set([...shared(facts, a, all[n + b + i].concl), ...shared(facts, a, all[N + i].concl)].map(e => JSON.stringify(e)))].map(e => JSON.parse(e));
|
|
490
|
+
if (o.length)
|
|
491
|
+
all[i] = { ...a, opaque: o };
|
|
492
|
+
}
|
|
493
|
+
const until = performance.now() + 10_000;
|
|
494
|
+
for (const m of made) {
|
|
495
|
+
const whole = mainGoal(facts, all, all[m.node]);
|
|
496
|
+
const tree = timedKind('translate', 0, () => withProducts(() => decide(whole, m.decide ?? [], wp => translate(m.g, whole, m.tree, m.ren, st, until, new Map(), wp), st, until)));
|
|
497
|
+
if (!tree)
|
|
498
|
+
return { specs: [], why: `${all[m.node].name}: not translated` };
|
|
499
|
+
all[m.node] = { ...all[m.node], main: tree };
|
|
500
|
+
}
|
|
501
|
+
return { specs: sliced(all) };
|
|
502
|
+
}
|
|
389
503
|
}
|
|
390
504
|
// Function facts at the keys a node's goal reads them at: each fact about every key of a function read f[k] (the key
|
|
391
505
|
// alone) at each key f is read at, twice over (a fact's own reads give more keys).
|
|
@@ -426,7 +540,9 @@ function withReads(facts, specs, nodes = specs) {
|
|
|
426
540
|
// read from a map of the state (a table of nodes: a node's fold at its child), whose cells a run's maps put at keys
|
|
427
541
|
// the prover would have to prove equal through products (`machineNodes` proves those laws far slower than the usual
|
|
428
542
|
// rule, past its budget: research/cyclic/probe.ts).
|
|
429
|
-
export const cyclicMachine = (m, laws = []) =>
|
|
543
|
+
export const cyclicMachine = (m, laws = []) => uncoveredWhy(m, laws) === undefined;
|
|
544
|
+
// Why a machine's laws are no instance's (undefined where they are one).
|
|
545
|
+
export const uncoveredWhy = (m, laws = []) => {
|
|
430
546
|
const maps = Object.keys(m.maps ?? {});
|
|
431
547
|
const nested = (t) => { let found = false; const walk = (x, inKey) => { if (found || !x || typeof x !== 'object')
|
|
432
548
|
return; const o = x; if (o.op === 'get' && inKey && maps.includes(o.map)) {
|
|
@@ -437,7 +553,7 @@ export const cyclicMachine = (m, laws = []) => {
|
|
|
437
553
|
return;
|
|
438
554
|
} for (const val of Object.values(o))
|
|
439
555
|
walk(val, inKey); }; walk(t, false); return found; };
|
|
440
|
-
return
|
|
556
|
+
return m.history ? 'a history' : Object.values(m.step.flags ?? {}).some(c => c.op === 'all') ? 'a flag at every key' : laws.some(l => nested(l.holds)) ? 'a law reading a map at a key read from a map' : undefined;
|
|
441
557
|
};
|
|
442
558
|
// The run as maps over time: names at time u read as their maps there.
|
|
443
559
|
function trace(m) {
|
|
@@ -470,7 +586,8 @@ const flagsOf = (m) => {
|
|
|
470
586
|
out[f] = substC(c, { vars: {}, maps: {}, flags: out });
|
|
471
587
|
return out;
|
|
472
588
|
};
|
|
473
|
-
|
|
589
|
+
// `lawsMade`: the laws' nodes are made already (their trees not translated again).
|
|
590
|
+
export function machineNodes(m, laws, instances, usual, st = tstats()) {
|
|
474
591
|
const { at } = trace(m), flags = flagsOf(m), inline = (t) => substC(t, { vars: {}, maps: {}, flags });
|
|
475
592
|
const t = v('t$'), T = v('T$'), u = v('u$'), key = v('k$');
|
|
476
593
|
const inlineE = (e) => substE(e, { vars: {}, maps: {}, flags });
|
|
@@ -635,7 +752,161 @@ export function machineNodes(m, laws, instances) {
|
|
|
635
752
|
insts: [...run(t), ...factInsts, ...base, ...state.map(st => ({ fact: fi(`${m.name}: ${st} is a natural`), at: [t] })), { fact: fi(`${m.name}: params are naturals`), at: [] }], uses,
|
|
636
753
|
};
|
|
637
754
|
});
|
|
755
|
+
if (usual)
|
|
756
|
+
return { facts, ...translatedNodes() };
|
|
638
757
|
return { facts, specs: sliced(stepNodesOn() ? stepNodes(specs) : specs) };
|
|
758
|
+
// The usual proof translated (core/translate.ts): each law three nodes. At t, rank (t, 1), as above (Bridge.lean's
|
|
759
|
+
// cycMOk reads it), its goal grown: it carries the law at the start or after t's step to the cells at t through the
|
|
760
|
+
// start's and the step's equations. After a step from t − 1, rank (t, 0): the usual step goal over the run's cells at
|
|
761
|
+
// t − 1 (each hypothesis of it read so: the functions' facts at the keys the usual proof names, the precondition, the
|
|
762
|
+
// request's assumption, every law at t − 1 at those keys, each a use guarded by 0 <= 0, its descent from 1 <= t), its
|
|
763
|
+
// tree the usual one's renamed. At the start, rank (0, 0): the usual start goal (the law at the start's values), the
|
|
764
|
+
// law's fresh key k# its own k, its tree the usual one's. Only the facts and uses the usual tree reads are kept.
|
|
765
|
+
function translatedNodes() {
|
|
766
|
+
const L = laws.length, pre = at(less1(t)), one = le(k(1), t), omegaHyp = mapNames.length ? [{ op: '<', a: T, b: omega }] : [];
|
|
767
|
+
const fresh = Object.fromEntries(laws.flatMap(x => (x.forall ? [[`${x.forall}#`, v(x.forall)]] : [])));
|
|
768
|
+
const sigma = { vars: { ...pre.vars, ...fresh }, maps: pre.maps, flags: {} };
|
|
769
|
+
const ren = renamer({ s: sigma, cells: Object.fromEntries([...mapNames, ...(m.inputMaps ?? [])].map(map => [map, { to: `${map}@`, at: less1(t) }])) }), ren0 = renamer({ s: vars(fresh) });
|
|
770
|
+
const fnInsts = (inst, frame) => (m.facts ?? []).flatMap(f => {
|
|
771
|
+
const ks = keysOf(f), flat = inst[f.name] ?? [], fact = fi(f.name);
|
|
772
|
+
if (!ks.length)
|
|
773
|
+
return [{ fact, at: [] }];
|
|
774
|
+
return Array.from({ length: Math.floor(flat.length / ks.length) }, (_, i) => ({ fact, at: flat.slice(i * ks.length, (i + 1) * ks.length).map(e => substE(substE(e, vars(fresh)), frame)) }));
|
|
775
|
+
});
|
|
776
|
+
const goals = new Map(obligations(m, laws, instances).map(g => [g.name, g]));
|
|
777
|
+
// The state the step gives over the frame before it, a write's reads at its key read at the key read instead (as the
|
|
778
|
+
// map's step fact has them): the usual step goal's conclusion but for those reads, which the translation reads at
|
|
779
|
+
// the key read where the usual path has the two keys equal (two cells over time then split apart: `close`).
|
|
780
|
+
const upd = { flags: {}, vars: Object.fromEntries(state.map(st2 => [st2, inlineE(m.step.state?.[st2] ?? v(st2))])), maps: Object.fromEntries(mapNames.map(map => [map, (kk) => {
|
|
781
|
+
const w = m.step.maps?.[map];
|
|
782
|
+
return !w ? get(map, kk) : 'each' in w ? substE(inlineE(w.value), vars({ [w.each]: kk })) : { op: 'if', c: eq(kk, inlineE(w.key)), then: readsAt(inlineE(w.value), inlineE(w.key), kk), else: get(map, kk) };
|
|
783
|
+
}])) };
|
|
784
|
+
// A step law's condition with its primed names read as given, its flags inlined (over the frame before the step).
|
|
785
|
+
const primedOf = (c, s0) => substC(c, { ...s0, flags });
|
|
786
|
+
const nows = [], afters = [], starts = [], made = [];
|
|
787
|
+
// A write's key and the keys the usual conclusion reads its map at (each a condition if(K = W, ...)).
|
|
788
|
+
const wkeys = new Set(mapNames.flatMap(map => { const w = m.step.maps?.[map]; return w && !('each' in w) ? [JSON.stringify(inlineE(w.key))] : []; }));
|
|
789
|
+
const readsOf = (c) => {
|
|
790
|
+
const out = [], seen = new Set();
|
|
791
|
+
const walk = (x) => {
|
|
792
|
+
if (!x || typeof x !== 'object')
|
|
793
|
+
return;
|
|
794
|
+
const o = x;
|
|
795
|
+
if (o.op === 'if' && o.c.op === '=' && wkeys.has(JSON.stringify(o.c.b))) {
|
|
796
|
+
const id = JSON.stringify([o.c.a, o.c.b]);
|
|
797
|
+
if (!seen.has(id)) {
|
|
798
|
+
seen.add(id);
|
|
799
|
+
out.push({ k: o.c.a, w: o.c.b });
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
for (const val of Object.values(o))
|
|
803
|
+
walk(val);
|
|
804
|
+
};
|
|
805
|
+
walk(c);
|
|
806
|
+
return out;
|
|
807
|
+
};
|
|
808
|
+
for (const [i, l] of laws.entries()) {
|
|
809
|
+
const g0 = goals.get(`${l.name} @start`), g1 = goals.get(`${l.name} @step`), t0 = g0 && usual.trees[g0.name], t1 = g1 && usual.trees[g1.name];
|
|
810
|
+
if (!g0 || !g1 || !t0 || !t1)
|
|
811
|
+
return { specs: [], why: `${l.name}: no usual tree` };
|
|
812
|
+
const inst = instances[l.name] ?? {}, nf = assumed(m.facts ?? [], inst).length, np = m.pre ? 1 : 0, na = m.assume ? 1 : 0;
|
|
813
|
+
const uses = laws.flatMap((l2, j) => {
|
|
814
|
+
const ks = keysOf(l2);
|
|
815
|
+
if (!ks.length)
|
|
816
|
+
return [{ node: j, subst: { 't$': less1(t) }, guard: yes }];
|
|
817
|
+
const flat = inst[l2.name] ?? [];
|
|
818
|
+
return Array.from({ length: Math.floor(flat.length / ks.length) }, (_, q) => ({ node: j, subst: { 't$': less1(t), ...Object.fromEntries(ks.map((kn, r) => [kn, substE(flat[q * ks.length + r], sigma)])) }, guard: yes }));
|
|
819
|
+
});
|
|
820
|
+
const fn1 = fnInsts(inst, pre), fn0 = fnInsts(inst, vars({}));
|
|
821
|
+
if (g1.hyps.length !== nf + np + na + uses.length + 1 || g0.hyps.length !== nf + np + 1 || fn1.length !== nf)
|
|
822
|
+
return { specs: [], why: `${l.name}: a usual goal of another shape` };
|
|
823
|
+
const used1 = usedHyps(g1, t1), used0 = usedHyps(g0, t0), last = (g) => g.hyps.at(-1).x;
|
|
824
|
+
const concl = substC(l.holds, at(t));
|
|
825
|
+
afters.push({ name: `${m.name}: ${l.name}, after a step`, hyps: [...(m.pre ? [m.pre] : []), one, le(t, T), ...omegaHyp, ...(m.assume ? [now(inline(m.assume), less1(t))] : [])],
|
|
826
|
+
concl: substC(substC(substC(l.holds, vars(fresh)), upd), pre), rank: [t, k(0)], insts: [...fn1.filter((_, q) => used1.has(q))], uses: uses.filter((_, j) => used1.has(nf + np + na + j)) });
|
|
827
|
+
starts.push({ name: `${m.name}: ${l.name}, at the start`, hyps: m.pre ? [m.pre] : [], concl: substC(last(g0), vars(fresh)), rank: [k(0), k(0)], insts: [...fn0.filter((_, q) => used0.has(q))], uses: [] });
|
|
828
|
+
nows.push({ name: `${m.name}: ${l.name}`, hyps: [...(m.pre ? [m.pre] : []), le(t, T), ...omegaHyp], concl, rank: [t, k(1)], insts: [...run(t), ...mapInsts(concl)],
|
|
829
|
+
uses: [{ node: L + i, subst: {}, guard: one }, { node: 2 * L + i, subst: {}, guard: le(t, k(0)) }], carry: true });
|
|
830
|
+
made.push({ g: g1, tree: t1, node: L + i, ren, decide: [one], reads: readsOf(last(g1)) }, { g: g0, tree: t0, node: 2 * L + i, ren: ren0 });
|
|
831
|
+
}
|
|
832
|
+
// One-step step laws (`usual.steps`), each two nodes after the laws' (core/cyclic.ts's header has their shape): after a
|
|
833
|
+
// step from t − 1, rank (t, 0), the usual step law's goal over the run's cells at t − 1 (its `when` and the request's
|
|
834
|
+
// assumption hypotheses, the laws at t − 1 at its keys uses), the state after it the step's terms; and the step law
|
|
835
|
+
// itself, rank (t, 2), over the cells at t − 1 and at t (a primed name at t), carried from the first by the step's
|
|
836
|
+
// facts at t, its hypotheses the precondition, 1 <= t <= T, T < Ω and `when` at t − 1.
|
|
837
|
+
const stepSpecs = [];
|
|
838
|
+
for (const [q, u] of (usual.steps ?? []).entries()) {
|
|
839
|
+
const { sl, inst, goal: g, tree } = u, whens = sl.when === undefined ? [] : Array.isArray(sl.when) ? sl.when : [sl.when], K = sl.steps ?? 1;
|
|
840
|
+
// (k steps: a machine with no map, the state after each step a cell at its time; one step: any the instances cover)
|
|
841
|
+
if (K > 1 && (mapNames.length || m.inputMaps?.length))
|
|
842
|
+
return { specs: [], why: `${sl.name}: steps over a map` };
|
|
843
|
+
// The times the steps start from (t − k, ..., t − 1), and the frames: the state before step j its cells at t − k + j − 1
|
|
844
|
+
// (n~j the state after step j, at t − k + j), its request read there (i@j).
|
|
845
|
+
const time = (back) => (back === 0 ? t : { op: 'sub', a: t, b: k(back) });
|
|
846
|
+
const frame = { vars: { ...at(time(K)).vars, ...Object.fromEntries(Array.from({ length: K }, (_, j) => j + 1).flatMap(j => [
|
|
847
|
+
...(j >= 2 ? m.inputs.map(i => [`${i}@${j}`, get(`${i}@`, time(K - j + 1))]) : []),
|
|
848
|
+
...(j < K ? state.map(n => [`${n}~${j}`, get(`${n}@`, time(K - j))]) : []),
|
|
849
|
+
])) }, maps: pre.maps, flags: {} };
|
|
850
|
+
const sig = K === 1 ? sigma : frame, renK = K === 1 ? ren : renamer({ s: frame });
|
|
851
|
+
// (the usual goal's hypotheses: the precondition, the facts at the instances, then each step's laws, assumption and
|
|
852
|
+
// `when`, and after each step but the last its state's equations)
|
|
853
|
+
const nf = assumed(m.facts ?? [], inst).length, np = m.pre ? 1 : 0, na = m.assume ? 1 : 0, nl = assumed(laws, inst).length;
|
|
854
|
+
const lawsAt = (back) => laws.flatMap((l2, j) => {
|
|
855
|
+
const ks = keysOf(l2), tb = time(back), ab = at(tb);
|
|
856
|
+
if (!ks.length)
|
|
857
|
+
return [{ node: j, subst: { 't$': tb }, guard: yes }];
|
|
858
|
+
const flat = inst[l2.name] ?? [];
|
|
859
|
+
return Array.from({ length: Math.floor(flat.length / ks.length) }, (_, r) => ({ node: j, subst: { 't$': tb, ...Object.fromEntries(ks.map((kn, c) => [kn, substE(flat[r * ks.length + c], ab)])) }, guard: yes }));
|
|
860
|
+
});
|
|
861
|
+
const perStep = Array.from({ length: K }, (_, j) => lawsAt(K - j)), fn1 = fnInsts(inst, sig);
|
|
862
|
+
// Each step's block: its laws, its assumption, its when, then (but after the last) its state's equations.
|
|
863
|
+
const blocks = [];
|
|
864
|
+
let pos = np + nf;
|
|
865
|
+
for (let j = 0; j < K; j++) {
|
|
866
|
+
blocks.push(pos);
|
|
867
|
+
pos += nl + na + (whens[j] ? 1 : 0) + (j < K - 1 ? state.length : 0);
|
|
868
|
+
}
|
|
869
|
+
if (g.hyps.length !== pos + 1 || fn1.length !== nf || perStep.some(us => us.length !== nl))
|
|
870
|
+
return { specs: [], why: `${sl.name}: a usual goal of another shape` };
|
|
871
|
+
const used = usedHyps(g, tree), last = g.hyps.at(-1).x;
|
|
872
|
+
const assumes = Array.from({ length: K }, (_, j) => (m.assume ? [now(inline(m.assume), time(K - j))] : [])).flat();
|
|
873
|
+
const when = whens.map((w, j) => now(inline(w), time(K - j)));
|
|
874
|
+
const bounds = [...(m.pre ? [m.pre] : []), le(k(K), t), le(t, T), ...omegaHyp];
|
|
875
|
+
const after = K === 1 ? substC(primedOf(sl.then, { vars: Object.fromEntries(state.map(st2 => [`${st2}'`, inlineE(m.step.state?.[st2] ?? v(st2))])), maps: Object.fromEntries(mapNames.map(map => [`${map}'`, upd.maps[map]])), flags: {} }), pre) : substC(last, frame);
|
|
876
|
+
const later = Object.fromEntries(Array.from({ length: K - 1 }, (_, j) => j + 2).flatMap(j => m.inputs.map(i => [`${i}@${j}`, get(`${i}@`, time(K - j + 1))])));
|
|
877
|
+
const cells = substC(primedOf(sl.then, { vars: { ...later, ...Object.fromEntries(state.map(st2 => [`${st2}'`, get(`${st2}@`, t)])) }, maps: Object.fromEntries(mapNames.map(map => [`${map}'`, (kk) => get(`${map}@`, pair(t, kk))])), flags: {} }), at(time(K)));
|
|
878
|
+
const a = 3 * L + 2 * q, steps = Array.from({ length: K - 1 }, (_, j) => ({ fact: 1, at: [time(K - j - 1)] }));
|
|
879
|
+
// (the state after each step but the last its equations, hypotheses here as in the usual goal: the step's facts at
|
|
880
|
+
// those times give them where the step law is, `steps` below)
|
|
881
|
+
const eqs = blocks.slice(0, K - 1).flatMap((b0, j) => g.hyps.slice(b0 + nl + na + (whens[j] ? 1 : 0), b0 + nl + na + (whens[j] ? 1 : 0) + state.length).map(h => substC(h, frame)));
|
|
882
|
+
stepSpecs.push({ name: `${m.name}: ${sl.name}, after ${K === 1 ? 'a step' : `${K} steps`}`, hyps: [...bounds, ...assumes, ...when, ...eqs], concl: after, rank: [t, k(0)],
|
|
883
|
+
insts: fn1.filter((_, r) => used.has(np + r)), uses: perStep.flatMap((us, j) => us.filter((_, r) => used.has(blocks[j] + r))) });
|
|
884
|
+
stepSpecs.push({ name: `${m.name}: ${sl.name}`, hyps: [...bounds, ...when], concl: cells, rank: [t, k(2)],
|
|
885
|
+
insts: [...run(t), ...steps, ...mapInsts(cells)], uses: [{ node: a, subst: {}, guard: yes }], carry: true });
|
|
886
|
+
// (each time decided at least the steps before it first: t ∸ j is then t − j)
|
|
887
|
+
made.push({ g, tree, node: a, ren: renK, decide: Array.from({ length: K }, (_, j) => le(k(j + 1), t)), reads: K === 1 ? readsOf(last) : [] });
|
|
888
|
+
}
|
|
889
|
+
const all = [...nows, ...afters, ...starts, ...stepSpecs];
|
|
890
|
+
for (const [i, a] of nows.entries()) {
|
|
891
|
+
const o = [...new Set([...shared(facts, a, afters[i].concl), ...shared(facts, a, starts[i].concl)].map(e => JSON.stringify(e)))].map(e => JSON.parse(e));
|
|
892
|
+
if (o.length)
|
|
893
|
+
a.opaque = o;
|
|
894
|
+
}
|
|
895
|
+
// (a translation is bounded by a minute: a step law over 32 steps has a goal of 32 frames at every leaf)
|
|
896
|
+
const until = performance.now() + 60_000;
|
|
897
|
+
for (const x of made) {
|
|
898
|
+
if (usual.lawsMade && x.node < 3 * L)
|
|
899
|
+
continue;
|
|
900
|
+
// (after a step, 1 <= t decided first: t ∸ 1 is then t − 1, and a cell read at a key over the cells at t − 1 has a
|
|
901
|
+
// linear form)
|
|
902
|
+
const whole = mainGoal(facts, all, all[x.node]);
|
|
903
|
+
const tree = timedKind('translate', 0, () => withProducts(() => decide(whole, x.decide ?? [], wp => readsFirst(whole, x.reads ?? [], x.ren, (p, swaps) => translate(x.g, whole, x.tree, x.ren, st, until, new Map(), p, swaps), st, until, wp), st, until)));
|
|
904
|
+
if (!tree)
|
|
905
|
+
return { specs: [], why: `${all[x.node].name}: not translated` };
|
|
906
|
+
all[x.node] = { ...all[x.node], main: tree };
|
|
907
|
+
}
|
|
908
|
+
return { specs: sliced(all) };
|
|
909
|
+
}
|
|
639
910
|
}
|
|
640
911
|
// The terms (not a read, a variable or a constant) of a conclusion that a node's facts read too, largest first; none
|
|
641
912
|
// inside a product (a product of a term's linear form is no linear form).
|
|
@@ -676,10 +947,224 @@ function shared(facts, a, concl) {
|
|
|
676
947
|
walk(concl);
|
|
677
948
|
return out.sort((x, y) => JSON.stringify(y).length - JSON.stringify(x).length);
|
|
678
949
|
}
|
|
950
|
+
// --- step laws and k-step laws over the run -----------------------------------------------------------------------
|
|
951
|
+
// A k-step law (or a step law, k = 1) as a node over the machine's run (BridgeRules.lean's cycKOk, cyc_kstep_sound
|
|
952
|
+
// carries it to the law): the run's last k steps up to time t, each time one step back from the next (t − 1, t − 1 − 1,
|
|
953
|
+
// …), as a step's fact reads the time before it, so the cells a step gives are those the next one reads. The first state
|
|
954
|
+
// at t − k, the j-th request at t − (k − j), the state after the last at t.
|
|
955
|
+
export const tBack = (n) => (n === 0 ? v('t$') : less1(tBack(n - 1)));
|
|
956
|
+
// The same times read t − n at once (BridgeRules.lean's tSub; core/translate.ts's step laws over several steps).
|
|
957
|
+
export const tSub = (n) => (n === 0 ? v('t$') : { op: 'sub', a: v('t$'), b: k(n) });
|
|
958
|
+
// `then` over the run (BridgeRules.lean's kσ and kRd): the primed names at t, each later request's inputs (input@j) and
|
|
959
|
+
// the maps it carries (map@j) at its time, the plain names at t − k.
|
|
960
|
+
export function kStepAt(m, k, time = tBack) {
|
|
961
|
+
const { at } = trace(m), first = at(time(k)), last = at(time(0));
|
|
962
|
+
const vars = { ...first.vars }, maps = { ...first.maps };
|
|
963
|
+
for (const x of Object.keys(m.state))
|
|
964
|
+
vars[`${x}'`] = last.vars[x];
|
|
965
|
+
for (const map of Object.keys(m.maps ?? {}))
|
|
966
|
+
maps[`${map}'`] = last.maps[map];
|
|
967
|
+
for (let j = 1; j < k; j++) {
|
|
968
|
+
const a = at(time(k - j));
|
|
969
|
+
for (const i of m.inputs)
|
|
970
|
+
vars[`${i}@${j + 1}`] = a.vars[i];
|
|
971
|
+
for (const c of m.inputMaps ?? [])
|
|
972
|
+
maps[`${c}@${j + 1}`] = a.maps[c];
|
|
973
|
+
}
|
|
974
|
+
return { vars, maps, flags: {} };
|
|
975
|
+
}
|
|
976
|
+
// A k-step law's `when`s, one per step (none: 0 <= 0), its flags inlined, as Lean's SKStep has them.
|
|
977
|
+
export const kWhens = (m, sl) => {
|
|
978
|
+
const ws = sl.when === undefined ? [] : Array.isArray(sl.when) ? sl.when : [sl.when], flags = flagsOf(m);
|
|
979
|
+
return Array.from({ length: sl.steps ?? 1 }, (_, j) => (ws[j] ? substC(ws[j], { vars: {}, maps: {}, flags }) : yes));
|
|
980
|
+
};
|
|
981
|
+
// The nodes of the step laws, after the laws' (`laws` the machine's, `lawNodes` their nodes, the first L at time t, as
|
|
982
|
+
// machineNodes makes them). Each node: the precondition, k <= t <= T, T < Ω and each step's `when` at its time;
|
|
983
|
+
// the run's facts at the times it reads (each step's, each map cell's step back from where it is read, the functions'
|
|
984
|
+
// facts at the keys the usual proof names), every law at each state but the last (at the keys it names), and `then`.
|
|
985
|
+
export function stepLawNodes(m, laws, steps, facts, lawNodes, time = tBack) {
|
|
986
|
+
const rank = lawNodes[0]?.rank.length ?? 2;
|
|
987
|
+
const { at } = trace(m), flags = flagsOf(m), inline = (c) => substC(c, { vars: {}, maps: {}, flags });
|
|
988
|
+
const t = v('t$'), T = v('T$'), mapNames = Object.keys(m.maps ?? {}), traced = [...mapNames, ...(m.inputMaps ?? [])], state = Object.keys(m.state);
|
|
989
|
+
const fi = (name) => facts.findIndex(f => f.name === name);
|
|
990
|
+
const keysOf = (l) => [...(l.forall ? [l.forall] : []), ...(l.keys ?? [])];
|
|
991
|
+
const tuples = (inst, l) => { const ks = keysOf(l), flat = inst[l.name] ?? []; return ks.length ? Array.from({ length: Math.floor(flat.length / ks.length) }, (_, i) => flat.slice(i * ks.length, (i + 1) * ks.length)) : [[]]; };
|
|
992
|
+
const specs = steps.map(({ law: sl, instances: inst }) => {
|
|
993
|
+
const K = sl.steps ?? 1, times = Array.from({ length: K + 1 }, (_, j) => time(K - j));
|
|
994
|
+
const hyps = [...(m.pre ? [m.pre] : []), le(k(K), t), le(t, T), ...(traced.length ? [{ op: '<', a: T, b: omega }] : []),
|
|
995
|
+
...kWhens(m, sl).map((w, j) => substC(w, at(times[j])))];
|
|
996
|
+
const concl = substC(inline(sl.then), kStepAt(m, K, time));
|
|
997
|
+
// Every law at each state but the last, at the keys the proof names there.
|
|
998
|
+
const uses = times.slice(0, K).flatMap(u => laws.flatMap((l, i) => tuples(inst, l).map(ts => ({
|
|
999
|
+
node: i, subst: { 't$': u, ...Object.fromEntries(keysOf(l).map((kn, q) => [kn, substE(ts[q], at(u))])) }, guard: yes
|
|
1000
|
+
}))));
|
|
1001
|
+
const insts = [
|
|
1002
|
+
...times.slice(1).map(u => ({ fact: fi(`${m.name}: a step`), at: [u] })),
|
|
1003
|
+
...times.flatMap(u => state.map(st => ({ fact: fi(`${m.name}: ${st} is a natural`), at: [u] }))),
|
|
1004
|
+
{ fact: fi(`${m.name}: params are naturals`), at: [] },
|
|
1005
|
+
...(m.facts ?? []).flatMap(f => tuples(inst, f).map(ts => ({ fact: fi(f.name), at: ts.map(e => substE(e, at(times[0]))) }))),
|
|
1006
|
+
];
|
|
1007
|
+
// Each map cell the node reads at a time after the first: that map's step there, back to the time before, and so on.
|
|
1008
|
+
const seen = new Set(), ix = new Map(times.map((u, j) => [JSON.stringify(u), j]));
|
|
1009
|
+
const cellFacts = (x) => {
|
|
1010
|
+
if (!x || typeof x !== 'object')
|
|
1011
|
+
return;
|
|
1012
|
+
if (Array.isArray(x)) {
|
|
1013
|
+
x.forEach(cellFacts);
|
|
1014
|
+
return;
|
|
1015
|
+
}
|
|
1016
|
+
const o = x;
|
|
1017
|
+
if (o.op === 'get' && typeof o.map === 'string' && o.map.endsWith('@') && traced.includes(o.map.slice(0, -1))) {
|
|
1018
|
+
const key = o.key;
|
|
1019
|
+
if (key.op === 'add' && key.b.op === 'prod' && JSON.stringify(key.b.b) === JSON.stringify(omega)) {
|
|
1020
|
+
const j = ix.get(JSON.stringify(key.a)), map = o.map.slice(0, -1), id = `${map}:${JSON.stringify(key)}`, u = key.a, cell = key.b.a;
|
|
1021
|
+
if (j !== undefined && !seen.has(id) && mapNames.includes(map)) {
|
|
1022
|
+
seen.add(id);
|
|
1023
|
+
const more = [...(j > 0 ? ['at a step'] : []), '\'s cells are naturals'].map(what => ({ fact: fi(`${m.name}: ${map}${what.startsWith('\'') ? what : ` ${what}`}`), at: [u, cell] }));
|
|
1024
|
+
insts.push(...more);
|
|
1025
|
+
for (const p of more)
|
|
1026
|
+
cellFacts(substC(facts[p.fact].holds, vars({ 'u$': p.at[0], 'k$': p.at[1] })));
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
for (const val of Object.values(o))
|
|
1031
|
+
cellFacts(val);
|
|
1032
|
+
};
|
|
1033
|
+
cellFacts([hyps, concl, uses.map(us => substC(laws[us.node].holds, { ...at(us.subst['t$']), vars: { ...at(us.subst['t$']).vars, ...Object.fromEntries(Object.entries(us.subst).filter(([n]) => n !== 't$')) } }))]);
|
|
1034
|
+
for (const p of [...insts])
|
|
1035
|
+
if (p.fact === fi(`${m.name}: a step`))
|
|
1036
|
+
cellFacts(substC(facts[p.fact].holds, vars({ 'u$': p.at[0] })));
|
|
1037
|
+
return { name: `${m.name}: ${sl.name}`, hyps, concl, rank: rank === 1 ? [t] : [t, ...Array.from({ length: rank - 1 }, () => k(2))], insts: insts.filter(p => p.fact >= 0), uses };
|
|
1038
|
+
});
|
|
1039
|
+
return sliced(withReads(facts, specs, [...lawNodes, ...specs]));
|
|
1040
|
+
}
|
|
1041
|
+
// A request allowed (BridgeRules.lean's cycAOk, cyc_allow_sound carries it to the claim): from every reachable state,
|
|
1042
|
+
// every request meeting `w` (a helpful request's region and terms), carrying empty sets, is one the machine allows. A node
|
|
1043
|
+
// at time t, the request the run takes at t the claim's (no step reads it): the precondition, t <= T, T < Ω, `w` at t,
|
|
1044
|
+
// each carried map's cell the assumption reads at t 0, every law at t (rank (t, 2) above theirs), the run's facts at t;
|
|
1045
|
+
// its conclusion the assumption at t, flags inlined.
|
|
1046
|
+
export function allowNode(m, laws, inst, w, facts, lawNodes, name, extra = []) {
|
|
1047
|
+
const { at } = trace(m), flags = flagsOf(m), t = v('t$'), T = v('T$'), mapNames = Object.keys(m.maps ?? {}), carried = m.inputMaps ?? [];
|
|
1048
|
+
const traced = [...mapNames, ...carried], state = Object.keys(m.state), now = at(t);
|
|
1049
|
+
const fi = (n) => facts.findIndex(f => f.name === n);
|
|
1050
|
+
const keysOf = (l) => [...(l.forall ? [l.forall] : []), ...(l.keys ?? [])];
|
|
1051
|
+
const tuples = (l) => { const ks = keysOf(l), flat = inst[l.name] ?? []; return ks.length ? Array.from({ length: Math.floor(flat.length / ks.length) }, (_, i) => flat.slice(i * ks.length, (i + 1) * ks.length)) : [[]]; };
|
|
1052
|
+
const concl = substC(substC(m.assume ?? yes, { vars: {}, maps: {}, flags }), now);
|
|
1053
|
+
// Each carried cell the conclusion and the facts given at t read there (a flag's body at its witness, say): 0 (the
|
|
1054
|
+
// request carries empty sets).
|
|
1055
|
+
const zeros = [], seen = new Set();
|
|
1056
|
+
const walk = (x) => {
|
|
1057
|
+
if (!x || typeof x !== 'object')
|
|
1058
|
+
return;
|
|
1059
|
+
const o = x;
|
|
1060
|
+
if (o.op === 'get' && typeof o.map === 'string' && o.map.endsWith('@') && carried.includes(o.map.slice(0, -1))) {
|
|
1061
|
+
const id = JSON.stringify(o);
|
|
1062
|
+
if (!seen.has(id)) {
|
|
1063
|
+
seen.add(id);
|
|
1064
|
+
zeros.push(eq(o, k(0)));
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
1067
|
+
for (const val of Object.values(o))
|
|
1068
|
+
walk(val);
|
|
1069
|
+
};
|
|
1070
|
+
walk([concl, instHyps(facts, extra)]);
|
|
1071
|
+
const hyps = [...(m.pre ? [m.pre] : []), le(t, T), ...(traced.length ? [{ op: '<', a: T, b: omega }] : []), substC(w, now), ...zeros];
|
|
1072
|
+
const uses = laws.flatMap((l, i) => tuples(l).map(ts => ({ node: i, subst: Object.fromEntries(keysOf(l).map((kn, q) => [kn, substE(ts[q], now)])), guard: yes })));
|
|
1073
|
+
const insts = [{ fact: fi(`${m.name}: the start`), at: [t] }, { fact: fi(`${m.name}: a step`), at: [t] },
|
|
1074
|
+
...state.map(st => ({ fact: fi(`${m.name}: ${st} is a natural`), at: [t] })), { fact: fi(`${m.name}: params are naturals`), at: [] },
|
|
1075
|
+
...(m.facts ?? []).flatMap(f => tuples(f).map(ts => ({ fact: fi(f.name), at: ts.map(e => substE(e, now)) }))), ...extra];
|
|
1076
|
+
// Each map cell read at t: its start, its step and that it is a natural there.
|
|
1077
|
+
const cells = new Set();
|
|
1078
|
+
const cellWalk = (x) => {
|
|
1079
|
+
if (!x || typeof x !== 'object')
|
|
1080
|
+
return;
|
|
1081
|
+
const o = x;
|
|
1082
|
+
if (o.op === 'get' && typeof o.map === 'string' && o.map.endsWith('@') && mapNames.includes(o.map.slice(0, -1))) {
|
|
1083
|
+
const key = o.key;
|
|
1084
|
+
if (key.op === 'add' && JSON.stringify(key.a) === JSON.stringify(t) && key.b.op === 'prod') {
|
|
1085
|
+
const map = o.map.slice(0, -1), cell = key.b.a, id = `${map}:${JSON.stringify(cell)}`;
|
|
1086
|
+
if (!cells.has(id)) {
|
|
1087
|
+
cells.add(id);
|
|
1088
|
+
insts.push(...['at the start', 'at a step', '\'s cells are naturals'].map(what => ({ fact: fi(`${m.name}: ${map}${what.startsWith('\'') ? what : ` ${what}`}`), at: [t, cell] })));
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1091
|
+
}
|
|
1092
|
+
for (const val of Object.values(o))
|
|
1093
|
+
cellWalk(val);
|
|
1094
|
+
};
|
|
1095
|
+
cellWalk([hyps, concl, uses.map(us => substC(laws[us.node].holds, { ...now, vars: { ...now.vars, ...us.subst } }))]);
|
|
1096
|
+
const rank = lawNodes[0]?.rank.length ?? 2;
|
|
1097
|
+
const spec = { name, hyps, concl, rank: [t, ...Array.from({ length: rank - 1 }, () => k(2))], insts: insts.filter(p => p.fact >= 0), uses };
|
|
1098
|
+
return sliced(withReads(facts, [spec], [...lawNodes, spec]))[0];
|
|
1099
|
+
}
|
|
1100
|
+
// --- flags that hold at every key ------------------------------------------------------------------------------------
|
|
1101
|
+
// A machine with flags that hold at every key, read as its sk machine (BridgeRules.lean's SMachine.sk): each such flag
|
|
1102
|
+
// ?f and its witnesses inputs a request names, a read of the flag `?f = 1`; the closing conditions facts of the run
|
|
1103
|
+
// (`qflagFacts`). Untrusted: BridgeRules.lean's cycQMOk checks the facts against the machine.
|
|
1104
|
+
const quantifiedOf = (m) => Object.entries(m.step.flags ?? {}).filter((e) => e[1].op === 'all');
|
|
1105
|
+
export const hasQuantified = (m) => quantifiedOf(m).length > 0;
|
|
1106
|
+
export function skMachine(m) {
|
|
1107
|
+
const q = quantifiedOf(m);
|
|
1108
|
+
if (!q.length)
|
|
1109
|
+
return m;
|
|
1110
|
+
const flags = Object.fromEntries(Object.entries(m.step.flags ?? {}).map(([f, c]) => [f, c.op === 'all' ? eq(v(`?${f}`), k(1)) : c]));
|
|
1111
|
+
return { ...m, inputs: [...m.inputs, ...q.map(([f]) => `?${f}`), ...q.flatMap(([f, c]) => witnessesOf(f, c))], step: { ...m.step, flags } };
|
|
1112
|
+
}
|
|
1113
|
+
// Each flag's closing conditions at each time u <= T (BridgeRules.lean's cycQFacts): 0 or 1; 0, or its body at every
|
|
1114
|
+
// value of its keys; 1, or its body failing at the witness.
|
|
1115
|
+
export function qflagFacts(m) {
|
|
1116
|
+
const { at } = trace(skMachine(m)), u = v('u$'), T = v('T$');
|
|
1117
|
+
const upTo = (c) => implies(le(u, T), substC(c, at(u)));
|
|
1118
|
+
return quantifiedOf(m).flatMap(([f, q]) => {
|
|
1119
|
+
const fv = v(`?${f}`), keys = boundOf(q), wits = witnessesOf(f, q);
|
|
1120
|
+
return [
|
|
1121
|
+
{ name: `${m.name}: ?${f} is 0 or 1`, keys: ['u$'], holds: upTo(le(fv, k(1))) },
|
|
1122
|
+
{ name: `${m.name}: ?${f} is 0 or its body holds`, keys: ['u$', ...keys], holds: upTo({ op: 'or', xs: [eq(fv, k(0)), q.c] }) },
|
|
1123
|
+
{ name: `${m.name}: ?${f} is 1 or its body fails at the witness`, keys: ['u$'],
|
|
1124
|
+
holds: upTo({ op: 'or', xs: [eq(fv, k(1)), { op: 'not', x: substC(q.c, vars(Object.fromEntries(keys.map((key, i) => [key, v(wits[i])])))) }] }) },
|
|
1125
|
+
];
|
|
1126
|
+
});
|
|
1127
|
+
}
|
|
1128
|
+
// The closing facts at the times a node reads the run (each flag's at its time, its body at the keys the usual proof
|
|
1129
|
+
// closes it at, read there).
|
|
1130
|
+
export function closingInsts(m, facts, insts, times, laws = []) {
|
|
1131
|
+
// (a law's fresh key k#, which the usual proof's keys may read, the node's own k)
|
|
1132
|
+
const fresh = vars(Object.fromEntries(laws.flatMap(x => (x.forall ? [[`${x.forall}#`, v(x.forall)]] : []))));
|
|
1133
|
+
const { at } = trace(skMachine(m)), fi = (n) => facts.findIndex(f => f.name === n), out = [], seen = new Set();
|
|
1134
|
+
const push = (p) => { const id = JSON.stringify(p); if (p.fact >= 0 && !seen.has(id)) {
|
|
1135
|
+
seen.add(id);
|
|
1136
|
+
out.push(p);
|
|
1137
|
+
} };
|
|
1138
|
+
for (const [f, q] of quantifiedOf(m)) {
|
|
1139
|
+
const n = boundOf(q).length;
|
|
1140
|
+
for (const u of times) {
|
|
1141
|
+
push({ fact: fi(`${m.name}: ?${f} is 0 or 1`), at: [u] });
|
|
1142
|
+
push({ fact: fi(`${m.name}: ?${f} is 1 or its body fails at the witness`), at: [u] });
|
|
1143
|
+
for (const inst of insts) {
|
|
1144
|
+
const flat = inst[`?${f}`] ?? [];
|
|
1145
|
+
for (let i = 0; i + n <= flat.length; i += n)
|
|
1146
|
+
push({ fact: fi(`${m.name}: ?${f} is 0 or its body holds`), at: [u, ...flat.slice(i, i + n).map(e => substE(substE(e, fresh), at(u)))] });
|
|
1147
|
+
}
|
|
1148
|
+
}
|
|
1149
|
+
}
|
|
1150
|
+
return out;
|
|
1151
|
+
}
|
|
1152
|
+
// The laws' nodes of a machine with such flags: its sk machine's (searched), with the closing facts, each node reading
|
|
1153
|
+
// them where the run's step before it and its own time are.
|
|
1154
|
+
export function qflagNodes(m, laws, instances) {
|
|
1155
|
+
const base = machineNodes(skMachine(m), laws, instances);
|
|
1156
|
+
if (base.why)
|
|
1157
|
+
return base;
|
|
1158
|
+
const facts = [...base.facts, ...qflagFacts(m)], t = v('t$');
|
|
1159
|
+
const close = closingInsts(m, facts, Object.values(instances), [t, less1(t)], laws);
|
|
1160
|
+
const add = (sp) => ({ ...sp, insts: [...sp.insts, ...close], ...(sp.variants ? { variants: sp.variants.map(add) } : {}) });
|
|
1161
|
+
return { facts, specs: base.specs.map(add) };
|
|
1162
|
+
}
|
|
679
1163
|
// --- folds over trees -----------------------------------------------------------------------------------------------
|
|
680
1164
|
// The tree as a table of nodes below its size N: a node n is a leaf (Kind[n] = 0) whose values come from its inputs,
|
|
681
1165
|
// or a node (Kind[n] = 1) with children Left[n], Right[n] numbered below n, its values from theirs and its inputs.
|
|
682
|
-
|
|
1166
|
+
// `usual`: the usual proof's trees (kernel.ts's foldObligations), translated (`translatedFold` below), or no nodes with why not.
|
|
1167
|
+
export function foldNodes(f, laws, usual, st = tstats()) {
|
|
683
1168
|
const n = v('n$'), N = v('N$'), names = Object.keys(f.leaf);
|
|
684
1169
|
const at = (i) => vars(Object.fromEntries([...names, ...f.inputs].map(x => [x, get(`${x}@`, i)])));
|
|
685
1170
|
const kind = (i) => get('Kind@', i), left = (i) => get('Left@', i), right = (i) => get('Right@', i);
|
|
@@ -713,7 +1198,42 @@ export function foldNodes(f, laws) {
|
|
|
713
1198
|
const o = shared(facts, a, fromChildren[i].concl);
|
|
714
1199
|
return o.length ? { ...a, opaque: o } : a;
|
|
715
1200
|
});
|
|
1201
|
+
if (usual)
|
|
1202
|
+
return { facts, ...translatedFold() };
|
|
716
1203
|
return { facts, specs: sliced([...atNode, ...fromChildren]) };
|
|
1204
|
+
// The usual proof translated, as a machine's is: each law three nodes. At a node, as above, its goal grown: from the
|
|
1205
|
+
// law at a leaf (Kind[n] = 0) or from its children (Kind[n] = 1). From its children, rank (n, 0): the usual node goal
|
|
1206
|
+
// over the table (an input as its cell at n, l_x and r_x as x at Left[n] and Right[n]), the laws at both children its
|
|
1207
|
+
// uses (each child below n a hypothesis, which the table at n gives where the node is used), the assumption at n
|
|
1208
|
+
// too. At a leaf, rank (n, 0): the usual leaf goal at n.
|
|
1209
|
+
function translatedFold() {
|
|
1210
|
+
const goals = new Map(foldObligations(f, laws).map(g => [g.name, g])), sigma = nodeAt(n), sigma0 = at(n), ren = renamer({ s: sigma }), ren0 = renamer({ s: sigma0 });
|
|
1211
|
+
const na = (f.pre ? 1 : 0) + (f.assume ? 1 : 0), last = (g) => g.hyps.at(-1).x;
|
|
1212
|
+
const kids = [], leaves = [], made = [];
|
|
1213
|
+
for (const [i, l] of laws.entries()) {
|
|
1214
|
+
const g0 = goals.get(`${l.name} @leaf`), g1 = goals.get(`${l.name} @node`), t0 = g0 && usual[g0.name], t1 = g1 && usual[g1.name];
|
|
1215
|
+
if (!g0 || !g1 || !t0 || !t1 || g0.hyps.length !== na + 1 || g1.hyps.length !== na + 2 * L + 1)
|
|
1216
|
+
return { specs: [], why: `${l.name}: no usual tree, or one of another shape` };
|
|
1217
|
+
const used1 = usedHyps(g1, t1);
|
|
1218
|
+
kids.push({ name: `${f.name}: ${l.name}, from its children`, hyps: [...hyps, inner, { op: '<', a: left(n), b: n }, { op: '<', a: right(n), b: n }, ...(f.assume ? [substC(f.assume, sigma0)] : [])], concl: substC(last(g1), sigma), rank: [n, k(0)], insts: [],
|
|
1219
|
+
uses: laws.flatMap((_, j) => [left, right].map(c => ({ node: j, subst: { 'n$': c(n) }, guard: yes }))).filter((_, q) => used1.has(na + q)) });
|
|
1220
|
+
leaves.push({ name: `${f.name}: ${l.name}, at a leaf`, hyps: [...hyps, eq(kind(n), k(0)), ...(f.assume ? [substC(f.assume, sigma0)] : [])], concl: substC(last(g0), sigma0), rank: [n, k(0)], insts: [], uses: [] });
|
|
1221
|
+
made.push({ g: g1, tree: t1, node: L + i, ren }, { g: g0, tree: t0, node: 2 * L + i, ren: ren0 });
|
|
1222
|
+
}
|
|
1223
|
+
const carried = atNode.map((a, i) => {
|
|
1224
|
+
const b = { ...a, variants: undefined, carry: true, uses: [...a.uses, { node: 2 * L + i, subst: {}, guard: eq(kind(n), k(0)) }] };
|
|
1225
|
+
const o = [...new Set([...shared(facts, b, kids[i].concl), ...shared(facts, b, leaves[i].concl)].map(e => JSON.stringify(e)))].map(e => JSON.parse(e));
|
|
1226
|
+
return { ...b, opaque: o.length ? o : undefined };
|
|
1227
|
+
});
|
|
1228
|
+
const all = [...carried, ...kids, ...leaves], until = performance.now() + 10_000;
|
|
1229
|
+
for (const m of made) {
|
|
1230
|
+
const tree = timedKind('translate', 0, () => withProducts(() => translate(m.g, mainGoal(facts, all, all[m.node]), m.tree, m.ren, st, until)));
|
|
1231
|
+
if (!tree)
|
|
1232
|
+
return { specs: [], why: `${all[m.node].name}: not translated` };
|
|
1233
|
+
all[m.node] = { ...all[m.node], main: tree };
|
|
1234
|
+
}
|
|
1235
|
+
return { specs: sliced(all) };
|
|
1236
|
+
}
|
|
717
1237
|
}
|
|
718
1238
|
// --- the cyclic route beside the usual one (GDP_CYCLIC=1) -------------------------------------------------------------
|
|
719
1239
|
// With GDP_CYCLIC=1, every proof the instances above cover is made again as a cyclic proof, and a verdict that differs
|
|
@@ -721,6 +1241,10 @@ export function foldNodes(f, laws) {
|
|
|
721
1241
|
const env = () => globalThis.process?.env;
|
|
722
1242
|
let quiet = 0;
|
|
723
1243
|
export const cyclicOn = () => quiet === 0 && env()?.GDP_CYCLIC === '1';
|
|
1244
|
+
// The `cyclic: {...}` lines research/cyclic/ratios.ts reads, printed only with GDP_CYCLIC=1 (the cross-check) or
|
|
1245
|
+
// GDP_CYCLIC_LOG=1 (modal/run.py sets it): a user's proofs print nothing.
|
|
1246
|
+
export const cycLog = (o) => { if (env()?.GDP_CYCLIC === '1' || env()?.GDP_CYCLIC_LOG === '1')
|
|
1247
|
+
console.error(`cyclic: ${JSON.stringify(o)}`); };
|
|
724
1248
|
// A search's many trial proofs (core/discover.ts, core/infer.ts) aren't compared: what it finds is proven again.
|
|
725
1249
|
export const quietly = (f) => { quiet++; try {
|
|
726
1250
|
return f();
|
|
@@ -728,13 +1252,130 @@ export const quietly = (f) => { quiet++; try {
|
|
|
728
1252
|
finally {
|
|
729
1253
|
quiet--;
|
|
730
1254
|
} };
|
|
731
|
-
|
|
1255
|
+
// A translated proof by what it was made from (the machine, its laws and instances; a recurrence's or fold's case): a
|
|
1256
|
+
// proof made again (a rule proven twice, as the suite does) is the one made and checked before (the same proof: the
|
|
1257
|
+
// kernel's check is a function of it, and nothing changes a proof once made). The key's time is GDP_CYCLIC_PROF's `key`.
|
|
1258
|
+
const proofsMade = new Map();
|
|
1259
|
+
// `base`: the key of a proof made before whose nodes these begin with (a machine's laws, for its step laws), reused where
|
|
1260
|
+
// its facts are these.
|
|
1261
|
+
export function translatedProof(key, build, st, base) {
|
|
1262
|
+
const k0 = timedKind('key', 0, key), had = proofsMade.get(k0);
|
|
1263
|
+
if (had) {
|
|
1264
|
+
st.cached++;
|
|
1265
|
+
return { verdict: { proven: true, checked: true, proof: had } };
|
|
1266
|
+
}
|
|
1267
|
+
const b = base && proofsMade.get(timedKind('key', 0, base)), { facts, specs, why } = build(!!b);
|
|
1268
|
+
if (why)
|
|
1269
|
+
return { why };
|
|
1270
|
+
const made = b && b.nodes.length <= specs.length && JSON.stringify(b.facts) === JSON.stringify(facts) ? b.nodes : [];
|
|
1271
|
+
if (b && !made.length)
|
|
1272
|
+
return translatedProof(key, () => build(false), st);
|
|
1273
|
+
const v = proveCyclic(facts, specs, undefined, translatedCache, st, made);
|
|
1274
|
+
if (v.proven && v.checked) {
|
|
1275
|
+
if (proofsMade.size >= 1024)
|
|
1276
|
+
proofsMade.clear();
|
|
1277
|
+
proofsMade.set(k0, v.proof);
|
|
1278
|
+
}
|
|
1279
|
+
return { verdict: v };
|
|
1280
|
+
}
|
|
1281
|
+
export const routeOf = () => env()?.GDP_CYCLIC_ROUTE ?? 'translate';
|
|
1282
|
+
const none = () => ({ proven: false, checked: false, node: '', goal: 'no instance searched', stuck: 'unknown' });
|
|
1283
|
+
export function crossCyclic(kind, name, usual, run0, usualMs, translated) {
|
|
1284
|
+
const run = run0 ?? none;
|
|
732
1285
|
for (const key of Object.keys(cyclicProf))
|
|
733
1286
|
delete cyclicProf[key];
|
|
734
|
-
const
|
|
735
|
-
|
|
1287
|
+
const route = routeOf(), st = tstats(), t0 = performance.now();
|
|
1288
|
+
let out, how = 'searched', why;
|
|
1289
|
+
if (route !== 'search' && usual && translated) {
|
|
1290
|
+
const r = translated(st);
|
|
1291
|
+
if (r.verdict?.proven && r.verdict.checked) {
|
|
1292
|
+
out = r.verdict;
|
|
1293
|
+
how = 'translated';
|
|
1294
|
+
}
|
|
1295
|
+
else {
|
|
1296
|
+
how = 'fallback';
|
|
1297
|
+
why = r.why ?? (r.verdict && !r.verdict.proven ? `${r.verdict.goal}: ${r.verdict.stuck}` : r.verdict?.why);
|
|
1298
|
+
}
|
|
1299
|
+
}
|
|
1300
|
+
out ??= run();
|
|
1301
|
+
const ms = Math.round(performance.now() - t0), cyclic = out.proven && out.checked;
|
|
736
1302
|
const prof = profOn() ? { prof: Object.fromEntries(Object.entries(cyclicProf).map(([key, p]) => [key, Object.fromEntries(Object.entries(p).map(([f, x]) => [f, Math.round(x)]))])) } : {};
|
|
737
|
-
|
|
738
|
-
if (
|
|
1303
|
+
let search;
|
|
1304
|
+
if (route === 'both' && run0) {
|
|
1305
|
+
const s0 = performance.now(), o = run0();
|
|
1306
|
+
search = { searchMs: Math.round(performance.now() - s0), searchCyclic: o.proven && o.checked };
|
|
1307
|
+
}
|
|
1308
|
+
cycLog({ kind, name, usual, cyclic, ms, ...(usualMs === undefined ? {} : { usualMs: Math.round(usualMs) }), route: how, ...(how !== 'searched' ? st : {}), ...(why ? { fellBack: why } : {}), ...search, ...prof, ...(out.proven ? { nodes: out.proof.nodes.length, ...(out.why ? { why: out.why } : {}) } : { goal: out.goal, stuck: out.stuck }) });
|
|
1309
|
+
const lax = env()?.GDP_CYCLIC_LAX === '1';
|
|
1310
|
+
if (cyclic !== usual && !lax)
|
|
739
1311
|
throw new Error(`cyclic route: ${kind} ${name} is ${cyclic ? 'proven' : 'not proven'} as a cyclic proof, ${usual ? 'proven' : 'not proven'} the usual way${out.proven ? (out.why ? ` (${out.why})` : '') : ` (${out.goal}: ${out.stuck})`}`);
|
|
1312
|
+
if (search && search.searchCyclic !== usual && !lax)
|
|
1313
|
+
throw new Error(`cyclic route: ${kind} ${name} is ${search.searchCyclic ? 'proven' : 'not proven'} as a cyclic proof searched, ${usual ? 'proven' : 'not proven'} the usual way`);
|
|
1314
|
+
}
|
|
1315
|
+
// The certificate of every proof the instances above cover is the cyclic one (GDP_CERT=usual: the usual trees, as
|
|
1316
|
+
// before 0.19.0), made from the usual proof (translated, searched where that fails) and checked by kernel/cyclic.ts; the
|
|
1317
|
+
// usual trees are then the translation's input, not checked again (kernel/prove.ts, core/dsl.ts). Each logs a line
|
|
1318
|
+
// `cyclic: {..., cert: true}` (cycLog).
|
|
1319
|
+
export const certOn = () => quiet === 0 && env()?.GDP_CERT !== 'usual';
|
|
1320
|
+
// With GDP_CERT=cyclic, what no translation covers (flags that hold at every key, k-step laws over a map) gets a searched
|
|
1321
|
+
// certificate too where GDP_CERT_SEARCH=1, each within GDP_CERT_MS (10 s by default), and keeps the usual one where none
|
|
1322
|
+
// is found then. Off by default: a search costs seconds a proof.
|
|
1323
|
+
export const certSearch = () => env()?.GDP_CERT_SEARCH === '1';
|
|
1324
|
+
export const certMs = () => Number(env()?.GDP_CERT_MS ?? 10_000);
|
|
1325
|
+
export function certCyclic(kind, name, translated, run0, usualMs) {
|
|
1326
|
+
const run = run0 ?? none;
|
|
1327
|
+
const st = tstats(), t0 = performance.now();
|
|
1328
|
+
let out, how = 'searched', why;
|
|
1329
|
+
if (translated) {
|
|
1330
|
+
const r = translated(st);
|
|
1331
|
+
if (r.verdict?.proven && r.verdict.checked) {
|
|
1332
|
+
out = r.verdict;
|
|
1333
|
+
how = 'translated';
|
|
1334
|
+
}
|
|
1335
|
+
else {
|
|
1336
|
+
how = 'fallback';
|
|
1337
|
+
why = r.why ?? (r.verdict && !r.verdict.proven ? `${r.verdict.goal}: ${r.verdict.stuck}` : r.verdict?.why);
|
|
1338
|
+
}
|
|
1339
|
+
}
|
|
1340
|
+
out ??= run();
|
|
1341
|
+
const checked = out.proven && out.checked;
|
|
1342
|
+
cycLog({ kind, name, cert: true, usual: true, cyclic: checked, ms: Math.round(performance.now() - t0), ...(usualMs === undefined ? {} : { usualMs: Math.round(usualMs) }), route: how, ...(how !== 'searched' ? st : {}), ...(why ? { fellBack: why } : {}) });
|
|
1343
|
+
return { checked, ...(out.proven ? { proof: out.proof } : {}) };
|
|
1344
|
+
}
|
|
1345
|
+
// The step laws' searched nodes after the laws' (stepLawNodes over the sk machine), with the flags' closing facts read
|
|
1346
|
+
// where each node reads the run on a machine with flags that hold at every key: what GDP_CERT=cyclic's search and the
|
|
1347
|
+
// Lean export's (lean-kernel/emit.ts) both try.
|
|
1348
|
+
export function searchedStepNodes(m, laws, steps, facts, lawNodes) {
|
|
1349
|
+
const q = hasQuantified(m);
|
|
1350
|
+
return stepLawNodes(skMachine(m), laws, steps, facts, lawNodes).map((sp, i) => {
|
|
1351
|
+
if (!q)
|
|
1352
|
+
return sp;
|
|
1353
|
+
const more = closingInsts(m, facts, [steps[i].instances], Array.from({ length: (steps[i].law.steps ?? 1) + 1 }, (_, j) => tBack(j)));
|
|
1354
|
+
const add = (x) => ({ ...x, insts: [...x.insts, ...more], ...(x.variants ? { variants: x.variants.map(add) } : {}) });
|
|
1355
|
+
return add(sp);
|
|
1356
|
+
});
|
|
1357
|
+
}
|
|
1358
|
+
// Live.lean's reqC: each input named by its term, two parts per and.
|
|
1359
|
+
export const reqTS = (req) => (req.length ? { op: 'and', xs: [{ op: '=', a: { op: 'var', name: req[0][0] }, b: req[0][1] }, reqTS(req.slice(1))] } : { op: '<=', a: { op: 'const', value: 0 }, b: { op: 'const', value: 0 } });
|
|
1360
|
+
// Witnesses for a helpful request's free inputs, for its allowed goal: all free first, then each free input fixed to 0, a
|
|
1361
|
+
// state variable or a param (a history's query, say, which its relations bound), the first combination that proves.
|
|
1362
|
+
export function withWitness(m, request, attempt) {
|
|
1363
|
+
const free = m.inputs.filter(i => !(i in request));
|
|
1364
|
+
const cands = [undefined, { op: 'const', value: 0 }, ...Object.keys(m.state).map(v => ({ op: 'var', name: v })), ...m.params.map(v => ({ op: 'var', name: v }))];
|
|
1365
|
+
const combos = [[]];
|
|
1366
|
+
if (free.length <= 2) {
|
|
1367
|
+
const go = (i, acc) => { if (i === free.length) {
|
|
1368
|
+
if (acc.length)
|
|
1369
|
+
combos.push(acc);
|
|
1370
|
+
return;
|
|
1371
|
+
} for (const c of cands)
|
|
1372
|
+
go(i + 1, c ? [...acc, [free[i], c]] : acc); };
|
|
1373
|
+
go(0, []);
|
|
1374
|
+
}
|
|
1375
|
+
for (const wit of combos) {
|
|
1376
|
+
const out = attempt(wit);
|
|
1377
|
+
if (out !== undefined)
|
|
1378
|
+
return { wit, out };
|
|
1379
|
+
}
|
|
1380
|
+
return undefined;
|
|
740
1381
|
}
|