gdp-wired 0.19.0 → 0.21.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +37 -6
- package/dist/core/cycles.d.ts +35 -2
- package/dist/core/cycles.js +458 -51
- package/dist/core/cyclic.d.ts +18 -4
- package/dist/core/cyclic.js +319 -263
- package/dist/core/data.d.ts +196 -15
- package/dist/core/data.js +1284 -213
- package/dist/core/discover.js +54 -33
- package/dist/core/dsl.d.ts +31 -3
- package/dist/core/dsl.js +168 -38
- package/dist/core/exists.d.ts +26 -0
- package/dist/core/exists.js +625 -0
- package/dist/core/infer.d.ts +13 -0
- package/dist/core/infer.js +258 -41
- package/dist/core/machine.d.ts +27 -0
- package/dist/core/machine.js +45 -0
- package/dist/core/solver.d.ts +3 -0
- package/dist/core/solver.js +5 -0
- package/dist/core/transfer.d.ts +17 -0
- package/dist/core/transfer.js +468 -0
- package/dist/core/translate.js +10 -4
- package/dist/core/watch.js +5 -1
- package/dist/kernel/prove.d.ts +8 -1
- package/dist/kernel/prove.js +106 -55
- package/dist/lean-kernel/emit.d.ts +4 -1
- package/dist/lean-kernel/emit.js +336 -69
- package/dist/lean-kernel/library.js +2 -2
- package/package.json +2 -2
package/dist/core/cyclic.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
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
|
-
import { cellProducts, grow, profile, usedHyps, widen } from "../kernel/prove.js";
|
|
3
|
+
import { cellProducts, clock, grow, profile, usedHyps, widen, withDeadline, withinDeadline } from "../kernel/prove.js";
|
|
4
4
|
import { carry, decide, readsFirst, renamer, translate, tstats } from "./translate.js";
|
|
5
5
|
const v = (name) => ({ op: 'var', name });
|
|
6
6
|
const k = (value) => ({ op: 'const', value });
|
|
@@ -83,7 +83,7 @@ function rebuild(c, keep, at = { j: 0 }) {
|
|
|
83
83
|
}
|
|
84
84
|
// The hypotheses with a part the goal reaches (whole ones included).
|
|
85
85
|
export function reached(g, whole) { return slice(g, whole, true); }
|
|
86
|
-
export function slice(g, whole, hypsOnly = false) {
|
|
86
|
+
export function slice(g, whole, hypsOnly = false, join = true) {
|
|
87
87
|
const reach = new Set(), parts = g.hyps.map(h => partsOf(h));
|
|
88
88
|
const pieces = [];
|
|
89
89
|
g.hyps.forEach((h, i) => {
|
|
@@ -118,7 +118,7 @@ export function slice(g, whole, hypsOnly = false) {
|
|
|
118
118
|
// The equations that give a cell the conclusion reads (a step's, the start's) join the conclusion, under their guards:
|
|
119
119
|
// the prover splits the conclusion's atoms first, as the usual rule does a law read after the step.
|
|
120
120
|
const last = g.hyps.length - 1, concl = whole(last) ? cellsIn(g.hyps[last]) : new Set();
|
|
121
|
-
const joins = (p) => !!p.gives && concl.has(p.gives);
|
|
121
|
+
const joins = (p) => join && !!p.gives && concl.has(p.gives);
|
|
122
122
|
if (kept.size === pieces.length && !pieces.some(joins))
|
|
123
123
|
return undefined;
|
|
124
124
|
const stays = new Set(), joined = [];
|
|
@@ -150,8 +150,6 @@ finally {
|
|
|
150
150
|
} };
|
|
151
151
|
let sliceNow = false;
|
|
152
152
|
const sliceOn = () => sliceNow && env()?.GDP_CYCLIC_SLICE !== '0';
|
|
153
|
-
const afterOn = () => env()?.GDP_CYCLIC_AFTER !== '0';
|
|
154
|
-
const stepNodesOn = () => env()?.GDP_CYCLIC_STEP !== '0';
|
|
155
153
|
const searched = new Map();
|
|
156
154
|
export const translatedCache = new Map();
|
|
157
155
|
// `order`: the order the nodes are grown in, by default those with a weak variant first (a variant that uses a weak node
|
|
@@ -169,18 +167,18 @@ export function proveCyclic(facts, specs, order0, grownBefore = searched, st = t
|
|
|
169
167
|
const sliced = sliceOn() ? slice(g, own) : undefined, part = sliced && hide(sliced);
|
|
170
168
|
if (part) {
|
|
171
169
|
// (within two seconds: a weaker goal no easier than the whole is left to the whole)
|
|
172
|
-
const t0 = performance.now(), p = timedKind(`${kind} sliced`, part.hyps.length, () => grow(part, new Map(), { nodes: 0, until:
|
|
170
|
+
const t0 = performance.now(), p = timedKind(`${kind} sliced`, part.hyps.length, () => grow(part, new Map(), { nodes: 0, until: clock() + Math.min(2_000, later ?? 2_000) }));
|
|
173
171
|
if (env()?.GDP_CYCLIC_DEBUG)
|
|
174
172
|
console.error(`[cyc] ${g.name}: ${part.hyps.length} hyps${part !== sliced ? ' (opaque)' : ''}, ${'tree' in p ? `${p.nodes} nodes` : p.stuck}, ${Math.round(performance.now() - t0)} ms`);
|
|
175
173
|
if ('tree' in p)
|
|
176
174
|
return { tree: p.tree, from: part, used: () => new Set([...usedHyps(part, p.tree)].flatMap(i => part.from[i])) };
|
|
177
175
|
if (part !== sliced) {
|
|
178
|
-
const q = timedKind(`${kind} sliced`, sliced.hyps.length, () => grow(sliced, new Map(), { nodes: 0, until:
|
|
176
|
+
const q = timedKind(`${kind} sliced`, sliced.hyps.length, () => grow(sliced, new Map(), { nodes: 0, until: clock() + Math.min(2_000, later ?? 2_000) }));
|
|
179
177
|
if ('tree' in q)
|
|
180
178
|
return { tree: q.tree, from: sliced, used: () => new Set([...usedHyps(sliced, q.tree)].flatMap(i => sliced.from[i])) };
|
|
181
179
|
}
|
|
182
180
|
}
|
|
183
|
-
const o = timedKind(kind, g.hyps.length, () => grow(g, new Map(), { nodes: 0, until:
|
|
181
|
+
const o = timedKind(kind, g.hyps.length, () => grow(g, new Map(), { nodes: 0, until: clock() + (later ?? Math.max(20_000, 1_000 * g.hyps.length)) }));
|
|
184
182
|
return 'tree' in o ? { tree: o.tree, from: g, used: () => usedHyps(g, o.tree) } : { proven: false, checked: false, node, goal: g.name, stuck: o.stuck };
|
|
185
183
|
}
|
|
186
184
|
finally {
|
|
@@ -222,6 +220,22 @@ export function proveCyclic(facts, specs, order0, grownBefore = searched, st = t
|
|
|
222
220
|
opaqueOf.set(out, names);
|
|
223
221
|
return out;
|
|
224
222
|
};
|
|
223
|
+
// A node's tree made from its goal (core/translate.ts's `carry`): first on the goal sliced (its facts' parts the
|
|
224
|
+
// conclusion and the uses reach: a step's equations for state the law doesn't read, whose choices `carry` would
|
|
225
|
+
// otherwise split at every leaf, left out), the tree then the whole goal's (`widen`); else on the whole goal.
|
|
226
|
+
const carried = (n1, first) => {
|
|
227
|
+
const g = mainGoal(facts, shapes, n1), h0 = n1.hyps.length, base = baseHyps(facts, n1).length, last = g.hyps.length - 1;
|
|
228
|
+
const part = first && sliceOn() ? slice(g, i => i < h0 || i >= base, false, false) : undefined;
|
|
229
|
+
if (part) {
|
|
230
|
+
const nf = part.from.filter(f => f[0] >= h0 && f[0] < base).length, nu = part.from.filter(f => f[0] >= base && f[0] < last).length;
|
|
231
|
+
const tree = carry(part, nf, nu, st, clock() + 2_000), wide = tree && timedKind('widen', 0, () => widen(part, g, tree));
|
|
232
|
+
if (env()?.GDP_CYCLIC_DEBUG)
|
|
233
|
+
console.error(`[carry] ${n1.name}: ${part.hyps.length} of ${g.hyps.length} hyps, ${tree ? (wide ? 'carried, widened' : 'carried, not widened') : 'not carried'}`);
|
|
234
|
+
if (wide)
|
|
235
|
+
return wide;
|
|
236
|
+
}
|
|
237
|
+
return carry(g, n1.insts.length, n1.uses.length, st, clock() + 2_000);
|
|
238
|
+
};
|
|
225
239
|
// A node's goal grown; then the node keeps only the facts and uses its tree reads (the fewer, the fewer hypotheses its
|
|
226
240
|
// goal has for the kernel to read at every leaf, and the fewer uses to prove below); then each use's goals.
|
|
227
241
|
const attempt = ({ opaque, variants: _v, weak: _w, slice: sl, main: given0, carry: made, ...n0 }) => {
|
|
@@ -229,7 +243,7 @@ export function proveCyclic(facts, specs, order0, grownBefore = searched, st = t
|
|
|
229
243
|
sliceNow = !!sl;
|
|
230
244
|
// A goal's own hypotheses: the node's, and those after its facts and uses.
|
|
231
245
|
const n1 = { ...n0, insts: n0.insts.filter(p => facts[p.fact]) };
|
|
232
|
-
const given = given0 ?? (made ? timedKind('carry', 0, () => withProducts(() =>
|
|
246
|
+
const given = given0 ?? (made ? timedKind('carry', 0, () => withProducts(() => carried(n1, made === 'sliced'))) : undefined);
|
|
233
247
|
if (made && !given0) {
|
|
234
248
|
if (given)
|
|
235
249
|
st.carried++;
|
|
@@ -251,7 +265,7 @@ export function proveCyclic(facts, specs, order0, grownBefore = searched, st = t
|
|
|
251
265
|
const n2 = keep && { ...n1, insts: n1.insts.filter((_, i) => keep.has(h0 + i)), uses: n1.uses.filter((_, j) => keep.has(base + j)) };
|
|
252
266
|
if (n2 && (n2.insts.length < n1.insts.length || n2.uses.length < n1.uses.length)) {
|
|
253
267
|
const g2 = mainGoal(facts, shapes, n2), part = hide(g2);
|
|
254
|
-
const p = timedKind('main selected', g2.hyps.length, () => withProducts(() => grow(part, new Map(), { nodes: 0, until:
|
|
268
|
+
const p = timedKind('main selected', g2.hyps.length, () => withProducts(() => grow(part, new Map(), { nodes: 0, until: clock() + Math.min(2_000, later ?? 2_000) })));
|
|
255
269
|
if (env()?.GDP_CYCLIC_DEBUG)
|
|
256
270
|
console.error(`[cyc] ${g.name}: ${g2.hyps.length} of ${g.hyps.length} hyps selected, ${'tree' in p ? `${p.nodes} nodes` : p.stuck}`);
|
|
257
271
|
const t = 'tree' in p ? own({ tree: p.tree, from: part }, g2) : undefined;
|
|
@@ -292,6 +306,8 @@ export function proveCyclic(facts, specs, order0, grownBefore = searched, st = t
|
|
|
292
306
|
const premises = [];
|
|
293
307
|
for (const [i, c] of m.hyps.entries()) {
|
|
294
308
|
const t = whole(premiseGoal(facts, n, u, c, i), n.name, mine2);
|
|
309
|
+
if ('proven' in t && env()?.GDP_CYCLIC_DEBUG)
|
|
310
|
+
console.error(`[premise] ${n.name}: use of node ${u.node}, its hypothesis ${i} (${t.stuck}): ${JSON.stringify(substC(c, vars(u.subst)))}`);
|
|
295
311
|
if ('proven' in t)
|
|
296
312
|
return t;
|
|
297
313
|
premises.push(t);
|
|
@@ -316,7 +332,9 @@ export function proveCyclic(facts, specs, order0, grownBefore = searched, st = t
|
|
|
316
332
|
for (const [j, spec] of [specs[i], ...(specs[i].variants ?? [])].entries()) {
|
|
317
333
|
// (a variant after the first within a few seconds a goal, one second once a node is refuted: a variant the first
|
|
318
334
|
// wasn't proven by is seldom needed, and seldom proves a node where another is refuted)
|
|
319
|
-
|
|
335
|
+
// (a translated node's goals, its uses' among them, within four: past that its translation isn't kept, the usual
|
|
336
|
+
// trees are)
|
|
337
|
+
later = j ? (refuted ? 1_000 : 3_000) : spec.main || spec.carry ? 4_000 : undefined;
|
|
320
338
|
if (spec.uses.some(u => weak.has(u.node)))
|
|
321
339
|
continue;
|
|
322
340
|
const r = attempt(spec);
|
|
@@ -336,13 +354,25 @@ export function proveCyclic(facts, specs, order0, grownBefore = searched, st = t
|
|
|
336
354
|
if (failed || !nodes[i])
|
|
337
355
|
return failed ?? { proven: false, checked: false, node: specs[i].name, goal: specs[i].name, stuck: 'unknown' };
|
|
338
356
|
}
|
|
357
|
+
// (GDP_CYCLIC_PROF: the nodes made before, checked again within the whole, timed apart too)
|
|
358
|
+
if (profOn() && made.length)
|
|
359
|
+
timedKind('check, of the nodes made before', 0, () => refuseCyclic({ facts, nodes: made }));
|
|
339
360
|
const proof = { facts, nodes }, why = timedKind('check', 0, () => refuseCyclic(proof));
|
|
340
361
|
return { proven: true, checked: why === undefined, proof, ...(why ? { why } : {}) };
|
|
341
362
|
}
|
|
363
|
+
// Hypotheses as their conjuncts, each a hypothesis of its own: a node used with them proves each premise apart, a goal
|
|
364
|
+
// as small as the conjunct (a premise that is a conjunction of disjunctions, refuted against itself, would be split
|
|
365
|
+
// case by case through every conjunct), and the rows its goal has are the same.
|
|
366
|
+
const conjuncts = (cs) => cs.flatMap(c => (c.op === 'and' ? conjuncts(c.xs) : [c]));
|
|
342
367
|
// An instance's nodes, and their variants, grown first on what their goals read.
|
|
343
368
|
const sliced = (specs) => specs.map(sp => ({ ...sp, slice: true, ...(sp.variants ? { variants: sp.variants.map(x => ({ ...x, slice: true })) } : {}) }));
|
|
369
|
+
// A law's keys (its key at every value, then its further keys); the tuples of terms a proof's instances name it at; the
|
|
370
|
+
// fresh key k# a usual proof proves a law about every key at, as the law's own key.
|
|
371
|
+
const keysOf = (l) => [...(l.forall ? [l.forall] : []), ...(l.keys ?? [])];
|
|
372
|
+
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)) : [[]]; };
|
|
373
|
+
const freshOf = (laws) => Object.fromEntries(laws.flatMap(x => (x.forall ? [[`${x.forall}#`, v(x.forall)]] : [])));
|
|
344
374
|
// The facts of a machine's functions (laws about every key of them), as cyclic facts.
|
|
345
|
-
const factOf = (l) => ({ name: l.name, keys:
|
|
375
|
+
const factOf = (l) => ({ name: l.name, keys: keysOf(l), holds: l.holds });
|
|
346
376
|
// `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
377
|
// translated (`translatedRecur` below), or no nodes with why not.
|
|
348
378
|
export function recurNodes(r, usual, st = tstats()) {
|
|
@@ -367,9 +397,7 @@ export function recurNodes(r, usual, st = tstats()) {
|
|
|
367
397
|
insts: [{ fact: 1, at: [less1(y)] }],
|
|
368
398
|
uses: [...r.between.map((_, j) => ({ node: n + j, subst: { y: less1(y) }, guard: le(add(x, k(1)), y) })), ...lemmaUses(x, yes), ...lemmaUses(y, yes), ...lemmaUses(less1(y), le(k(1), y))],
|
|
369
399
|
});
|
|
370
|
-
|
|
371
|
-
return { facts, specs: sliced(withReads(facts, specs)) };
|
|
372
|
-
// Each lemma and law twice, as a machine's laws are (GDP_CYCLIC_STEP=0 states the first alone): at x (rank (0, x, 1)),
|
|
400
|
+
// Each lemma and law twice, as a machine's laws are: at x (rank (0, x, 1)),
|
|
373
401
|
// from f(0) or from the lemma after a step to x, rank (0, x, 0), read at next(x − 1, f(x − 1)) (the usual rule's step
|
|
374
402
|
// goal) and using every lemma at x − 1; a law between x and y likewise at y (rank (1, y, ·)), after a step to y using
|
|
375
403
|
// the laws at y − 1 and the lemmas at x and y − 1.
|
|
@@ -490,15 +518,8 @@ export function recurNodes(r, usual, st = tstats()) {
|
|
|
490
518
|
if (o.length)
|
|
491
519
|
all[i] = { ...a, opaque: o };
|
|
492
520
|
}
|
|
493
|
-
const until =
|
|
494
|
-
|
|
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) };
|
|
521
|
+
const until = withinDeadline(clock() + 10_000);
|
|
522
|
+
return translateMade(facts, all, made, st, until);
|
|
502
523
|
}
|
|
503
524
|
}
|
|
504
525
|
// 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
|
|
@@ -536,8 +557,8 @@ function withReads(facts, specs, nodes = specs) {
|
|
|
536
557
|
});
|
|
537
558
|
}
|
|
538
559
|
// --- machines -------------------------------------------------------------------------------------------------------
|
|
539
|
-
// The machines this instance covers: no history
|
|
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
|
|
560
|
+
// The machines this instance covers: no history (its plain machine is one: kernel.ts's historyMachine), and no law that
|
|
561
|
+
// reads a map at a key 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
|
|
541
562
|
// the prover would have to prove equal through products (`machineNodes` proves those laws far slower than the usual
|
|
542
563
|
// rule, past its budget: research/cyclic/probe.ts).
|
|
543
564
|
export const cyclicMachine = (m, laws = []) => uncoveredWhy(m, laws) === undefined;
|
|
@@ -553,7 +574,7 @@ export const uncoveredWhy = (m, laws = []) => {
|
|
|
553
574
|
return;
|
|
554
575
|
} for (const val of Object.values(o))
|
|
555
576
|
walk(val, inKey); }; walk(t, false); return found; };
|
|
556
|
-
return m.history ? 'a history' :
|
|
577
|
+
return m.history ? 'a history' : laws.some(l => nested(l.holds)) ? 'a law reading a map at a key read from a map' : undefined;
|
|
557
578
|
};
|
|
558
579
|
// The run as maps over time: names at time u read as their maps there.
|
|
559
580
|
function trace(m) {
|
|
@@ -587,7 +608,11 @@ const flagsOf = (m) => {
|
|
|
587
608
|
return out;
|
|
588
609
|
};
|
|
589
610
|
// `lawsMade`: the laws' nodes are made already (their trees not translated again).
|
|
590
|
-
|
|
611
|
+
// A machine with flags that hold at every key is read as its sk machine (`skMachine`), the flags' closing conditions facts
|
|
612
|
+
// of the run (`qflagFacts`, BridgeRules.lean's cycQFacts): the usual goals' closing hypotheses (kernel.ts's `closing`)
|
|
613
|
+
// are those facts at the time before the step (`closings`).
|
|
614
|
+
export function machineNodes(given, laws, instances, usual, st = tstats()) {
|
|
615
|
+
const quant = hasQuantified(given), m = skMachine(given);
|
|
591
616
|
const { at } = trace(m), flags = flagsOf(m), inline = (t) => substC(t, { vars: {}, maps: {}, flags });
|
|
592
617
|
const t = v('t$'), T = v('T$'), u = v('u$'), key = v('k$');
|
|
593
618
|
const inlineE = (e) => substE(e, { vars: {}, maps: {}, flags });
|
|
@@ -616,62 +641,39 @@ export function machineNodes(m, laws, instances, usual, st = tstats()) {
|
|
|
616
641
|
// value a column of its own (a value read only inside a product would be none, and no two reads of it equal).
|
|
617
642
|
...state.map((s) => ({ name: `${m.name}: ${s} is a natural`, keys: ['u$'], holds: le(k(0), get(`${s}@`, u)) })),
|
|
618
643
|
{ name: `${m.name}: params are naturals`, keys: [], holds: and(yes, ...m.params.map(x => le(k(0), v(x)))) },
|
|
644
|
+
...(quant ? qflagFacts(given) : []),
|
|
619
645
|
];
|
|
620
646
|
const fi = (name) => facts.findIndex(f => f.name === name);
|
|
647
|
+
const closings = (inst, u, frame) => closingsAt(given, laws, facts, inst, u, frame);
|
|
648
|
+
// Every law at time u, at the keys the proof names it at, each key's term read by `key`, under `guard`.
|
|
649
|
+
const lawUses = (inst, u, key, guard) => laws.flatMap((l2, j) => tuples(inst, l2).map(ts => ({ node: j, subst: { 't$': u, ...Object.fromEntries(keysOf(l2).map((kn, r) => [kn, key(ts[r])])) }, guard })));
|
|
650
|
+
// The usual proof's fresh key k# of a law about every key, the node's own k; the functions' facts at the keys the
|
|
651
|
+
// proof names, over a frame; the state the step gives over the frame before it, a write's reads at its key read at the
|
|
652
|
+
// key read instead (as the map's step fact has them: the same cells there).
|
|
653
|
+
const fresh = freshOf(laws), mapOmega = mapNames.length ? [{ op: '<', a: T, b: omega }] : [];
|
|
654
|
+
const fnInsts = (inst, frame) => (m.facts ?? []).flatMap(f => tuples(inst, f).map(ts => ({ fact: fi(f.name), at: ts.map(e => substE(substE(e, vars(fresh)), frame)) })));
|
|
655
|
+
const upd = { flags: {}, vars: Object.fromEntries(state.map(st => [st, inlineE(m.step.state?.[st] ?? v(st))])), maps: Object.fromEntries(mapNames.map(map => [map, (kk) => {
|
|
656
|
+
const w = m.step.maps?.[map];
|
|
657
|
+
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) };
|
|
658
|
+
}])) };
|
|
621
659
|
// The run's facts at time t: the start and the step (Bridge.lean's cycCanon has them so).
|
|
622
660
|
const run = (when) => [{ fact: 0, at: [when] }, { fact: 1, at: [when] }];
|
|
623
|
-
const nexts = new Map(state.flatMap(s => { const e = m.step.state?.[s]; return e && !(e.op === 'var' && e.name === s)
|
|
661
|
+
const nexts = new Map(state.flatMap(s => { const e = m.step.state?.[s]; return e && !(e.op === 'var' && e.name === s) ? [[JSON.stringify(inlineE(e)), get(`${s}@`, t)]] : []; }));
|
|
624
662
|
const afterStep = (e) => {
|
|
625
663
|
const walk = (x) => (Array.isArray(x) ? x.map(walk) : !x || typeof x !== 'object' ? x
|
|
626
664
|
: nexts.get(JSON.stringify(x)) ?? Object.fromEntries(Object.entries(x).map(([key, val]) => [key, walk(val)])));
|
|
627
665
|
return nexts.size ? walk(e) : e;
|
|
628
666
|
};
|
|
629
|
-
const keysOf = (l) => [...(l.forall ? [l.forall] : []), ...(l.keys ?? [])];
|
|
630
667
|
// A map read at the node's time t at key e: that map's start, step and natural facts at (t, e).
|
|
631
|
-
const mapInsts = (c) =>
|
|
632
|
-
|
|
633
|
-
const walk = (x) => {
|
|
634
|
-
if (!x || typeof x !== 'object')
|
|
635
|
-
return;
|
|
636
|
-
const o = x;
|
|
637
|
-
if (o.op === 'get' && typeof o.map === 'string' && o.map.endsWith('@') && mapNames.includes(o.map.slice(0, -1))) {
|
|
638
|
-
const kk = o.key;
|
|
639
|
-
if (kk.op === 'add' && JSON.stringify(kk.a) === JSON.stringify(t) && kk.b.op === 'prod') {
|
|
640
|
-
const read = kk.b.a, id = JSON.stringify(read), map = o.map.slice(0, -1);
|
|
641
|
-
if (!seen.has(`${map}:${id}`)) {
|
|
642
|
-
seen.add(`${map}:${id}`);
|
|
643
|
-
out.push(...['at the start', 'at a step', '\'s cells are naturals'].map(what => ({ fact: fi(`${m.name}: ${map}${what.startsWith('\'') ? what : ` ${what}`}`), at: [t, read] })));
|
|
644
|
-
}
|
|
645
|
-
}
|
|
646
|
-
}
|
|
647
|
-
for (const val of Object.values(o))
|
|
648
|
-
walk(val);
|
|
649
|
-
};
|
|
650
|
-
walk(c);
|
|
651
|
-
return out;
|
|
652
|
-
};
|
|
653
|
-
// Each law twice (GDP_CYCLIC_STEP=0 states the first alone, its goal the whole step): at time t, rank (t, 1), and after
|
|
668
|
+
const mapInsts = (c) => cellsAt(m, facts, c);
|
|
669
|
+
// Each law twice: at time t, rank (t, 1), and after
|
|
654
670
|
// a step from t − 1, rank (t, 0), the law read at the state the step gives (the usual rule's step goal, over the run's
|
|
655
671
|
// cells at t − 1). The law at t is the start's at t = 0, and after t's step else: the step's equations carry the law
|
|
656
672
|
// after the step to the state at t. The law after the step uses every law at t − 1, at the keys the machine's proof
|
|
657
673
|
// assumes them at, with the functions' facts at the keys it names, as the usual rule's goal does.
|
|
658
674
|
function stepNodes(single) {
|
|
659
|
-
const L = laws.length, pre = at(less1(t)), one = le(k(1), t);
|
|
660
|
-
const fresh = Object.fromEntries(laws.flatMap(x => (x.forall ? [[`${x.forall}#`, v(x.forall)]] : [])));
|
|
675
|
+
const L = laws.length, pre = at(less1(t)), one = le(k(1), t), omegaHyp = mapOmega;
|
|
661
676
|
const over = (e, frame) => substE(substE(e, vars(fresh)), frame);
|
|
662
|
-
// The state the step gives, over the frame before it.
|
|
663
|
-
const upd = { flags: {}, vars: Object.fromEntries(state.map(st => [st, inlineE(m.step.state?.[st] ?? v(st))])), maps: Object.fromEntries(mapNames.map(map => [map, (kk) => {
|
|
664
|
-
const w = m.step.maps?.[map];
|
|
665
|
-
// (a write's reads at its key read at the key read instead, as the map's step fact has them: the same cells there)
|
|
666
|
-
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) };
|
|
667
|
-
}])) };
|
|
668
|
-
const omegaHyp = mapNames.length ? [{ op: '<', a: T, b: omega }] : [];
|
|
669
|
-
const fnInsts = (inst, frame) => (m.facts ?? []).flatMap(f => {
|
|
670
|
-
const ks = keysOf(f), flat = inst[f.name] ?? [], fact = fi(f.name);
|
|
671
|
-
if (!ks.length)
|
|
672
|
-
return [{ fact, at: [] }];
|
|
673
|
-
return Array.from({ length: Math.floor(flat.length / ks.length) }, (_, i) => ({ fact, at: flat.slice(i * ks.length, (i + 1) * ks.length).map(e => over(e, frame)) }));
|
|
674
|
-
});
|
|
675
677
|
const naturals = (when) => state.map(st => ({ fact: fi(`${m.name}: ${st} is a natural`), at: [when] }));
|
|
676
678
|
const params = { fact: fi(`${m.name}: params are naturals`), at: [] };
|
|
677
679
|
// The law at t first with the start's and the step's facts alone, then with the functions' facts at the keys the
|
|
@@ -687,13 +689,7 @@ export function machineNodes(m, laws, instances, usual, st = tstats()) {
|
|
|
687
689
|
});
|
|
688
690
|
const stepNodes = laws.map((l) => {
|
|
689
691
|
const inst = instances[l.name] ?? {}, concl = substC(substC(substC(l.holds, vars(fresh)), upd), pre);
|
|
690
|
-
const uses =
|
|
691
|
-
const ks = keysOf(l2);
|
|
692
|
-
if (!ks.length)
|
|
693
|
-
return [{ node: j, subst: { 't$': less1(t) }, guard: one }];
|
|
694
|
-
const flat = inst[l2.name] ?? [];
|
|
695
|
-
return Array.from({ length: Math.floor(flat.length / ks.length) }, (_, q) => ({ node: j, subst: { 't$': less1(t), ...Object.fromEntries(ks.map((kn, r) => [kn, over(flat[q * ks.length + r], pre)])) }, guard: one }));
|
|
696
|
-
});
|
|
692
|
+
const uses = lawUses(inst, less1(t), e => over(e, pre), one);
|
|
697
693
|
// (the request the step assumes a hypothesis of its own, which the law at t has from the step's fact: the step's
|
|
698
694
|
// equations are no hypothesis of this goal)
|
|
699
695
|
const hyps = [...(m.pre ? [m.pre] : []), one, le(t, T), ...omegaHyp, ...(m.assume ? [now(inline(m.assume), less1(t))] : [])], name = `${m.name}: ${l.name}, after a step`;
|
|
@@ -709,11 +705,12 @@ export function machineNodes(m, laws, instances, usual, st = tstats()) {
|
|
|
709
705
|
}
|
|
710
706
|
return [...nowNodes, ...stepNodes];
|
|
711
707
|
}
|
|
708
|
+
if (usual)
|
|
709
|
+
return { facts, ...translatedNodes() };
|
|
712
710
|
const specs = laws.map(l => {
|
|
713
711
|
const inst = instances[l.name] ?? {};
|
|
714
712
|
// Terms over the frame before the step (the machine's proof proves a law about every key at a fresh key k#: here
|
|
715
713
|
// the node's own k).
|
|
716
|
-
const fresh = Object.fromEntries(laws.flatMap(x => (x.forall ? [[`${x.forall}#`, v(x.forall)]] : [])));
|
|
717
714
|
const pre = at(less1(t)), before = (e) => substE(substE(e, vars(fresh)), pre);
|
|
718
715
|
// A function's fact also at its keys with the terms the step gives a state variable (the usual proof reads the state
|
|
719
716
|
// after the step so) as that variable's cell at t, where it then reads the function where the law at t does: at the
|
|
@@ -731,13 +728,7 @@ export function machineNodes(m, laws, instances, usual, st = tstats()) {
|
|
|
731
728
|
return JSON.stringify(a) === JSON.stringify(b) || !fnReads(read(b)).some(c => reads.has(c)) ? [a] : [a, b];
|
|
732
729
|
};
|
|
733
730
|
// The laws at the step before, at the keys the machine's proof assumes them at.
|
|
734
|
-
const uses =
|
|
735
|
-
const ks = keysOf(l2);
|
|
736
|
-
if (!ks.length)
|
|
737
|
-
return [{ node: j, subst: { 't$': less1(t) }, guard: le(k(1), t) }];
|
|
738
|
-
const flat = inst[l2.name] ?? [];
|
|
739
|
-
return Array.from({ length: Math.floor(flat.length / ks.length) }, (_, i) => ({ node: j, subst: { 't$': less1(t), ...Object.fromEntries(ks.map((kn, q) => [kn, before(flat[i * ks.length + q])])) }, guard: le(k(1), t) }));
|
|
740
|
-
});
|
|
731
|
+
const uses = lawUses(inst, less1(t), before, le(k(1), t));
|
|
741
732
|
// The functions' facts at the keys the proof names, over the frame before the step (at t = 0, the start's).
|
|
742
733
|
const factInsts = (m.facts ?? []).flatMap(f => {
|
|
743
734
|
const ks = keysOf(f), flat = inst[f.name] ?? [], fact = fi(f.name);
|
|
@@ -752,9 +743,14 @@ export function machineNodes(m, laws, instances, usual, st = tstats()) {
|
|
|
752
743
|
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,
|
|
753
744
|
};
|
|
754
745
|
});
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
746
|
+
const searchedSpecs = sliced(stepNodes(specs));
|
|
747
|
+
if (!quant)
|
|
748
|
+
return { facts, specs: searchedSpecs };
|
|
749
|
+
// (searched, with flags at every key: each node reads the closing facts where the run's step before it and its own time
|
|
750
|
+
// are)
|
|
751
|
+
const close = closingInsts(given, facts, Object.values(instances), [t, less1(t)], laws);
|
|
752
|
+
const withClose = (sp) => ({ ...sp, insts: [...sp.insts, ...close], ...(sp.variants ? { variants: sp.variants.map(withClose) } : {}) });
|
|
753
|
+
return { facts, specs: searchedSpecs.map(withClose) };
|
|
758
754
|
// The usual proof translated (core/translate.ts): each law three nodes. At t, rank (t, 1), as above (Bridge.lean's
|
|
759
755
|
// 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
756
|
// 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
|
|
@@ -763,24 +759,12 @@ export function machineNodes(m, laws, instances, usual, st = tstats()) {
|
|
|
763
759
|
// 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
760
|
// 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
761
|
function translatedNodes() {
|
|
766
|
-
const L = laws.length, pre = at(less1(t)), one = le(k(1), t), omegaHyp =
|
|
767
|
-
const fresh = Object.fromEntries(laws.flatMap(x => (x.forall ? [[`${x.forall}#`, v(x.forall)]] : [])));
|
|
762
|
+
const L = laws.length, pre = at(less1(t)), one = le(k(1), t), omegaHyp = mapOmega;
|
|
768
763
|
const sigma = { vars: { ...pre.vars, ...fresh }, maps: pre.maps, flags: {} };
|
|
769
764
|
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
|
|
771
|
-
|
|
772
|
-
|
|
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
|
-
}])) };
|
|
765
|
+
const goals = new Map(obligations(given, laws, instances).map(g => [g.name, g]));
|
|
766
|
+
// (`upd`: the usual step goal's conclusion but for a write's reads at its key, which the translation reads at the key
|
|
767
|
+
// read where the usual path has the two keys equal: two cells over time then split apart, `close`)
|
|
784
768
|
// A step law's condition with its primed names read as given, its flags inlined (over the frame before the step).
|
|
785
769
|
const primedOf = (c, s0) => substC(c, { ...s0, flags });
|
|
786
770
|
const nows = [], afters = [], starts = [], made = [];
|
|
@@ -810,24 +794,19 @@ export function machineNodes(m, laws, instances, usual, st = tstats()) {
|
|
|
810
794
|
if (!g0 || !g1 || !t0 || !t1)
|
|
811
795
|
return { specs: [], why: `${l.name}: no usual tree` };
|
|
812
796
|
const inst = instances[l.name] ?? {}, nf = assumed(m.facts ?? [], inst).length, np = m.pre ? 1 : 0, na = m.assume ? 1 : 0;
|
|
813
|
-
const uses =
|
|
814
|
-
|
|
815
|
-
|
|
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)
|
|
797
|
+
const uses = lawUses(inst, less1(t), e => substE(e, sigma), yes);
|
|
798
|
+
const fn1 = fnInsts(inst, pre), fn0 = fnInsts(inst, vars({})), cl = closings(inst, less1(t), pre), nb = nf + np + na + uses.length;
|
|
799
|
+
if (g1.hyps.length !== nb + cl.length + 1 || g0.hyps.length !== nf + np + 1 || fn1.length !== nf)
|
|
822
800
|
return { specs: [], why: `${l.name}: a usual goal of another shape` };
|
|
823
801
|
const used1 = usedHyps(g1, t1), used0 = usedHyps(g0, t0), last = (g) => g.hyps.at(-1).x;
|
|
824
802
|
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: [] });
|
|
803
|
+
afters.push({ name: `${m.name}: ${l.name}, after a step`, hyps: conjuncts([...(m.pre ? [m.pre] : []), one, le(t, T), ...omegaHyp, ...(m.assume ? [now(inline(m.assume), less1(t))] : [])]),
|
|
804
|
+
concl: substC(substC(substC(l.holds, vars(fresh)), upd), pre), rank: [t, k(0)], insts: [...fn1.filter((_, q) => used1.has(q)), ...cl.filter((_, j) => used1.has(nb + j))], uses: uses.filter((_, j) => used1.has(nf + np + na + j)) });
|
|
805
|
+
starts.push({ name: `${m.name}: ${l.name}, at the start`, hyps: conjuncts(m.pre ? [m.pre] : []), concl: substC(last(g0), vars(fresh)), rank: [k(0), k(0)], insts: [...fn0.filter((_, q) => used0.has(q))], uses: [] });
|
|
828
806
|
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
807
|
uses: [{ node: L + i, subst: {}, guard: one }, { node: 2 * L + i, subst: {}, guard: le(t, k(0)) }], carry: true });
|
|
830
|
-
|
|
808
|
+
// (the closing facts' time t − 1 <= T decided too: their rows are then the closing hypotheses')
|
|
809
|
+
made.push({ g: g1, tree: t1, node: L + i, ren, decide: [one, ...(cl.length ? [le(less1(t), T)] : [])], reads: readsOf(last(g1)) }, { g: g0, tree: t0, node: 2 * L + i, ren: ren0 });
|
|
831
810
|
}
|
|
832
811
|
// One-step step laws (`usual.steps`), each two nodes after the laws' (core/cyclic.ts's header has their shape): after a
|
|
833
812
|
// 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
|
|
@@ -837,54 +816,80 @@ export function machineNodes(m, laws, instances, usual, st = tstats()) {
|
|
|
837
816
|
const stepSpecs = [];
|
|
838
817
|
for (const [q, u] of (usual.steps ?? []).entries()) {
|
|
839
818
|
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:
|
|
841
|
-
|
|
842
|
-
|
|
819
|
+
// (k steps: the state after each step a cell at its time, a map read after a step the usual goal's term over the
|
|
820
|
+
// cells at t − k, the step's writes, a map a later request carries (c@j) its cells at its time)
|
|
821
|
+
const carried = m.inputMaps ?? [], overMaps = K > 1 && mapNames.length > 0;
|
|
822
|
+
// The times: over a map, one step back at a time (t − 1 − 1, as a step's fact reads the time before it: the cells it
|
|
823
|
+
// gives are the frames' own, with no key to prove equal); else t − n at once (BridgeRules.lean's cycKOk reads both).
|
|
824
|
+
// (one step back at a time, a time t − n is a term n deep and a premise's slice follows each state's cell back
|
|
825
|
+
// through every step's fact before it: the budget rule's 32 steps took 39 s against its usual proof's 1.5 s)
|
|
826
|
+
const time = overMaps ? tBack : tSub;
|
|
827
|
+
const laterMaps = Object.fromEntries(Array.from({ length: K - 1 }, (_, j) => j + 2).flatMap(j => carried.map(c => [`${c}@${j}`, { to: `${c}@`, at: time(K - j + 1) }])));
|
|
843
828
|
// 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
829
|
// (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
830
|
const frame = { vars: { ...at(time(K)).vars, ...Object.fromEntries(Array.from({ length: K }, (_, j) => j + 1).flatMap(j => [
|
|
847
831
|
...(j >= 2 ? m.inputs.map(i => [`${i}@${j}`, get(`${i}@`, time(K - j + 1))]) : []),
|
|
848
832
|
...(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 });
|
|
833
|
+
])) }, maps: K === 1 ? pre.maps : { ...at(time(K)).maps, ...Object.fromEntries(Object.entries(laterMaps).map(([n, c]) => [n, (kk) => get(c.to, pair(c.at, kk))])) }, flags: {} };
|
|
834
|
+
const sig = K === 1 ? sigma : frame, renK = K === 1 ? ren : renamer({ s: frame, cells: { ...Object.fromEntries([...mapNames, ...carried].map(map => [map, { to: `${map}@`, at: time(K) }])), ...laterMaps } });
|
|
851
835
|
// (the usual goal's hypotheses: the precondition, the facts at the instances, then each step's laws, assumption and
|
|
852
836
|
// `when`, and after each step but the last its state's equations)
|
|
853
837
|
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) =>
|
|
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
|
-
});
|
|
838
|
+
const lawsAt = (back) => lawUses(inst, time(back), e => substE(e, at(time(back))), yes);
|
|
861
839
|
const perStep = Array.from({ length: K }, (_, j) => lawsAt(K - j)), fn1 = fnInsts(inst, sig);
|
|
862
|
-
//
|
|
840
|
+
// (a flag at every key: each step's closing hypotheses the closing facts at its time)
|
|
841
|
+
// (its keys over the frame: a key may read a later step's request, a flag read at a later step's witness)
|
|
842
|
+
const cls = Array.from({ length: K }, (_, j) => closings(inst, time(K - j), K === 1 ? at(time(K - j)) : { vars: { ...frame.vars, ...at(time(K - j)).vars }, maps: { ...frame.maps, ...at(time(K - j)).maps }, flags: {} })), nc = cls[0].length;
|
|
843
|
+
// Each step's block: its laws, its assumption, its when, its closing hypotheses, then (but after the last) its
|
|
844
|
+
// state's equations.
|
|
863
845
|
const blocks = [];
|
|
864
846
|
let pos = np + nf;
|
|
865
847
|
for (let j = 0; j < K; j++) {
|
|
866
848
|
blocks.push(pos);
|
|
867
|
-
pos += nl + na + (whens[j] ? 1 : 0) + (j < K - 1 ? state.length : 0);
|
|
849
|
+
pos += nl + na + (whens[j] ? 1 : 0) + nc + (j < K - 1 ? state.length : 0);
|
|
868
850
|
}
|
|
869
851
|
if (g.hyps.length !== pos + 1 || fn1.length !== nf || perStep.some(us => us.length !== nl))
|
|
870
852
|
return { specs: [], why: `${sl.name}: a usual goal of another shape` };
|
|
871
853
|
const used = usedHyps(g, tree), last = g.hyps.at(-1).x;
|
|
872
854
|
const assumes = Array.from({ length: K }, (_, j) => (m.assume ? [now(inline(m.assume), time(K - j))] : [])).flat();
|
|
873
855
|
const when = whens.map((w, j) => now(inline(w), time(K - j)));
|
|
856
|
+
// Over a map, a later step reads it as the usual goal's terms over the cells at t − k: each later step's laws (a use
|
|
857
|
+
// at its time) must read no map so, and its assumption, `when` and closing hypotheses are the usual goal's renamed
|
|
858
|
+
// (hypotheses of the node after the steps, which the step law's facts back to t − k give).
|
|
859
|
+
const renamed = (i) => substC(g.hyps[i], frame), later0 = [];
|
|
860
|
+
if (overMaps)
|
|
861
|
+
for (let j = 1; j < K; j++) {
|
|
862
|
+
const b0 = blocks[j];
|
|
863
|
+
for (const [r, us] of perStep[j].entries())
|
|
864
|
+
if (used.has(b0 + r) && JSON.stringify(substC(nows[us.node].concl, vars(us.subst))) !== JSON.stringify(renamed(b0 + r)))
|
|
865
|
+
return { specs: [], why: `${sl.name}: a law read over a map after a step` };
|
|
866
|
+
for (let r = nl; r < nl + na + (whens[j] ? 1 : 0) + nc; r++)
|
|
867
|
+
if (used.has(b0 + r))
|
|
868
|
+
later0.push(renamed(b0 + r));
|
|
869
|
+
}
|
|
874
870
|
const bounds = [...(m.pre ? [m.pre] : []), le(k(K), t), le(t, T), ...omegaHyp];
|
|
875
871
|
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
872
|
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)));
|
|
873
|
+
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))])), ...frame.maps }, flags: {} }), at(time(K)));
|
|
878
874
|
const a = 3 * L + 2 * q, steps = Array.from({ length: K - 1 }, (_, j) => ({ fact: 1, at: [time(K - j - 1)] }));
|
|
879
875
|
// (the state after each step but the last its equations, hypotheses here as in the usual goal: the step's facts at
|
|
880
876
|
// those times give them where the step law is, `steps` below)
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
877
|
+
// (those the usual tree reads: each is a premise of this node where the step law uses it, and over a map one that
|
|
878
|
+
// reads the map after a step is proven through the map's cells back to t − k)
|
|
879
|
+
const eqs = blocks.slice(0, K - 1).flatMap((b0, j) => { const e0 = b0 + nl + na + (whens[j] ? 1 : 0) + nc; return g.hyps.slice(e0, e0 + state.length).flatMap((h, r) => (used.has(e0 + r) ? [substC(h, frame)] : [])); });
|
|
880
|
+
const closed = cls.flatMap((c, j) => (overMaps && j ? [] : c.filter((_, r) => used.has(blocks[j] + nl + na + (whens[j] ? 1 : 0) + r))));
|
|
881
|
+
const own = overMaps ? [...assumes.slice(0, na), ...when.slice(0, whens[0] ? 1 : 0), ...later0] : [...assumes, ...when];
|
|
882
|
+
stepSpecs.push({ name: `${m.name}: ${sl.name}, after ${K === 1 ? 'a step' : `${K} steps`}`, hyps: conjuncts([...bounds, ...own, ...eqs]), concl: after, rank: [t, k(0)],
|
|
883
|
+
insts: [...fn1.filter((_, r) => used.has(np + r)), ...closed], uses: perStep.flatMap((us, j) => us.filter((_, r) => used.has(blocks[j] + r))) });
|
|
884
|
+
// (over a map, each cell the step law and its `when`s read back to t − k, and the closing facts at each later time)
|
|
885
|
+
const back = [];
|
|
886
|
+
if (overMaps)
|
|
887
|
+
cellChain(m, facts, Array.from({ length: K + 1 }, (_, j) => time(K - j)), back)([cells, when, later0, instHyps(facts, [...run(t), ...steps, ...cls.slice(1).flat()])]);
|
|
884
888
|
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 });
|
|
889
|
+
insts: [...run(t), ...steps, ...(overMaps ? [...back, ...cls.slice(1).flat()] : mapInsts(cells))], uses: [{ node: a, subst: {}, guard: yes }], carry: overMaps ? 'sliced' : true });
|
|
886
890
|
// (each time decided at least the steps before it first: t ∸ j is then t − j)
|
|
887
|
-
|
|
891
|
+
// (and each closing fact's time at most T)
|
|
892
|
+
made.push({ g, tree, node: a, ren: renK, decide: [...Array.from({ length: K }, (_, j) => le(k(j + 1), t)), ...(nc ? Array.from({ length: K }, (_, j) => le(time(j + 1), T)) : [])], reads: K === 1 ? readsOf(last) : [] });
|
|
888
893
|
}
|
|
889
894
|
const all = [...nows, ...afters, ...starts, ...stepSpecs];
|
|
890
895
|
for (const [i, a] of nows.entries()) {
|
|
@@ -893,20 +898,21 @@ export function machineNodes(m, laws, instances, usual, st = tstats()) {
|
|
|
893
898
|
a.opaque = o;
|
|
894
899
|
}
|
|
895
900
|
// (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 =
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
901
|
+
const until = withinDeadline(clock() + 60_000);
|
|
902
|
+
// (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
|
|
903
|
+
// linear form)
|
|
904
|
+
return translateMade(facts, all, usual.lawsMade ? made.filter(x => x.node >= 3 * L) : made, st, until);
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
function translateMade(facts, all, made, st, until) {
|
|
908
|
+
for (const x of made) {
|
|
909
|
+
const whole = mainGoal(facts, all, all[x.node]);
|
|
910
|
+
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)));
|
|
911
|
+
if (!tree)
|
|
912
|
+
return { specs: [], why: `${all[x.node].name}: not translated` };
|
|
913
|
+
all[x.node] = { ...all[x.node], main: tree };
|
|
909
914
|
}
|
|
915
|
+
return { specs: sliced(all) };
|
|
910
916
|
}
|
|
911
917
|
// The terms (not a read, a variable or a constant) of a conclusion that a node's facts read too, largest first; none
|
|
912
918
|
// inside a product (a product of a term's linear form is no linear form).
|
|
@@ -987,8 +993,6 @@ export function stepLawNodes(m, laws, steps, facts, lawNodes, time = tBack) {
|
|
|
987
993
|
const { at } = trace(m), flags = flagsOf(m), inline = (c) => substC(c, { vars: {}, maps: {}, flags });
|
|
988
994
|
const t = v('t$'), T = v('T$'), mapNames = Object.keys(m.maps ?? {}), traced = [...mapNames, ...(m.inputMaps ?? [])], state = Object.keys(m.state);
|
|
989
995
|
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
996
|
const specs = steps.map(({ law: sl, instances: inst }) => {
|
|
993
997
|
const K = sl.steps ?? 1, times = Array.from({ length: K + 1 }, (_, j) => time(K - j));
|
|
994
998
|
const hyps = [...(m.pre ? [m.pre] : []), le(k(K), t), le(t, T), ...(traced.length ? [{ op: '<', a: T, b: omega }] : []),
|
|
@@ -1004,32 +1008,7 @@ export function stepLawNodes(m, laws, steps, facts, lawNodes, time = tBack) {
|
|
|
1004
1008
|
{ fact: fi(`${m.name}: params are naturals`), at: [] },
|
|
1005
1009
|
...(m.facts ?? []).flatMap(f => tuples(inst, f).map(ts => ({ fact: fi(f.name), at: ts.map(e => substE(e, at(times[0]))) }))),
|
|
1006
1010
|
];
|
|
1007
|
-
|
|
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
|
-
};
|
|
1011
|
+
const cellFacts = cellChain(m, facts, times, insts);
|
|
1033
1012
|
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
1013
|
for (const p of [...insts])
|
|
1035
1014
|
if (p.fact === fi(`${m.name}: a step`))
|
|
@@ -1038,6 +1017,64 @@ export function stepLawNodes(m, laws, steps, facts, lawNodes, time = tBack) {
|
|
|
1038
1017
|
});
|
|
1039
1018
|
return sliced(withReads(facts, specs, [...lawNodes, ...specs]));
|
|
1040
1019
|
}
|
|
1020
|
+
// Each map cell `x` reads at the node's time t: that map's start, its step and that its cells are naturals there.
|
|
1021
|
+
function cellsAt(m, facts, x) {
|
|
1022
|
+
const mapNames = Object.keys(m.maps ?? {}), t = JSON.stringify(v('t$')), fi = (name) => facts.findIndex(f => f.name === name);
|
|
1023
|
+
const out = [], seen = new Set();
|
|
1024
|
+
const walk = (y) => {
|
|
1025
|
+
if (!y || typeof y !== 'object')
|
|
1026
|
+
return;
|
|
1027
|
+
const o = y;
|
|
1028
|
+
if (o.op === 'get' && typeof o.map === 'string' && o.map.endsWith('@') && mapNames.includes(o.map.slice(0, -1))) {
|
|
1029
|
+
const key = o.key;
|
|
1030
|
+
if (key.op === 'add' && JSON.stringify(key.a) === t && key.b.op === 'prod') {
|
|
1031
|
+
const map = o.map.slice(0, -1), cell = key.b.a, id = `${map}:${JSON.stringify(cell)}`;
|
|
1032
|
+
if (!seen.has(id)) {
|
|
1033
|
+
seen.add(id);
|
|
1034
|
+
out.push(...['at the start', 'at a step', '\'s cells are naturals'].map(what => ({ fact: fi(`${m.name}: ${map}${what.startsWith('\'') ? what : ` ${what}`}`), at: [key.a, cell] })));
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
1038
|
+
for (const val of Object.values(o))
|
|
1039
|
+
walk(val);
|
|
1040
|
+
};
|
|
1041
|
+
walk(x);
|
|
1042
|
+
return out;
|
|
1043
|
+
}
|
|
1044
|
+
// Each map cell a node reads at one of `times` (the first the earliest), pushed to `insts`: that map's step there (at a
|
|
1045
|
+
// time after the first) and that its cells are naturals, then the same of the cells those facts read, back to the first
|
|
1046
|
+
// time: the cells a step gives read back to the run's cells where the node starts.
|
|
1047
|
+
function cellChain(m, facts, times, insts) {
|
|
1048
|
+
const mapNames = Object.keys(m.maps ?? {}), traced = [...mapNames, ...(m.inputMaps ?? [])], fi = (name) => facts.findIndex(f => f.name === name);
|
|
1049
|
+
// (a time read either way, t − n or one step back at a time: a step's fact at t − 1 reads t − 1 − 1)
|
|
1050
|
+
const norm = (u) => (u.op === 'sub' && u.b.op === 'const' && u.a.op === 'sub' && u.a.b.op === 'const' ? norm({ op: 'sub', a: u.a.a, b: k(u.a.b.value + u.b.value) }) : u);
|
|
1051
|
+
const seen = new Set(), ix = new Map(times.map((u, j) => [JSON.stringify(norm(u)), j]));
|
|
1052
|
+
const cellFacts = (x) => {
|
|
1053
|
+
if (!x || typeof x !== 'object')
|
|
1054
|
+
return;
|
|
1055
|
+
if (Array.isArray(x)) {
|
|
1056
|
+
x.forEach(cellFacts);
|
|
1057
|
+
return;
|
|
1058
|
+
}
|
|
1059
|
+
const o = x;
|
|
1060
|
+
if (o.op === 'get' && typeof o.map === 'string' && o.map.endsWith('@') && traced.includes(o.map.slice(0, -1))) {
|
|
1061
|
+
const key = o.key;
|
|
1062
|
+
if (key.op === 'add' && key.b.op === 'prod' && JSON.stringify(key.b.b) === JSON.stringify(omega)) {
|
|
1063
|
+
const j = ix.get(JSON.stringify(norm(key.a))), map = o.map.slice(0, -1), id = `${map}:${JSON.stringify(key)}`, u = key.a, cell = key.b.a;
|
|
1064
|
+
if (j !== undefined && !seen.has(id) && mapNames.includes(map)) {
|
|
1065
|
+
seen.add(id);
|
|
1066
|
+
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] }));
|
|
1067
|
+
insts.push(...more);
|
|
1068
|
+
for (const p of more)
|
|
1069
|
+
cellFacts(substC(facts[p.fact].holds, vars({ 'u$': p.at[0], 'k$': p.at[1] })));
|
|
1070
|
+
}
|
|
1071
|
+
}
|
|
1072
|
+
}
|
|
1073
|
+
for (const val of Object.values(o))
|
|
1074
|
+
cellFacts(val);
|
|
1075
|
+
};
|
|
1076
|
+
return cellFacts;
|
|
1077
|
+
}
|
|
1041
1078
|
// A request allowed (BridgeRules.lean's cycAOk, cyc_allow_sound carries it to the claim): from every reachable state,
|
|
1042
1079
|
// every request meeting `w` (a helpful request's region and terms), carrying empty sets, is one the machine allows. A node
|
|
1043
1080
|
// 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,
|
|
@@ -1047,8 +1084,7 @@ export function allowNode(m, laws, inst, w, facts, lawNodes, name, extra = []) {
|
|
|
1047
1084
|
const { at } = trace(m), flags = flagsOf(m), t = v('t$'), T = v('T$'), mapNames = Object.keys(m.maps ?? {}), carried = m.inputMaps ?? [];
|
|
1048
1085
|
const traced = [...mapNames, ...carried], state = Object.keys(m.state), now = at(t);
|
|
1049
1086
|
const fi = (n) => facts.findIndex(f => f.name === n);
|
|
1050
|
-
const
|
|
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)) : [[]]; };
|
|
1087
|
+
const tuplesOf = (l) => tuples(inst, l);
|
|
1052
1088
|
const concl = substC(substC(m.assume ?? yes, { vars: {}, maps: {}, flags }), now);
|
|
1053
1089
|
// Each carried cell the conclusion and the facts given at t read there (a flag's body at its witness, say): 0 (the
|
|
1054
1090
|
// request carries empty sets).
|
|
@@ -1069,34 +1105,66 @@ export function allowNode(m, laws, inst, w, facts, lawNodes, name, extra = []) {
|
|
|
1069
1105
|
};
|
|
1070
1106
|
walk([concl, instHyps(facts, extra)]);
|
|
1071
1107
|
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) =>
|
|
1108
|
+
const uses = laws.flatMap((l, i) => tuplesOf(l).map(ts => ({ node: i, subst: Object.fromEntries(keysOf(l).map((kn, q) => [kn, substE(ts[q], now)])), guard: yes })));
|
|
1073
1109
|
const insts = [{ fact: fi(`${m.name}: the start`), at: [t] }, { fact: fi(`${m.name}: a step`), at: [t] },
|
|
1074
1110
|
...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 =>
|
|
1076
|
-
|
|
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 } }))]);
|
|
1111
|
+
...(m.facts ?? []).flatMap(f => tuplesOf(f).map(ts => ({ fact: fi(f.name), at: ts.map(e => substE(e, now)) }))), ...extra];
|
|
1112
|
+
insts.push(...cellsAt(m, facts, [hyps, concl, uses.map(us => substC(laws[us.node].holds, { ...now, vars: { ...now.vars, ...us.subst } }))]));
|
|
1096
1113
|
const rank = lawNodes[0]?.rank.length ?? 2;
|
|
1097
1114
|
const spec = { name, hyps, concl, rank: [t, ...Array.from({ length: rank - 1 }, () => k(2))], insts: insts.filter(p => p.fact >= 0), uses };
|
|
1098
1115
|
return sliced(withReads(facts, [spec], [...lawNodes, spec]))[0];
|
|
1099
1116
|
}
|
|
1117
|
+
// A request allowed, translated (proveProgress's goal `<name>: the request is allowed`, kernel.ts's progressObligations,
|
|
1118
|
+
// read at time t), two nodes after the laws' (`lawNodes`, rank (t, 1) the laws at t): the usual goal over the cells at
|
|
1119
|
+
// t, the request's terms as it reads them, rank (t, 2): its hypotheses the precondition, t <= T, T < Ω, `when` at t and
|
|
1120
|
+
// the closing hypotheses no closing fact gives (each a conjunct), the laws at t at the keys it names its uses, the
|
|
1121
|
+
// functions' and the closing facts its facts, its tree the usual one's; then the request allowed itself, rank (t, 3)
|
|
1122
|
+
// (BridgeRules.lean's cycAOk: `w` the region, the request's terms and its witnesses at t, its conclusion the assumption
|
|
1123
|
+
// at t), carried from the first by the request's equations.
|
|
1124
|
+
export function allowTranslated(given, laws, inst, p, usual, facts, lawNodes, name, st) {
|
|
1125
|
+
const m = skMachine(given), { at } = trace(m), t = v('t$'), T = v('T$'), now = at(t), mapNames = Object.keys(m.maps ?? {});
|
|
1126
|
+
const fi = (n) => facts.findIndex(f => f.name === n), tuplesOf = (l) => tuples(inst, l);
|
|
1127
|
+
const g = usual.goal, used = usedHyps(g, usual.tree), renamed = (c) => substC(c, now);
|
|
1128
|
+
const fns = (m.facts ?? []).flatMap(f => tuplesOf(f).map(ts => ({ fact: fi(f.name), at: ts.map(e => substE(e, now)) })));
|
|
1129
|
+
const uses = laws.flatMap((l, i) => tuplesOf(l).map(ts => ({ node: i, subst: Object.fromEntries(keysOf(l).map((kn, q) => [kn, substE(ts[q], now)])), guard: yes })));
|
|
1130
|
+
const cl = closingsAt(given, laws, facts, inst, t, now), nf = fns.length, np = m.pre ? 1 : 0;
|
|
1131
|
+
if (g.hyps.length !== nf + np + uses.length + 1 + cl.length + 1 || !m.assume)
|
|
1132
|
+
return { specs: [], why: `${name}: a usual goal of another shape` };
|
|
1133
|
+
// (a closing hypothesis the closing fact at t gives as it is, a fact; one whose body reads the request, a hypothesis)
|
|
1134
|
+
const nb = nf + np + uses.length + 1, closeFacts = [], closeHyps = [];
|
|
1135
|
+
for (const [j, q] of cl.entries()) {
|
|
1136
|
+
if (!used.has(nb + j))
|
|
1137
|
+
continue;
|
|
1138
|
+
if (JSON.stringify(instHyps(facts, [q])[0]) === JSON.stringify(renamed(g.hyps[nb + j])))
|
|
1139
|
+
closeFacts.push(q);
|
|
1140
|
+
else
|
|
1141
|
+
closeHyps.push(renamed(g.hyps[nb + j]));
|
|
1142
|
+
}
|
|
1143
|
+
const omegaHyp = mapNames.length ? [{ op: '<', a: T, b: omega }] : [], base = [...(m.pre ? [m.pre] : []), le(t, T), ...omegaHyp];
|
|
1144
|
+
const flags = flagsOf(m), terms = { name: `${name}, its terms`, hyps: conjuncts([...base, renamed(p.when), ...closeHyps]), concl: renamed(g.hyps.at(-1).x), rank: [t, k(2)],
|
|
1145
|
+
insts: [...fns.filter((_, q) => used.has(q)), ...closeFacts], uses: uses.filter((_, j) => used.has(nf + np + j)) };
|
|
1146
|
+
const w = and(p.when, reqTS(Object.entries(p.request)), reqTS([]));
|
|
1147
|
+
const allowed = { name, hyps: [...base, renamed(w)], concl: renamed(substC(m.assume, { vars: {}, maps: {}, flags })), rank: [t, k(3)],
|
|
1148
|
+
insts: closeHyps.length ? cl : [], uses: [{ node: lawNodes.length, subst: {}, guard: yes }], carry: true };
|
|
1149
|
+
const all = [...lawNodes, terms, allowed], whole = mainGoal(facts, all, terms), until = withinDeadline(clock() + 10_000);
|
|
1150
|
+
const ren = renamer({ s: now, cells: Object.fromEntries(mapNames.map(map => [map, { to: `${map}@`, at: t }])) });
|
|
1151
|
+
const tree = timedKind('translate', 0, () => withProducts(() => decide(whole, closeFacts.length ? [le(t, T)] : [], wp => translate(g, whole, usual.tree, ren, st, until, new Map(), wp), st, until)));
|
|
1152
|
+
if (!tree)
|
|
1153
|
+
return { specs: [], why: `${terms.name}: not translated` };
|
|
1154
|
+
return { specs: sliced([{ ...terms, main: tree }, allowed]) };
|
|
1155
|
+
}
|
|
1156
|
+
// A goal's closing hypotheses (kernel.ts's `closing`, in its order: each flag 0 or 1, its body at each key the proof
|
|
1157
|
+
// names, its body failing at the witness) as the closing facts at time u, the keys read over `frame` (a law's fresh key
|
|
1158
|
+
// its own k).
|
|
1159
|
+
function closingsAt(given, laws, facts, inst, u, frame) {
|
|
1160
|
+
const fi = (n) => facts.findIndex(f => f.name === n), fresh = vars(freshOf(laws));
|
|
1161
|
+
return quantifiedOf(given).flatMap(([f, qf]) => {
|
|
1162
|
+
const n = boundOf(qf).length, flat = inst[`?${f}`] ?? [];
|
|
1163
|
+
return [{ fact: fi(`${given.name}: ?${f} is 0 or 1`), at: [u] },
|
|
1164
|
+
...Array.from({ length: Math.floor(flat.length / n) }, (_, j) => ({ fact: fi(`${given.name}: ?${f} is 0 or its body holds`), at: [u, ...flat.slice(j * n, (j + 1) * n).map(e => substE(substE(e, fresh), frame))] })),
|
|
1165
|
+
{ fact: fi(`${given.name}: ?${f} is 1 or its body fails at the witness`), at: [u] }];
|
|
1166
|
+
});
|
|
1167
|
+
}
|
|
1100
1168
|
// --- flags that hold at every key ------------------------------------------------------------------------------------
|
|
1101
1169
|
// A machine with flags that hold at every key, read as its sk machine (BridgeRules.lean's SMachine.sk): each such flag
|
|
1102
1170
|
// ?f and its witnesses inputs a request names, a read of the flag `?f = 1`; the closing conditions facts of the run
|
|
@@ -1129,7 +1197,7 @@ export function qflagFacts(m) {
|
|
|
1129
1197
|
// closes it at, read there).
|
|
1130
1198
|
export function closingInsts(m, facts, insts, times, laws = []) {
|
|
1131
1199
|
// (a law's fresh key k#, which the usual proof's keys may read, the node's own k)
|
|
1132
|
-
const fresh = vars(
|
|
1200
|
+
const fresh = vars(freshOf(laws));
|
|
1133
1201
|
const { at } = trace(skMachine(m)), fi = (n) => facts.findIndex(f => f.name === n), out = [], seen = new Set();
|
|
1134
1202
|
const push = (p) => { const id = JSON.stringify(p); if (p.fact >= 0 && !seen.has(id)) {
|
|
1135
1203
|
seen.add(id);
|
|
@@ -1149,17 +1217,8 @@ export function closingInsts(m, facts, insts, times, laws = []) {
|
|
|
1149
1217
|
}
|
|
1150
1218
|
return out;
|
|
1151
1219
|
}
|
|
1152
|
-
// The laws' nodes of a machine with such flags
|
|
1153
|
-
|
|
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
|
-
}
|
|
1220
|
+
// The laws' nodes of a machine with such flags (searched): machineNodes, which reads it as its sk machine.
|
|
1221
|
+
export const qflagNodes = (m, laws, instances) => machineNodes(m, laws, instances);
|
|
1163
1222
|
// --- folds over trees -----------------------------------------------------------------------------------------------
|
|
1164
1223
|
// 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,
|
|
1165
1224
|
// or a node (Kind[n] = 1) with children Left[n], Right[n] numbered below n, its values from theirs and its inputs.
|
|
@@ -1180,9 +1239,7 @@ export function foldNodes(f, laws, usual, st = tstats()) {
|
|
|
1180
1239
|
insts: [{ fact: 0, at: [n] }, ...[n, left(n), right(n)].map(i => ({ fact: 1, at: [i] })), { fact: 2, at: [] }],
|
|
1181
1240
|
uses: laws.flatMap((_, j) => [left, right].map(c => ({ node: j, subst: { 'n$': c(n) }, guard: eq(kind(n), k(1)) }))),
|
|
1182
1241
|
}));
|
|
1183
|
-
|
|
1184
|
-
return { facts, specs: sliced(specs) };
|
|
1185
|
-
// Each law twice, as a machine's are (GDP_CYCLIC_STEP=0 states the first alone): at a node n, rank (n, 1), from the
|
|
1242
|
+
// Each law twice, as a machine's are: at a node n, rank (n, 1), from the
|
|
1186
1243
|
// table's leaf case or from the law at a node from its children, rank (n, 0), the law read at the values the node's
|
|
1187
1244
|
// case gives (the usual rule's node goal, over the table's cells at its children), using every law at both children.
|
|
1188
1245
|
const L = laws.length, hyps = [...(f.pre ? [f.pre] : []), { op: '<', a: n, b: N }], inner = eq(kind(n), k(1));
|
|
@@ -1225,14 +1282,8 @@ export function foldNodes(f, laws, usual, st = tstats()) {
|
|
|
1225
1282
|
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
1283
|
return { ...b, opaque: o.length ? o : undefined };
|
|
1227
1284
|
});
|
|
1228
|
-
const all = [...carried, ...kids, ...leaves], until =
|
|
1229
|
-
|
|
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) };
|
|
1285
|
+
const all = [...carried, ...kids, ...leaves], until = withinDeadline(clock() + 10_000);
|
|
1286
|
+
return translateMade(facts, all, made, st, until);
|
|
1236
1287
|
}
|
|
1237
1288
|
}
|
|
1238
1289
|
// --- the cyclic route beside the usual one (GDP_CYCLIC=1) -------------------------------------------------------------
|
|
@@ -1280,26 +1331,38 @@ export function translatedProof(key, build, st, base) {
|
|
|
1280
1331
|
}
|
|
1281
1332
|
export const routeOf = () => env()?.GDP_CYCLIC_ROUTE ?? 'translate';
|
|
1282
1333
|
const none = () => ({ proven: false, checked: false, node: '', goal: 'no instance searched', stuck: 'unknown' });
|
|
1334
|
+
// The cyclic proof of what the usual route proved: translated, else (`fallback`, or `searched` where nothing is
|
|
1335
|
+
// translated) searched, within `until` where it is given; with why the translation wasn't proven.
|
|
1336
|
+
// A translation within the certificate's budget (GDP_CERT_MS) or four times the usual proof's time, whichever is longer
|
|
1337
|
+
// (`translateMs`): one that will fail is cut there, not at its goals' own bounds.
|
|
1338
|
+
export const translateMs = (usualMs = 0) => Math.max(certMs(), 4 * usualMs);
|
|
1339
|
+
function cyclicOf(translated, run, st, until, usualMs) {
|
|
1340
|
+
let how = 'searched', why;
|
|
1341
|
+
if (translated) {
|
|
1342
|
+
const r = withDeadline(clock() + translateMs(usualMs), () => translated(st));
|
|
1343
|
+
if (r.verdict?.proven && r.verdict.checked)
|
|
1344
|
+
return { out: r.verdict, how: 'translated' };
|
|
1345
|
+
how = 'fallback';
|
|
1346
|
+
why = r.why ?? (r.verdict && !r.verdict.proven ? `${r.verdict.goal}: ${r.verdict.stuck}` : r.verdict?.why);
|
|
1347
|
+
}
|
|
1348
|
+
return { out: until === undefined ? run() : withDeadline(until, run), how, ...(why ? { why } : {}) };
|
|
1349
|
+
}
|
|
1350
|
+
// GDP_CYCLIC_PROF=1: where the proof's time went, by goal kind, in its line.
|
|
1351
|
+
const profLine = () => (profOn() ? { prof: Object.fromEntries(Object.entries(cyclicProf).map(([key, p]) => [key, Object.fromEntries(Object.entries(p).map(([f, x]) => [f, Math.round(x)]))])) } : {});
|
|
1283
1352
|
export function crossCyclic(kind, name, usual, run0, usualMs, translated) {
|
|
1284
1353
|
const run = run0 ?? none;
|
|
1285
1354
|
for (const key of Object.keys(cyclicProf))
|
|
1286
1355
|
delete cyclicProf[key];
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
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();
|
|
1356
|
+
// (a search within GDP_CERT_MS: one cut by it, or none to make where the translation fails, leaves the proof uncovered,
|
|
1357
|
+
// not differing)
|
|
1358
|
+
const route = routeOf(), st = tstats(), t0 = performance.now(), until = clock() + certMs();
|
|
1359
|
+
const { out, how, why } = cyclicOf(route !== 'search' && usual ? translated : undefined, run, st, until, usualMs);
|
|
1301
1360
|
const ms = Math.round(performance.now() - t0), cyclic = out.proven && out.checked;
|
|
1302
|
-
|
|
1361
|
+
if (usual && !cyclic && (!run0 || clock() > until)) {
|
|
1362
|
+
uncovered(kind, name, `no cyclic proof found${run0 ? ` within ${certMs()} ms` : ''}${why ? ` (not translated: ${why})` : ''}`);
|
|
1363
|
+
return;
|
|
1364
|
+
}
|
|
1365
|
+
const prof = profLine();
|
|
1303
1366
|
let search;
|
|
1304
1367
|
if (route === 'both' && run0) {
|
|
1305
1368
|
const s0 = performance.now(), o = run0();
|
|
@@ -1313,34 +1376,27 @@ export function crossCyclic(kind, name, usual, run0, usualMs, translated) {
|
|
|
1313
1376
|
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
1377
|
}
|
|
1315
1378
|
// 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),
|
|
1317
|
-
//
|
|
1318
|
-
//
|
|
1319
|
-
|
|
1320
|
-
//
|
|
1321
|
-
|
|
1322
|
-
|
|
1379
|
+
// before 0.19.0), translated from the usual proof and checked by kernel/cyclic.ts; the usual trees are then the
|
|
1380
|
+
// translation's input, not checked again (kernel/prove.ts, core/dsl.ts). Where no cyclic proof is made, the caller
|
|
1381
|
+
// checks the usual trees and counts the proof uncovered (`why`). With GDP_CERT_SEARCH=1 (off by default: a search costs
|
|
1382
|
+
// seconds a proof) a proof the translation doesn't cover is searched first, within GDP_CERT_MS (10 s by default). Each
|
|
1383
|
+
// logs a line `cyclic: {..., cert: true}` (cycLog).
|
|
1384
|
+
export const certMode = () => env()?.GDP_CERT !== 'usual';
|
|
1385
|
+
export const certOn = () => quiet === 0 && certMode();
|
|
1323
1386
|
export const certSearch = () => env()?.GDP_CERT_SEARCH === '1';
|
|
1324
1387
|
export const certMs = () => Number(env()?.GDP_CERT_MS ?? 10_000);
|
|
1388
|
+
// A proof of a kind no cyclic instance covers, or whose cyclic proof wasn't made, counted (`cyclic: {..., uncovered:
|
|
1389
|
+
// true, why}`): its certificate the usual trees.
|
|
1390
|
+
export const uncovered = (kind, name, why) => cycLog({ kind, name, uncovered: true, why });
|
|
1325
1391
|
export function certCyclic(kind, name, translated, run0, usualMs) {
|
|
1326
|
-
const
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
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();
|
|
1392
|
+
for (const key of Object.keys(cyclicProf))
|
|
1393
|
+
delete cyclicProf[key];
|
|
1394
|
+
const st = tstats(), t0 = performance.now(), until = clock() + certMs();
|
|
1395
|
+
const { out, how, why } = cyclicOf(translated, (translated && !certSearch() ? undefined : run0) ?? none, st, until, usualMs);
|
|
1341
1396
|
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
|
-
|
|
1397
|
+
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 } : {}), ...profLine() });
|
|
1398
|
+
const late = clock() > until ? ` within ${certMs()} ms` : '';
|
|
1399
|
+
return { checked, ...(out.proven ? { proof: out.proof } : {}), why: checked ? '' : `no cyclic proof found${late}${why ? ` (not translated: ${why})` : ''}` };
|
|
1344
1400
|
}
|
|
1345
1401
|
// The step laws' searched nodes after the laws' (stepLawNodes over the sk machine), with the flags' closing facts read
|
|
1346
1402
|
// where each node reads the run on a machine with flags that hold at every key: what GDP_CERT=cyclic's search and the
|