gdp-wired 0.18.0 → 0.20.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/cycles.d.ts +23 -2
- package/dist/core/cycles.js +451 -51
- package/dist/core/cyclic.d.ts +105 -7
- package/dist/core/cyclic.js +800 -103
- package/dist/core/data.d.ts +61 -5
- package/dist/core/data.js +487 -113
- package/dist/core/discover.js +54 -33
- package/dist/core/dsl.d.ts +4 -2
- package/dist/core/dsl.js +47 -12
- package/dist/core/solver.d.ts +3 -0
- package/dist/core/solver.js +5 -0
- package/dist/core/translate.d.ts +30 -0
- package/dist/core/translate.js +578 -0
- package/dist/kernel/prove.d.ts +16 -0
- package/dist/kernel/prove.js +160 -13
- package/dist/lean-kernel/emit.d.ts +36 -1
- package/dist/lean-kernel/emit.js +527 -85
- 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
|
-
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
|
+
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();
|
|
@@ -82,7 +83,7 @@ function rebuild(c, keep, at = { j: 0 }) {
|
|
|
82
83
|
}
|
|
83
84
|
// The hypotheses with a part the goal reaches (whole ones included).
|
|
84
85
|
export function reached(g, whole) { return slice(g, whole, true); }
|
|
85
|
-
export function slice(g, whole, hypsOnly = false) {
|
|
86
|
+
export function slice(g, whole, hypsOnly = false, join = true) {
|
|
86
87
|
const reach = new Set(), parts = g.hyps.map(h => partsOf(h));
|
|
87
88
|
const pieces = [];
|
|
88
89
|
g.hyps.forEach((h, i) => {
|
|
@@ -117,7 +118,7 @@ export function slice(g, whole, hypsOnly = false) {
|
|
|
117
118
|
// The equations that give a cell the conclusion reads (a step's, the start's) join the conclusion, under their guards:
|
|
118
119
|
// the prover splits the conclusion's atoms first, as the usual rule does a law read after the step.
|
|
119
120
|
const last = g.hyps.length - 1, concl = whole(last) ? cellsIn(g.hyps[last]) : new Set();
|
|
120
|
-
const joins = (p) => !!p.gives && concl.has(p.gives);
|
|
121
|
+
const joins = (p) => join && !!p.gives && concl.has(p.gives);
|
|
121
122
|
if (kept.size === pieces.length && !pieces.some(joins))
|
|
122
123
|
return undefined;
|
|
123
124
|
const stays = new Set(), joined = [];
|
|
@@ -141,17 +142,22 @@ 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
|
-
const
|
|
147
|
-
const
|
|
148
|
-
// Trees already grown, by their goal (a node met again: a recurrence's lemmas in its laws between, a machine proven again),
|
|
149
|
-
// with the facts and uses a node's own goal kept.
|
|
150
|
-
const grownBefore = new Map();
|
|
153
|
+
const searched = new Map();
|
|
154
|
+
export const translatedCache = new Map();
|
|
151
155
|
// `order`: the order the nodes are grown in, by default those with a weak variant first (a variant that uses a weak node
|
|
152
156
|
// is passed over).
|
|
153
|
-
|
|
154
|
-
|
|
157
|
+
// `made`: the first nodes already made (a proof of the same facts whose nodes these specs begin with), taken as they are.
|
|
158
|
+
export function proveCyclic(facts, specs, order0, grownBefore = searched, st = tstats(), made = []) {
|
|
159
|
+
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);
|
|
160
|
+
const nodes = [...made], shapes = [...specs], weak = new Set();
|
|
155
161
|
let later, refuted = false;
|
|
156
162
|
// 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
163
|
const grown = (g, node, own) => {
|
|
@@ -161,30 +167,24 @@ export function proveCyclic(facts, specs, order = [...specs.keys()].sort((i, j)
|
|
|
161
167
|
const sliced = sliceOn() ? slice(g, own) : undefined, part = sliced && hide(sliced);
|
|
162
168
|
if (part) {
|
|
163
169
|
// (within two seconds: a weaker goal no easier than the whole is left to the whole)
|
|
164
|
-
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) }));
|
|
165
171
|
if (env()?.GDP_CYCLIC_DEBUG)
|
|
166
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`);
|
|
167
173
|
if ('tree' in p)
|
|
168
174
|
return { tree: p.tree, from: part, used: () => new Set([...usedHyps(part, p.tree)].flatMap(i => part.from[i])) };
|
|
169
175
|
if (part !== sliced) {
|
|
170
|
-
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) }));
|
|
171
177
|
if ('tree' in q)
|
|
172
178
|
return { tree: q.tree, from: sliced, used: () => new Set([...usedHyps(sliced, q.tree)].flatMap(i => sliced.from[i])) };
|
|
173
179
|
}
|
|
174
180
|
}
|
|
175
|
-
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)) }));
|
|
176
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 };
|
|
177
183
|
}
|
|
178
184
|
finally {
|
|
179
185
|
cellProducts.on = was;
|
|
180
186
|
}
|
|
181
187
|
};
|
|
182
|
-
const withProducts = (f) => { const was = cellProducts.on; cellProducts.on = true; try {
|
|
183
|
-
return f();
|
|
184
|
-
}
|
|
185
|
-
finally {
|
|
186
|
-
cellProducts.on = was;
|
|
187
|
-
} };
|
|
188
188
|
// The tree made the goal's own (a weaker goal's widened), or grown on the goal itself where that fails.
|
|
189
189
|
const own = (r, g) => (r.from === g ? r.tree : timedKind('widen', 0, () => widen(r.from, g, r.tree, opaqueOf.get(r.from))));
|
|
190
190
|
const whole = (g, node, mine) => {
|
|
@@ -220,17 +220,41 @@ export function proveCyclic(facts, specs, order = [...specs.keys()].sort((i, j)
|
|
|
220
220
|
opaqueOf.set(out, names);
|
|
221
221
|
return out;
|
|
222
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
|
+
};
|
|
223
239
|
// A node's goal grown; then the node keeps only the facts and uses its tree reads (the fewer, the fewer hypotheses its
|
|
224
240
|
// 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 }) => {
|
|
241
|
+
const attempt = ({ opaque, variants: _v, weak: _w, slice: sl, main: given0, carry: made, ...n0 }) => {
|
|
226
242
|
hidden = opaque ?? [];
|
|
227
243
|
sliceNow = !!sl;
|
|
228
244
|
// A goal's own hypotheses: the node's, and those after its facts and uses.
|
|
229
245
|
const n1 = { ...n0, insts: n0.insts.filter(p => facts[p.fact]) };
|
|
246
|
+
const given = given0 ?? (made ? timedKind('carry', 0, () => withProducts(() => carried(n1, made === 'sliced'))) : undefined);
|
|
247
|
+
if (made && !given0) {
|
|
248
|
+
if (given)
|
|
249
|
+
st.carried++;
|
|
250
|
+
else
|
|
251
|
+
st.uncarried++;
|
|
252
|
+
}
|
|
230
253
|
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 (
|
|
254
|
+
const g = given ? undefined : mainGoal(facts, shapes, n1), key = g ? JSON.stringify(g.hyps) : '', before = g && grownBefore.get(key);
|
|
255
|
+
let n = n1, main = given, src;
|
|
256
|
+
if (!g) { /* the tree given */ }
|
|
257
|
+
else if (before) {
|
|
234
258
|
n = { ...n1, insts: before.insts?.map(i => n1.insts[i]) ?? n1.insts, uses: before.uses?.map(j => n1.uses[j]) ?? n1.uses };
|
|
235
259
|
main = before.tree;
|
|
236
260
|
src = { tree: main, from: mainGoal(facts, shapes, n) };
|
|
@@ -241,7 +265,7 @@ export function proveCyclic(facts, specs, order = [...specs.keys()].sort((i, j)
|
|
|
241
265
|
const n2 = keep && { ...n1, insts: n1.insts.filter((_, i) => keep.has(h0 + i)), uses: n1.uses.filter((_, j) => keep.has(base + j)) };
|
|
242
266
|
if (n2 && (n2.insts.length < n1.insts.length || n2.uses.length < n1.uses.length)) {
|
|
243
267
|
const g2 = mainGoal(facts, shapes, n2), part = hide(g2);
|
|
244
|
-
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) })));
|
|
245
269
|
if (env()?.GDP_CYCLIC_DEBUG)
|
|
246
270
|
console.error(`[cyc] ${g.name}: ${g2.hyps.length} of ${g.hyps.length} hyps selected, ${'tree' in p ? `${p.nodes} nodes` : p.stuck}`);
|
|
247
271
|
const t = 'tree' in p ? own({ tree: p.tree, from: part }, g2) : undefined;
|
|
@@ -282,6 +306,8 @@ export function proveCyclic(facts, specs, order = [...specs.keys()].sort((i, j)
|
|
|
282
306
|
const premises = [];
|
|
283
307
|
for (const [i, c] of m.hyps.entries()) {
|
|
284
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)))}`);
|
|
285
311
|
if ('proven' in t)
|
|
286
312
|
return t;
|
|
287
313
|
premises.push(t);
|
|
@@ -306,7 +332,9 @@ export function proveCyclic(facts, specs, order = [...specs.keys()].sort((i, j)
|
|
|
306
332
|
for (const [j, spec] of [specs[i], ...(specs[i].variants ?? [])].entries()) {
|
|
307
333
|
// (a variant after the first within a few seconds a goal, one second once a node is refuted: a variant the first
|
|
308
334
|
// wasn't proven by is seldom needed, and seldom proves a node where another is refuted)
|
|
309
|
-
|
|
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;
|
|
310
338
|
if (spec.uses.some(u => weak.has(u.node)))
|
|
311
339
|
continue;
|
|
312
340
|
const r = attempt(spec);
|
|
@@ -326,14 +354,28 @@ export function proveCyclic(facts, specs, order = [...specs.keys()].sort((i, j)
|
|
|
326
354
|
if (failed || !nodes[i])
|
|
327
355
|
return failed ?? { proven: false, checked: false, node: specs[i].name, goal: specs[i].name, stuck: 'unknown' };
|
|
328
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 }));
|
|
329
360
|
const proof = { facts, nodes }, why = timedKind('check', 0, () => refuseCyclic(proof));
|
|
330
361
|
return { proven: true, checked: why === undefined, proof, ...(why ? { why } : {}) };
|
|
331
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]));
|
|
332
367
|
// An instance's nodes, and their variants, grown first on what their goals read.
|
|
333
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)]] : [])));
|
|
334
374
|
// The facts of a machine's functions (laws about every key of them), as cyclic facts.
|
|
335
|
-
const factOf = (l) => ({ name: l.name, keys:
|
|
336
|
-
|
|
375
|
+
const factOf = (l) => ({ name: l.name, keys: keysOf(l), holds: l.holds });
|
|
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),
|
|
377
|
+
// translated (`translatedRecur` below), or no nodes with why not.
|
|
378
|
+
export function recurNodes(r, usual, st = tstats()) {
|
|
337
379
|
const f = `${r.name}$`, x = v('x'), y = v('y'), kk = v('k$');
|
|
338
380
|
const facts = [
|
|
339
381
|
{ name: `${r.name}(0)`, keys: [], holds: eq(get(f, k(0)), r.zero) },
|
|
@@ -355,9 +397,7 @@ export function recurNodes(r) {
|
|
|
355
397
|
insts: [{ fact: 1, at: [less1(y)] }],
|
|
356
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))],
|
|
357
399
|
});
|
|
358
|
-
|
|
359
|
-
return { facts, specs: sliced(withReads(facts, specs)) };
|
|
360
|
-
// 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)),
|
|
361
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
|
|
362
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
|
|
363
403
|
// the laws at y − 1 and the lemmas at x and y − 1.
|
|
@@ -379,6 +419,8 @@ export function recurNodes(r) {
|
|
|
379
419
|
variants: [{ ...sp, rank: [...sp.rank, k(1)] }] };
|
|
380
420
|
return a;
|
|
381
421
|
});
|
|
422
|
+
if (usual)
|
|
423
|
+
return { facts, ...translatedRecur() };
|
|
382
424
|
const all = withReads(facts, [...own, ...lemmaAfter, ...betweenAfter]);
|
|
383
425
|
for (const [i, a] of all.slice(0, n + b).entries()) {
|
|
384
426
|
const o = shared(facts, a, all[n + b + i].concl);
|
|
@@ -386,6 +428,99 @@ export function recurNodes(r) {
|
|
|
386
428
|
all[i] = { ...a, opaque: o };
|
|
387
429
|
}
|
|
388
430
|
return { facts, specs: sliced(all.map(sp => (sp.variants ? { ...sp, variants: withReads(facts, sp.variants, all) } : sp))) };
|
|
431
|
+
// The usual proofs translated, as a machine's are: each lemma and law between three nodes. At x (at y), as above, its
|
|
432
|
+
// 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):
|
|
433
|
+
// 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
|
|
434
|
+
// (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
|
|
435
|
+
// 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,
|
|
436
|
+
// fx0 as f(0) (a start that reads a function), fx as f(x).
|
|
437
|
+
function translatedRecur() {
|
|
438
|
+
const lemmaAfterT = [], betweenAfterT = [], lemmaStart = [], betweenStart = [];
|
|
439
|
+
const made = [], N = 2 * n + 2 * b;
|
|
440
|
+
const factIx = (name) => facts.findIndex((q, i) => i >= 2 && q.name === name);
|
|
441
|
+
// A usual goal's hypotheses: the machine's facts at the instances, its precondition and assumption, the laws assumed.
|
|
442
|
+
const layout = (u, i) => {
|
|
443
|
+
const g = obligations(u.m, u.laws, u.proof.instances, i), inst = u.proof.instances[u.laws[i].name] ?? {};
|
|
444
|
+
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: [] }]; });
|
|
445
|
+
const np = u.m.pre ? 1 : 0, na = u.m.assume ? 1 : 0;
|
|
446
|
+
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 };
|
|
447
|
+
};
|
|
448
|
+
const last = (g) => g.hyps.at(-1).x;
|
|
449
|
+
if (n) {
|
|
450
|
+
const u = usual.lemmas;
|
|
451
|
+
if (!u || u.laws.map(l => l.name).join('\0') !== r.lemmas.map(l => l.name).join('\0') || u.laws.some(l => l.forall))
|
|
452
|
+
return { specs: [], why: 'lemmas: no usual proof of these lemmas' };
|
|
453
|
+
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 });
|
|
454
|
+
for (const [i] of r.lemmas.entries()) {
|
|
455
|
+
const lay = layout(u, i);
|
|
456
|
+
if (!lay.ok || !lay.t0 || !lay.t1)
|
|
457
|
+
return { specs: [], why: `${r.lemmas[i].name}: a usual goal of another shape` };
|
|
458
|
+
const used1 = usedHyps(lay.g1, lay.t1), used0 = usedHyps(lay.g0, lay.t0), insts1 = [], insts0 = [];
|
|
459
|
+
for (const [q, p] of lay.fs.entries()) {
|
|
460
|
+
const zeroFact = p.fact.name.startsWith('fx0 is '), ix = zeroFact ? 0 : factIx(p.fact.name);
|
|
461
|
+
for (const [used, out, s] of [[used1, insts1, sigma], [used0, insts0, sigma0]]) {
|
|
462
|
+
if (!used.has(q))
|
|
463
|
+
continue;
|
|
464
|
+
if (ix < 0 || (zeroFact && s === sigma))
|
|
465
|
+
return { specs: [], why: `${p.fact.name}: no fact of the recurrence's` };
|
|
466
|
+
out.push({ fact: ix, at: p.at.map(e => substE(e, s)) });
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
const pre = u.m.pre ? [substC(u.m.pre, sigma)] : [];
|
|
470
|
+
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)],
|
|
471
|
+
insts: insts1, uses: lemmaUses(less1(x), yes).filter((_, j) => used1.has(lay.fs.length + lay.np + lay.na + j)) });
|
|
472
|
+
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: [] });
|
|
473
|
+
made.push({ g: lay.g1, tree: lay.t1, node: n + b + i, ren }, { g: lay.g0, tree: lay.t0, node: N + i, ren: ren0 });
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
if (b) {
|
|
477
|
+
const u = usual.between, lemmaAt = (name) => r.lemmas.findIndex(l => `${l.name} at x` === name);
|
|
478
|
+
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))
|
|
479
|
+
return { specs: [], why: 'between: no usual proof of these laws' };
|
|
480
|
+
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 });
|
|
481
|
+
for (const [i] of r.between.entries()) {
|
|
482
|
+
const lay = layout(u, n + i);
|
|
483
|
+
if (!lay.ok || !lay.t0 || !lay.t1)
|
|
484
|
+
return { specs: [], why: `${r.between[i].name}: a usual goal of another shape` };
|
|
485
|
+
const used1 = usedHyps(lay.g1, lay.t1), used0 = usedHyps(lay.g0, lay.t0);
|
|
486
|
+
const parts = (used, s, after) => {
|
|
487
|
+
const insts = [], uses = [];
|
|
488
|
+
for (const [q, p] of lay.fs.entries()) {
|
|
489
|
+
if (!used.has(q))
|
|
490
|
+
continue;
|
|
491
|
+
const j = lemmaAt(p.fact.name), ix = factIx(p.fact.name);
|
|
492
|
+
if (j >= 0)
|
|
493
|
+
uses.push({ node: j, subst: { x }, guard: yes });
|
|
494
|
+
else if (ix >= 0)
|
|
495
|
+
insts.push({ fact: ix, at: p.at.map(e => substE(e, s)) });
|
|
496
|
+
else
|
|
497
|
+
return undefined;
|
|
498
|
+
}
|
|
499
|
+
if (after)
|
|
500
|
+
for (const [j] of u.laws.entries())
|
|
501
|
+
if (used.has(lay.fs.length + lay.np + lay.na + j))
|
|
502
|
+
uses.push(j < n ? { node: j, subst: { x: less1(y) }, guard: yes } : { node: j, subst: { y: less1(y) }, guard: yes });
|
|
503
|
+
return { insts, uses };
|
|
504
|
+
};
|
|
505
|
+
const p1 = parts(used1, sigma, true), p0 = parts(used0, sigma0, false);
|
|
506
|
+
if (!p1 || !p0)
|
|
507
|
+
return { specs: [], why: `${r.between[i].name}: a fact of no recurrence's` };
|
|
508
|
+
const pre = u.m.pre ? [substC(u.m.pre, sigma)] : [];
|
|
509
|
+
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 });
|
|
510
|
+
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 });
|
|
511
|
+
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 });
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
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) }] }));
|
|
515
|
+
const rest = [...lemmaAfterT, ...betweenAfterT, ...lemmaStart, ...betweenStart], all = [...withReads(facts, carried, [...carried, ...rest]), ...rest];
|
|
516
|
+
for (const [i, a] of all.slice(0, n + b).entries()) {
|
|
517
|
+
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));
|
|
518
|
+
if (o.length)
|
|
519
|
+
all[i] = { ...a, opaque: o };
|
|
520
|
+
}
|
|
521
|
+
const until = withinDeadline(clock() + 10_000);
|
|
522
|
+
return translateMade(facts, all, made, st, until);
|
|
523
|
+
}
|
|
389
524
|
}
|
|
390
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
|
|
391
526
|
// alone) at each key f is read at, twice over (a fact's own reads give more keys).
|
|
@@ -422,11 +557,13 @@ function withReads(facts, specs, nodes = specs) {
|
|
|
422
557
|
});
|
|
423
558
|
}
|
|
424
559
|
// --- machines -------------------------------------------------------------------------------------------------------
|
|
425
|
-
// The machines this instance covers: no history
|
|
426
|
-
// 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
|
|
427
562
|
// the prover would have to prove equal through products (`machineNodes` proves those laws far slower than the usual
|
|
428
563
|
// rule, past its budget: research/cyclic/probe.ts).
|
|
429
|
-
export const cyclicMachine = (m, laws = []) =>
|
|
564
|
+
export const cyclicMachine = (m, laws = []) => uncoveredWhy(m, laws) === undefined;
|
|
565
|
+
// Why a machine's laws are no instance's (undefined where they are one).
|
|
566
|
+
export const uncoveredWhy = (m, laws = []) => {
|
|
430
567
|
const maps = Object.keys(m.maps ?? {});
|
|
431
568
|
const nested = (t) => { let found = false; const walk = (x, inKey) => { if (found || !x || typeof x !== 'object')
|
|
432
569
|
return; const o = x; if (o.op === 'get' && inKey && maps.includes(o.map)) {
|
|
@@ -437,7 +574,7 @@ export const cyclicMachine = (m, laws = []) => {
|
|
|
437
574
|
return;
|
|
438
575
|
} for (const val of Object.values(o))
|
|
439
576
|
walk(val, inKey); }; walk(t, false); return found; };
|
|
440
|
-
return
|
|
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;
|
|
441
578
|
};
|
|
442
579
|
// The run as maps over time: names at time u read as their maps there.
|
|
443
580
|
function trace(m) {
|
|
@@ -470,7 +607,12 @@ const flagsOf = (m) => {
|
|
|
470
607
|
out[f] = substC(c, { vars: {}, maps: {}, flags: out });
|
|
471
608
|
return out;
|
|
472
609
|
};
|
|
473
|
-
|
|
610
|
+
// `lawsMade`: the laws' nodes are made already (their trees not translated again).
|
|
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);
|
|
474
616
|
const { at } = trace(m), flags = flagsOf(m), inline = (t) => substC(t, { vars: {}, maps: {}, flags });
|
|
475
617
|
const t = v('t$'), T = v('T$'), u = v('u$'), key = v('k$');
|
|
476
618
|
const inlineE = (e) => substE(e, { vars: {}, maps: {}, flags });
|
|
@@ -499,62 +641,39 @@ export function machineNodes(m, laws, instances) {
|
|
|
499
641
|
// value a column of its own (a value read only inside a product would be none, and no two reads of it equal).
|
|
500
642
|
...state.map((s) => ({ name: `${m.name}: ${s} is a natural`, keys: ['u$'], holds: le(k(0), get(`${s}@`, u)) })),
|
|
501
643
|
{ name: `${m.name}: params are naturals`, keys: [], holds: and(yes, ...m.params.map(x => le(k(0), v(x)))) },
|
|
644
|
+
...(quant ? qflagFacts(given) : []),
|
|
502
645
|
];
|
|
503
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
|
+
}])) };
|
|
504
659
|
// The run's facts at time t: the start and the step (Bridge.lean's cycCanon has them so).
|
|
505
660
|
const run = (when) => [{ fact: 0, at: [when] }, { fact: 1, at: [when] }];
|
|
506
|
-
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)]] : []; }));
|
|
507
662
|
const afterStep = (e) => {
|
|
508
663
|
const walk = (x) => (Array.isArray(x) ? x.map(walk) : !x || typeof x !== 'object' ? x
|
|
509
664
|
: nexts.get(JSON.stringify(x)) ?? Object.fromEntries(Object.entries(x).map(([key, val]) => [key, walk(val)])));
|
|
510
665
|
return nexts.size ? walk(e) : e;
|
|
511
666
|
};
|
|
512
|
-
const keysOf = (l) => [...(l.forall ? [l.forall] : []), ...(l.keys ?? [])];
|
|
513
667
|
// A map read at the node's time t at key e: that map's start, step and natural facts at (t, e).
|
|
514
|
-
const mapInsts = (c) =>
|
|
515
|
-
|
|
516
|
-
const walk = (x) => {
|
|
517
|
-
if (!x || typeof x !== 'object')
|
|
518
|
-
return;
|
|
519
|
-
const o = x;
|
|
520
|
-
if (o.op === 'get' && typeof o.map === 'string' && o.map.endsWith('@') && mapNames.includes(o.map.slice(0, -1))) {
|
|
521
|
-
const kk = o.key;
|
|
522
|
-
if (kk.op === 'add' && JSON.stringify(kk.a) === JSON.stringify(t) && kk.b.op === 'prod') {
|
|
523
|
-
const read = kk.b.a, id = JSON.stringify(read), map = o.map.slice(0, -1);
|
|
524
|
-
if (!seen.has(`${map}:${id}`)) {
|
|
525
|
-
seen.add(`${map}:${id}`);
|
|
526
|
-
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] })));
|
|
527
|
-
}
|
|
528
|
-
}
|
|
529
|
-
}
|
|
530
|
-
for (const val of Object.values(o))
|
|
531
|
-
walk(val);
|
|
532
|
-
};
|
|
533
|
-
walk(c);
|
|
534
|
-
return out;
|
|
535
|
-
};
|
|
536
|
-
// 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
|
|
537
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
|
|
538
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
|
|
539
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
|
|
540
673
|
// assumes them at, with the functions' facts at the keys it names, as the usual rule's goal does.
|
|
541
674
|
function stepNodes(single) {
|
|
542
|
-
const L = laws.length, pre = at(less1(t)), one = le(k(1), t);
|
|
543
|
-
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;
|
|
544
676
|
const over = (e, frame) => substE(substE(e, vars(fresh)), frame);
|
|
545
|
-
// The state the step gives, over the frame before it.
|
|
546
|
-
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) => {
|
|
547
|
-
const w = m.step.maps?.[map];
|
|
548
|
-
// (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)
|
|
549
|
-
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) };
|
|
550
|
-
}])) };
|
|
551
|
-
const omegaHyp = mapNames.length ? [{ op: '<', a: T, b: omega }] : [];
|
|
552
|
-
const fnInsts = (inst, frame) => (m.facts ?? []).flatMap(f => {
|
|
553
|
-
const ks = keysOf(f), flat = inst[f.name] ?? [], fact = fi(f.name);
|
|
554
|
-
if (!ks.length)
|
|
555
|
-
return [{ fact, at: [] }];
|
|
556
|
-
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)) }));
|
|
557
|
-
});
|
|
558
677
|
const naturals = (when) => state.map(st => ({ fact: fi(`${m.name}: ${st} is a natural`), at: [when] }));
|
|
559
678
|
const params = { fact: fi(`${m.name}: params are naturals`), at: [] };
|
|
560
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
|
|
@@ -570,13 +689,7 @@ export function machineNodes(m, laws, instances) {
|
|
|
570
689
|
});
|
|
571
690
|
const stepNodes = laws.map((l) => {
|
|
572
691
|
const inst = instances[l.name] ?? {}, concl = substC(substC(substC(l.holds, vars(fresh)), upd), pre);
|
|
573
|
-
const uses =
|
|
574
|
-
const ks = keysOf(l2);
|
|
575
|
-
if (!ks.length)
|
|
576
|
-
return [{ node: j, subst: { 't$': less1(t) }, guard: one }];
|
|
577
|
-
const flat = inst[l2.name] ?? [];
|
|
578
|
-
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 }));
|
|
579
|
-
});
|
|
692
|
+
const uses = lawUses(inst, less1(t), e => over(e, pre), one);
|
|
580
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
|
|
581
694
|
// equations are no hypothesis of this goal)
|
|
582
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`;
|
|
@@ -592,11 +705,12 @@ export function machineNodes(m, laws, instances) {
|
|
|
592
705
|
}
|
|
593
706
|
return [...nowNodes, ...stepNodes];
|
|
594
707
|
}
|
|
708
|
+
if (usual)
|
|
709
|
+
return { facts, ...translatedNodes() };
|
|
595
710
|
const specs = laws.map(l => {
|
|
596
711
|
const inst = instances[l.name] ?? {};
|
|
597
712
|
// Terms over the frame before the step (the machine's proof proves a law about every key at a fresh key k#: here
|
|
598
713
|
// the node's own k).
|
|
599
|
-
const fresh = Object.fromEntries(laws.flatMap(x => (x.forall ? [[`${x.forall}#`, v(x.forall)]] : [])));
|
|
600
714
|
const pre = at(less1(t)), before = (e) => substE(substE(e, vars(fresh)), pre);
|
|
601
715
|
// A function's fact also at its keys with the terms the step gives a state variable (the usual proof reads the state
|
|
602
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
|
|
@@ -614,13 +728,7 @@ export function machineNodes(m, laws, instances) {
|
|
|
614
728
|
return JSON.stringify(a) === JSON.stringify(b) || !fnReads(read(b)).some(c => reads.has(c)) ? [a] : [a, b];
|
|
615
729
|
};
|
|
616
730
|
// The laws at the step before, at the keys the machine's proof assumes them at.
|
|
617
|
-
const uses =
|
|
618
|
-
const ks = keysOf(l2);
|
|
619
|
-
if (!ks.length)
|
|
620
|
-
return [{ node: j, subst: { 't$': less1(t) }, guard: le(k(1), t) }];
|
|
621
|
-
const flat = inst[l2.name] ?? [];
|
|
622
|
-
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) }));
|
|
623
|
-
});
|
|
731
|
+
const uses = lawUses(inst, less1(t), before, le(k(1), t));
|
|
624
732
|
// The functions' facts at the keys the proof names, over the frame before the step (at t = 0, the start's).
|
|
625
733
|
const factInsts = (m.facts ?? []).flatMap(f => {
|
|
626
734
|
const ks = keysOf(f), flat = inst[f.name] ?? [], fact = fi(f.name);
|
|
@@ -635,7 +743,176 @@ export function machineNodes(m, laws, instances) {
|
|
|
635
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,
|
|
636
744
|
};
|
|
637
745
|
});
|
|
638
|
-
|
|
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) };
|
|
754
|
+
// The usual proof translated (core/translate.ts): each law three nodes. At t, rank (t, 1), as above (Bridge.lean's
|
|
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
|
|
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
|
|
757
|
+
// t − 1 (each hypothesis of it read so: the functions' facts at the keys the usual proof names, the precondition, the
|
|
758
|
+
// request's assumption, every law at t − 1 at those keys, each a use guarded by 0 <= 0, its descent from 1 <= t), its
|
|
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
|
|
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.
|
|
761
|
+
function translatedNodes() {
|
|
762
|
+
const L = laws.length, pre = at(less1(t)), one = le(k(1), t), omegaHyp = mapOmega;
|
|
763
|
+
const sigma = { vars: { ...pre.vars, ...fresh }, maps: pre.maps, flags: {} };
|
|
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) });
|
|
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`)
|
|
768
|
+
// A step law's condition with its primed names read as given, its flags inlined (over the frame before the step).
|
|
769
|
+
const primedOf = (c, s0) => substC(c, { ...s0, flags });
|
|
770
|
+
const nows = [], afters = [], starts = [], made = [];
|
|
771
|
+
// A write's key and the keys the usual conclusion reads its map at (each a condition if(K = W, ...)).
|
|
772
|
+
const wkeys = new Set(mapNames.flatMap(map => { const w = m.step.maps?.[map]; return w && !('each' in w) ? [JSON.stringify(inlineE(w.key))] : []; }));
|
|
773
|
+
const readsOf = (c) => {
|
|
774
|
+
const out = [], seen = new Set();
|
|
775
|
+
const walk = (x) => {
|
|
776
|
+
if (!x || typeof x !== 'object')
|
|
777
|
+
return;
|
|
778
|
+
const o = x;
|
|
779
|
+
if (o.op === 'if' && o.c.op === '=' && wkeys.has(JSON.stringify(o.c.b))) {
|
|
780
|
+
const id = JSON.stringify([o.c.a, o.c.b]);
|
|
781
|
+
if (!seen.has(id)) {
|
|
782
|
+
seen.add(id);
|
|
783
|
+
out.push({ k: o.c.a, w: o.c.b });
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
for (const val of Object.values(o))
|
|
787
|
+
walk(val);
|
|
788
|
+
};
|
|
789
|
+
walk(c);
|
|
790
|
+
return out;
|
|
791
|
+
};
|
|
792
|
+
for (const [i, l] of laws.entries()) {
|
|
793
|
+
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];
|
|
794
|
+
if (!g0 || !g1 || !t0 || !t1)
|
|
795
|
+
return { specs: [], why: `${l.name}: no usual tree` };
|
|
796
|
+
const inst = instances[l.name] ?? {}, nf = assumed(m.facts ?? [], inst).length, np = m.pre ? 1 : 0, na = m.assume ? 1 : 0;
|
|
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)
|
|
800
|
+
return { specs: [], why: `${l.name}: a usual goal of another shape` };
|
|
801
|
+
const used1 = usedHyps(g1, t1), used0 = usedHyps(g0, t0), last = (g) => g.hyps.at(-1).x;
|
|
802
|
+
const concl = substC(l.holds, at(t));
|
|
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: [] });
|
|
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)],
|
|
807
|
+
uses: [{ node: L + i, subst: {}, guard: one }, { node: 2 * L + i, subst: {}, guard: le(t, k(0)) }], carry: true });
|
|
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 });
|
|
810
|
+
}
|
|
811
|
+
// One-step step laws (`usual.steps`), each two nodes after the laws' (core/cyclic.ts's header has their shape): after a
|
|
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
|
|
813
|
+
// assumption hypotheses, the laws at t − 1 at its keys uses), the state after it the step's terms; and the step law
|
|
814
|
+
// 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
|
|
815
|
+
// facts at t, its hypotheses the precondition, 1 <= t <= T, T < Ω and `when` at t − 1.
|
|
816
|
+
const stepSpecs = [];
|
|
817
|
+
for (const [q, u] of (usual.steps ?? []).entries()) {
|
|
818
|
+
const { sl, inst, goal: g, tree } = u, whens = sl.when === undefined ? [] : Array.isArray(sl.when) ? sl.when : [sl.when], K = sl.steps ?? 1;
|
|
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) }])));
|
|
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
|
|
829
|
+
// (n~j the state after step j, at t − k + j), its request read there (i@j).
|
|
830
|
+
const frame = { vars: { ...at(time(K)).vars, ...Object.fromEntries(Array.from({ length: K }, (_, j) => j + 1).flatMap(j => [
|
|
831
|
+
...(j >= 2 ? m.inputs.map(i => [`${i}@${j}`, get(`${i}@`, time(K - j + 1))]) : []),
|
|
832
|
+
...(j < K ? state.map(n => [`${n}~${j}`, get(`${n}@`, time(K - j))]) : []),
|
|
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 } });
|
|
835
|
+
// (the usual goal's hypotheses: the precondition, the facts at the instances, then each step's laws, assumption and
|
|
836
|
+
// `when`, and after each step but the last its state's equations)
|
|
837
|
+
const nf = assumed(m.facts ?? [], inst).length, np = m.pre ? 1 : 0, na = m.assume ? 1 : 0, nl = assumed(laws, inst).length;
|
|
838
|
+
const lawsAt = (back) => lawUses(inst, time(back), e => substE(e, at(time(back))), yes);
|
|
839
|
+
const perStep = Array.from({ length: K }, (_, j) => lawsAt(K - j)), fn1 = fnInsts(inst, sig);
|
|
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.
|
|
845
|
+
const blocks = [];
|
|
846
|
+
let pos = np + nf;
|
|
847
|
+
for (let j = 0; j < K; j++) {
|
|
848
|
+
blocks.push(pos);
|
|
849
|
+
pos += nl + na + (whens[j] ? 1 : 0) + nc + (j < K - 1 ? state.length : 0);
|
|
850
|
+
}
|
|
851
|
+
if (g.hyps.length !== pos + 1 || fn1.length !== nf || perStep.some(us => us.length !== nl))
|
|
852
|
+
return { specs: [], why: `${sl.name}: a usual goal of another shape` };
|
|
853
|
+
const used = usedHyps(g, tree), last = g.hyps.at(-1).x;
|
|
854
|
+
const assumes = Array.from({ length: K }, (_, j) => (m.assume ? [now(inline(m.assume), time(K - j))] : [])).flat();
|
|
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
|
+
}
|
|
870
|
+
const bounds = [...(m.pre ? [m.pre] : []), le(k(K), t), le(t, T), ...omegaHyp];
|
|
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);
|
|
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))])));
|
|
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)));
|
|
874
|
+
const a = 3 * L + 2 * q, steps = Array.from({ length: K - 1 }, (_, j) => ({ fact: 1, at: [time(K - j - 1)] }));
|
|
875
|
+
// (the state after each step but the last its equations, hypotheses here as in the usual goal: the step's facts at
|
|
876
|
+
// those times give them where the step law is, `steps` below)
|
|
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()])]);
|
|
888
|
+
stepSpecs.push({ name: `${m.name}: ${sl.name}`, hyps: [...bounds, ...when], concl: cells, rank: [t, k(2)],
|
|
889
|
+
insts: [...run(t), ...steps, ...(overMaps ? [...back, ...cls.slice(1).flat()] : mapInsts(cells))], uses: [{ node: a, subst: {}, guard: yes }], carry: overMaps ? 'sliced' : true });
|
|
890
|
+
// (each time decided at least the steps before it first: t ∸ j is then t − j)
|
|
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) : [] });
|
|
893
|
+
}
|
|
894
|
+
const all = [...nows, ...afters, ...starts, ...stepSpecs];
|
|
895
|
+
for (const [i, a] of nows.entries()) {
|
|
896
|
+
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));
|
|
897
|
+
if (o.length)
|
|
898
|
+
a.opaque = o;
|
|
899
|
+
}
|
|
900
|
+
// (a translation is bounded by a minute: a step law over 32 steps has a goal of 32 frames at every leaf)
|
|
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 };
|
|
914
|
+
}
|
|
915
|
+
return { specs: sliced(all) };
|
|
639
916
|
}
|
|
640
917
|
// The terms (not a read, a variable or a constant) of a conclusion that a node's facts read too, largest first; none
|
|
641
918
|
// inside a product (a product of a term's linear form is no linear form).
|
|
@@ -676,10 +953,277 @@ function shared(facts, a, concl) {
|
|
|
676
953
|
walk(concl);
|
|
677
954
|
return out.sort((x, y) => JSON.stringify(y).length - JSON.stringify(x).length);
|
|
678
955
|
}
|
|
956
|
+
// --- step laws and k-step laws over the run -----------------------------------------------------------------------
|
|
957
|
+
// 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
|
|
958
|
+
// 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,
|
|
959
|
+
// …), 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
|
|
960
|
+
// at t − k, the j-th request at t − (k − j), the state after the last at t.
|
|
961
|
+
export const tBack = (n) => (n === 0 ? v('t$') : less1(tBack(n - 1)));
|
|
962
|
+
// The same times read t − n at once (BridgeRules.lean's tSub; core/translate.ts's step laws over several steps).
|
|
963
|
+
export const tSub = (n) => (n === 0 ? v('t$') : { op: 'sub', a: v('t$'), b: k(n) });
|
|
964
|
+
// `then` over the run (BridgeRules.lean's kσ and kRd): the primed names at t, each later request's inputs (input@j) and
|
|
965
|
+
// the maps it carries (map@j) at its time, the plain names at t − k.
|
|
966
|
+
export function kStepAt(m, k, time = tBack) {
|
|
967
|
+
const { at } = trace(m), first = at(time(k)), last = at(time(0));
|
|
968
|
+
const vars = { ...first.vars }, maps = { ...first.maps };
|
|
969
|
+
for (const x of Object.keys(m.state))
|
|
970
|
+
vars[`${x}'`] = last.vars[x];
|
|
971
|
+
for (const map of Object.keys(m.maps ?? {}))
|
|
972
|
+
maps[`${map}'`] = last.maps[map];
|
|
973
|
+
for (let j = 1; j < k; j++) {
|
|
974
|
+
const a = at(time(k - j));
|
|
975
|
+
for (const i of m.inputs)
|
|
976
|
+
vars[`${i}@${j + 1}`] = a.vars[i];
|
|
977
|
+
for (const c of m.inputMaps ?? [])
|
|
978
|
+
maps[`${c}@${j + 1}`] = a.maps[c];
|
|
979
|
+
}
|
|
980
|
+
return { vars, maps, flags: {} };
|
|
981
|
+
}
|
|
982
|
+
// A k-step law's `when`s, one per step (none: 0 <= 0), its flags inlined, as Lean's SKStep has them.
|
|
983
|
+
export const kWhens = (m, sl) => {
|
|
984
|
+
const ws = sl.when === undefined ? [] : Array.isArray(sl.when) ? sl.when : [sl.when], flags = flagsOf(m);
|
|
985
|
+
return Array.from({ length: sl.steps ?? 1 }, (_, j) => (ws[j] ? substC(ws[j], { vars: {}, maps: {}, flags }) : yes));
|
|
986
|
+
};
|
|
987
|
+
// The nodes of the step laws, after the laws' (`laws` the machine's, `lawNodes` their nodes, the first L at time t, as
|
|
988
|
+
// machineNodes makes them). Each node: the precondition, k <= t <= T, T < Ω and each step's `when` at its time;
|
|
989
|
+
// 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'
|
|
990
|
+
// facts at the keys the usual proof names), every law at each state but the last (at the keys it names), and `then`.
|
|
991
|
+
export function stepLawNodes(m, laws, steps, facts, lawNodes, time = tBack) {
|
|
992
|
+
const rank = lawNodes[0]?.rank.length ?? 2;
|
|
993
|
+
const { at } = trace(m), flags = flagsOf(m), inline = (c) => substC(c, { vars: {}, maps: {}, flags });
|
|
994
|
+
const t = v('t$'), T = v('T$'), mapNames = Object.keys(m.maps ?? {}), traced = [...mapNames, ...(m.inputMaps ?? [])], state = Object.keys(m.state);
|
|
995
|
+
const fi = (name) => facts.findIndex(f => f.name === name);
|
|
996
|
+
const specs = steps.map(({ law: sl, instances: inst }) => {
|
|
997
|
+
const K = sl.steps ?? 1, times = Array.from({ length: K + 1 }, (_, j) => time(K - j));
|
|
998
|
+
const hyps = [...(m.pre ? [m.pre] : []), le(k(K), t), le(t, T), ...(traced.length ? [{ op: '<', a: T, b: omega }] : []),
|
|
999
|
+
...kWhens(m, sl).map((w, j) => substC(w, at(times[j])))];
|
|
1000
|
+
const concl = substC(inline(sl.then), kStepAt(m, K, time));
|
|
1001
|
+
// Every law at each state but the last, at the keys the proof names there.
|
|
1002
|
+
const uses = times.slice(0, K).flatMap(u => laws.flatMap((l, i) => tuples(inst, l).map(ts => ({
|
|
1003
|
+
node: i, subst: { 't$': u, ...Object.fromEntries(keysOf(l).map((kn, q) => [kn, substE(ts[q], at(u))])) }, guard: yes
|
|
1004
|
+
}))));
|
|
1005
|
+
const insts = [
|
|
1006
|
+
...times.slice(1).map(u => ({ fact: fi(`${m.name}: a step`), at: [u] })),
|
|
1007
|
+
...times.flatMap(u => state.map(st => ({ fact: fi(`${m.name}: ${st} is a natural`), at: [u] }))),
|
|
1008
|
+
{ fact: fi(`${m.name}: params are naturals`), at: [] },
|
|
1009
|
+
...(m.facts ?? []).flatMap(f => tuples(inst, f).map(ts => ({ fact: fi(f.name), at: ts.map(e => substE(e, at(times[0]))) }))),
|
|
1010
|
+
];
|
|
1011
|
+
const cellFacts = cellChain(m, facts, times, insts);
|
|
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$')) } }))]);
|
|
1013
|
+
for (const p of [...insts])
|
|
1014
|
+
if (p.fact === fi(`${m.name}: a step`))
|
|
1015
|
+
cellFacts(substC(facts[p.fact].holds, vars({ 'u$': p.at[0] })));
|
|
1016
|
+
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 };
|
|
1017
|
+
});
|
|
1018
|
+
return sliced(withReads(facts, specs, [...lawNodes, ...specs]));
|
|
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
|
+
}
|
|
1078
|
+
// A request allowed (BridgeRules.lean's cycAOk, cyc_allow_sound carries it to the claim): from every reachable state,
|
|
1079
|
+
// every request meeting `w` (a helpful request's region and terms), carrying empty sets, is one the machine allows. A node
|
|
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,
|
|
1081
|
+
// 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;
|
|
1082
|
+
// its conclusion the assumption at t, flags inlined.
|
|
1083
|
+
export function allowNode(m, laws, inst, w, facts, lawNodes, name, extra = []) {
|
|
1084
|
+
const { at } = trace(m), flags = flagsOf(m), t = v('t$'), T = v('T$'), mapNames = Object.keys(m.maps ?? {}), carried = m.inputMaps ?? [];
|
|
1085
|
+
const traced = [...mapNames, ...carried], state = Object.keys(m.state), now = at(t);
|
|
1086
|
+
const fi = (n) => facts.findIndex(f => f.name === n);
|
|
1087
|
+
const tuplesOf = (l) => tuples(inst, l);
|
|
1088
|
+
const concl = substC(substC(m.assume ?? yes, { vars: {}, maps: {}, flags }), now);
|
|
1089
|
+
// Each carried cell the conclusion and the facts given at t read there (a flag's body at its witness, say): 0 (the
|
|
1090
|
+
// request carries empty sets).
|
|
1091
|
+
const zeros = [], seen = new Set();
|
|
1092
|
+
const walk = (x) => {
|
|
1093
|
+
if (!x || typeof x !== 'object')
|
|
1094
|
+
return;
|
|
1095
|
+
const o = x;
|
|
1096
|
+
if (o.op === 'get' && typeof o.map === 'string' && o.map.endsWith('@') && carried.includes(o.map.slice(0, -1))) {
|
|
1097
|
+
const id = JSON.stringify(o);
|
|
1098
|
+
if (!seen.has(id)) {
|
|
1099
|
+
seen.add(id);
|
|
1100
|
+
zeros.push(eq(o, k(0)));
|
|
1101
|
+
}
|
|
1102
|
+
}
|
|
1103
|
+
for (const val of Object.values(o))
|
|
1104
|
+
walk(val);
|
|
1105
|
+
};
|
|
1106
|
+
walk([concl, instHyps(facts, extra)]);
|
|
1107
|
+
const hyps = [...(m.pre ? [m.pre] : []), le(t, T), ...(traced.length ? [{ op: '<', a: T, b: omega }] : []), substC(w, now), ...zeros];
|
|
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 })));
|
|
1109
|
+
const insts = [{ fact: fi(`${m.name}: the start`), at: [t] }, { fact: fi(`${m.name}: a step`), at: [t] },
|
|
1110
|
+
...state.map(st => ({ fact: fi(`${m.name}: ${st} is a natural`), at: [t] })), { fact: fi(`${m.name}: params are naturals`), at: [] },
|
|
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 } }))]));
|
|
1113
|
+
const rank = lawNodes[0]?.rank.length ?? 2;
|
|
1114
|
+
const spec = { name, hyps, concl, rank: [t, ...Array.from({ length: rank - 1 }, () => k(2))], insts: insts.filter(p => p.fact >= 0), uses };
|
|
1115
|
+
return sliced(withReads(facts, [spec], [...lawNodes, spec]))[0];
|
|
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
|
+
}
|
|
1168
|
+
// --- flags that hold at every key ------------------------------------------------------------------------------------
|
|
1169
|
+
// A machine with flags that hold at every key, read as its sk machine (BridgeRules.lean's SMachine.sk): each such flag
|
|
1170
|
+
// ?f and its witnesses inputs a request names, a read of the flag `?f = 1`; the closing conditions facts of the run
|
|
1171
|
+
// (`qflagFacts`). Untrusted: BridgeRules.lean's cycQMOk checks the facts against the machine.
|
|
1172
|
+
const quantifiedOf = (m) => Object.entries(m.step.flags ?? {}).filter((e) => e[1].op === 'all');
|
|
1173
|
+
export const hasQuantified = (m) => quantifiedOf(m).length > 0;
|
|
1174
|
+
export function skMachine(m) {
|
|
1175
|
+
const q = quantifiedOf(m);
|
|
1176
|
+
if (!q.length)
|
|
1177
|
+
return m;
|
|
1178
|
+
const flags = Object.fromEntries(Object.entries(m.step.flags ?? {}).map(([f, c]) => [f, c.op === 'all' ? eq(v(`?${f}`), k(1)) : c]));
|
|
1179
|
+
return { ...m, inputs: [...m.inputs, ...q.map(([f]) => `?${f}`), ...q.flatMap(([f, c]) => witnessesOf(f, c))], step: { ...m.step, flags } };
|
|
1180
|
+
}
|
|
1181
|
+
// Each flag's closing conditions at each time u <= T (BridgeRules.lean's cycQFacts): 0 or 1; 0, or its body at every
|
|
1182
|
+
// value of its keys; 1, or its body failing at the witness.
|
|
1183
|
+
export function qflagFacts(m) {
|
|
1184
|
+
const { at } = trace(skMachine(m)), u = v('u$'), T = v('T$');
|
|
1185
|
+
const upTo = (c) => implies(le(u, T), substC(c, at(u)));
|
|
1186
|
+
return quantifiedOf(m).flatMap(([f, q]) => {
|
|
1187
|
+
const fv = v(`?${f}`), keys = boundOf(q), wits = witnessesOf(f, q);
|
|
1188
|
+
return [
|
|
1189
|
+
{ name: `${m.name}: ?${f} is 0 or 1`, keys: ['u$'], holds: upTo(le(fv, k(1))) },
|
|
1190
|
+
{ name: `${m.name}: ?${f} is 0 or its body holds`, keys: ['u$', ...keys], holds: upTo({ op: 'or', xs: [eq(fv, k(0)), q.c] }) },
|
|
1191
|
+
{ name: `${m.name}: ?${f} is 1 or its body fails at the witness`, keys: ['u$'],
|
|
1192
|
+
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])])))) }] }) },
|
|
1193
|
+
];
|
|
1194
|
+
});
|
|
1195
|
+
}
|
|
1196
|
+
// 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
|
|
1197
|
+
// closes it at, read there).
|
|
1198
|
+
export function closingInsts(m, facts, insts, times, laws = []) {
|
|
1199
|
+
// (a law's fresh key k#, which the usual proof's keys may read, the node's own k)
|
|
1200
|
+
const fresh = vars(freshOf(laws));
|
|
1201
|
+
const { at } = trace(skMachine(m)), fi = (n) => facts.findIndex(f => f.name === n), out = [], seen = new Set();
|
|
1202
|
+
const push = (p) => { const id = JSON.stringify(p); if (p.fact >= 0 && !seen.has(id)) {
|
|
1203
|
+
seen.add(id);
|
|
1204
|
+
out.push(p);
|
|
1205
|
+
} };
|
|
1206
|
+
for (const [f, q] of quantifiedOf(m)) {
|
|
1207
|
+
const n = boundOf(q).length;
|
|
1208
|
+
for (const u of times) {
|
|
1209
|
+
push({ fact: fi(`${m.name}: ?${f} is 0 or 1`), at: [u] });
|
|
1210
|
+
push({ fact: fi(`${m.name}: ?${f} is 1 or its body fails at the witness`), at: [u] });
|
|
1211
|
+
for (const inst of insts) {
|
|
1212
|
+
const flat = inst[`?${f}`] ?? [];
|
|
1213
|
+
for (let i = 0; i + n <= flat.length; i += n)
|
|
1214
|
+
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)))] });
|
|
1215
|
+
}
|
|
1216
|
+
}
|
|
1217
|
+
}
|
|
1218
|
+
return out;
|
|
1219
|
+
}
|
|
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);
|
|
679
1222
|
// --- folds over trees -----------------------------------------------------------------------------------------------
|
|
680
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,
|
|
681
1224
|
// or a node (Kind[n] = 1) with children Left[n], Right[n] numbered below n, its values from theirs and its inputs.
|
|
682
|
-
|
|
1225
|
+
// `usual`: the usual proof's trees (kernel.ts's foldObligations), translated (`translatedFold` below), or no nodes with why not.
|
|
1226
|
+
export function foldNodes(f, laws, usual, st = tstats()) {
|
|
683
1227
|
const n = v('n$'), N = v('N$'), names = Object.keys(f.leaf);
|
|
684
1228
|
const at = (i) => vars(Object.fromEntries([...names, ...f.inputs].map(x => [x, get(`${x}@`, i)])));
|
|
685
1229
|
const kind = (i) => get('Kind@', i), left = (i) => get('Left@', i), right = (i) => get('Right@', i);
|
|
@@ -695,9 +1239,7 @@ export function foldNodes(f, laws) {
|
|
|
695
1239
|
insts: [{ fact: 0, at: [n] }, ...[n, left(n), right(n)].map(i => ({ fact: 1, at: [i] })), { fact: 2, at: [] }],
|
|
696
1240
|
uses: laws.flatMap((_, j) => [left, right].map(c => ({ node: j, subst: { 'n$': c(n) }, guard: eq(kind(n), k(1)) }))),
|
|
697
1241
|
}));
|
|
698
|
-
|
|
699
|
-
return { facts, specs: sliced(specs) };
|
|
700
|
-
// 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
|
|
701
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
|
|
702
1244
|
// case gives (the usual rule's node goal, over the table's cells at its children), using every law at both children.
|
|
703
1245
|
const L = laws.length, hyps = [...(f.pre ? [f.pre] : []), { op: '<', a: n, b: N }], inner = eq(kind(n), k(1));
|
|
@@ -713,7 +1255,36 @@ export function foldNodes(f, laws) {
|
|
|
713
1255
|
const o = shared(facts, a, fromChildren[i].concl);
|
|
714
1256
|
return o.length ? { ...a, opaque: o } : a;
|
|
715
1257
|
});
|
|
1258
|
+
if (usual)
|
|
1259
|
+
return { facts, ...translatedFold() };
|
|
716
1260
|
return { facts, specs: sliced([...atNode, ...fromChildren]) };
|
|
1261
|
+
// The usual proof translated, as a machine's is: each law three nodes. At a node, as above, its goal grown: from the
|
|
1262
|
+
// law at a leaf (Kind[n] = 0) or from its children (Kind[n] = 1). From its children, rank (n, 0): the usual node goal
|
|
1263
|
+
// 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
|
|
1264
|
+
// uses (each child below n a hypothesis, which the table at n gives where the node is used), the assumption at n
|
|
1265
|
+
// too. At a leaf, rank (n, 0): the usual leaf goal at n.
|
|
1266
|
+
function translatedFold() {
|
|
1267
|
+
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 });
|
|
1268
|
+
const na = (f.pre ? 1 : 0) + (f.assume ? 1 : 0), last = (g) => g.hyps.at(-1).x;
|
|
1269
|
+
const kids = [], leaves = [], made = [];
|
|
1270
|
+
for (const [i, l] of laws.entries()) {
|
|
1271
|
+
const g0 = goals.get(`${l.name} @leaf`), g1 = goals.get(`${l.name} @node`), t0 = g0 && usual[g0.name], t1 = g1 && usual[g1.name];
|
|
1272
|
+
if (!g0 || !g1 || !t0 || !t1 || g0.hyps.length !== na + 1 || g1.hyps.length !== na + 2 * L + 1)
|
|
1273
|
+
return { specs: [], why: `${l.name}: no usual tree, or one of another shape` };
|
|
1274
|
+
const used1 = usedHyps(g1, t1);
|
|
1275
|
+
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: [],
|
|
1276
|
+
uses: laws.flatMap((_, j) => [left, right].map(c => ({ node: j, subst: { 'n$': c(n) }, guard: yes }))).filter((_, q) => used1.has(na + q)) });
|
|
1277
|
+
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: [] });
|
|
1278
|
+
made.push({ g: g1, tree: t1, node: L + i, ren }, { g: g0, tree: t0, node: 2 * L + i, ren: ren0 });
|
|
1279
|
+
}
|
|
1280
|
+
const carried = atNode.map((a, i) => {
|
|
1281
|
+
const b = { ...a, variants: undefined, carry: true, uses: [...a.uses, { node: 2 * L + i, subst: {}, guard: eq(kind(n), k(0)) }] };
|
|
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));
|
|
1283
|
+
return { ...b, opaque: o.length ? o : undefined };
|
|
1284
|
+
});
|
|
1285
|
+
const all = [...carried, ...kids, ...leaves], until = withinDeadline(clock() + 10_000);
|
|
1286
|
+
return translateMade(facts, all, made, st, until);
|
|
1287
|
+
}
|
|
717
1288
|
}
|
|
718
1289
|
// --- the cyclic route beside the usual one (GDP_CYCLIC=1) -------------------------------------------------------------
|
|
719
1290
|
// With GDP_CYCLIC=1, every proof the instances above cover is made again as a cyclic proof, and a verdict that differs
|
|
@@ -721,6 +1292,10 @@ export function foldNodes(f, laws) {
|
|
|
721
1292
|
const env = () => globalThis.process?.env;
|
|
722
1293
|
let quiet = 0;
|
|
723
1294
|
export const cyclicOn = () => quiet === 0 && env()?.GDP_CYCLIC === '1';
|
|
1295
|
+
// The `cyclic: {...}` lines research/cyclic/ratios.ts reads, printed only with GDP_CYCLIC=1 (the cross-check) or
|
|
1296
|
+
// GDP_CYCLIC_LOG=1 (modal/run.py sets it): a user's proofs print nothing.
|
|
1297
|
+
export const cycLog = (o) => { if (env()?.GDP_CYCLIC === '1' || env()?.GDP_CYCLIC_LOG === '1')
|
|
1298
|
+
console.error(`cyclic: ${JSON.stringify(o)}`); };
|
|
724
1299
|
// A search's many trial proofs (core/discover.ts, core/infer.ts) aren't compared: what it finds is proven again.
|
|
725
1300
|
export const quietly = (f) => { quiet++; try {
|
|
726
1301
|
return f();
|
|
@@ -728,13 +1303,135 @@ export const quietly = (f) => { quiet++; try {
|
|
|
728
1303
|
finally {
|
|
729
1304
|
quiet--;
|
|
730
1305
|
} };
|
|
731
|
-
|
|
1306
|
+
// A translated proof by what it was made from (the machine, its laws and instances; a recurrence's or fold's case): a
|
|
1307
|
+
// proof made again (a rule proven twice, as the suite does) is the one made and checked before (the same proof: the
|
|
1308
|
+
// 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`.
|
|
1309
|
+
const proofsMade = new Map();
|
|
1310
|
+
// `base`: the key of a proof made before whose nodes these begin with (a machine's laws, for its step laws), reused where
|
|
1311
|
+
// its facts are these.
|
|
1312
|
+
export function translatedProof(key, build, st, base) {
|
|
1313
|
+
const k0 = timedKind('key', 0, key), had = proofsMade.get(k0);
|
|
1314
|
+
if (had) {
|
|
1315
|
+
st.cached++;
|
|
1316
|
+
return { verdict: { proven: true, checked: true, proof: had } };
|
|
1317
|
+
}
|
|
1318
|
+
const b = base && proofsMade.get(timedKind('key', 0, base)), { facts, specs, why } = build(!!b);
|
|
1319
|
+
if (why)
|
|
1320
|
+
return { why };
|
|
1321
|
+
const made = b && b.nodes.length <= specs.length && JSON.stringify(b.facts) === JSON.stringify(facts) ? b.nodes : [];
|
|
1322
|
+
if (b && !made.length)
|
|
1323
|
+
return translatedProof(key, () => build(false), st);
|
|
1324
|
+
const v = proveCyclic(facts, specs, undefined, translatedCache, st, made);
|
|
1325
|
+
if (v.proven && v.checked) {
|
|
1326
|
+
if (proofsMade.size >= 1024)
|
|
1327
|
+
proofsMade.clear();
|
|
1328
|
+
proofsMade.set(k0, v.proof);
|
|
1329
|
+
}
|
|
1330
|
+
return { verdict: v };
|
|
1331
|
+
}
|
|
1332
|
+
export const routeOf = () => env()?.GDP_CYCLIC_ROUTE ?? 'translate';
|
|
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)]))])) } : {});
|
|
1352
|
+
export function crossCyclic(kind, name, usual, run0, usualMs, translated) {
|
|
1353
|
+
const run = run0 ?? none;
|
|
732
1354
|
for (const key of Object.keys(cyclicProf))
|
|
733
1355
|
delete cyclicProf[key];
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
const
|
|
737
|
-
|
|
738
|
-
|
|
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);
|
|
1360
|
+
const ms = Math.round(performance.now() - t0), cyclic = out.proven && out.checked;
|
|
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();
|
|
1366
|
+
let search;
|
|
1367
|
+
if (route === 'both' && run0) {
|
|
1368
|
+
const s0 = performance.now(), o = run0();
|
|
1369
|
+
search = { searchMs: Math.round(performance.now() - s0), searchCyclic: o.proven && o.checked };
|
|
1370
|
+
}
|
|
1371
|
+
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 }) });
|
|
1372
|
+
const lax = env()?.GDP_CYCLIC_LAX === '1';
|
|
1373
|
+
if (cyclic !== usual && !lax)
|
|
739
1374
|
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})`}`);
|
|
1375
|
+
if (search && search.searchCyclic !== usual && !lax)
|
|
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`);
|
|
1377
|
+
}
|
|
1378
|
+
// The certificate of every proof the instances above cover is the cyclic one (GDP_CERT=usual: the usual trees, as
|
|
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();
|
|
1386
|
+
export const certSearch = () => env()?.GDP_CERT_SEARCH === '1';
|
|
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 });
|
|
1391
|
+
export function certCyclic(kind, name, translated, run0, usualMs) {
|
|
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);
|
|
1396
|
+
const checked = out.proven && out.checked;
|
|
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})` : ''}` };
|
|
1400
|
+
}
|
|
1401
|
+
// The step laws' searched nodes after the laws' (stepLawNodes over the sk machine), with the flags' closing facts read
|
|
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
|
|
1403
|
+
// Lean export's (lean-kernel/emit.ts) both try.
|
|
1404
|
+
export function searchedStepNodes(m, laws, steps, facts, lawNodes) {
|
|
1405
|
+
const q = hasQuantified(m);
|
|
1406
|
+
return stepLawNodes(skMachine(m), laws, steps, facts, lawNodes).map((sp, i) => {
|
|
1407
|
+
if (!q)
|
|
1408
|
+
return sp;
|
|
1409
|
+
const more = closingInsts(m, facts, [steps[i].instances], Array.from({ length: (steps[i].law.steps ?? 1) + 1 }, (_, j) => tBack(j)));
|
|
1410
|
+
const add = (x) => ({ ...x, insts: [...x.insts, ...more], ...(x.variants ? { variants: x.variants.map(add) } : {}) });
|
|
1411
|
+
return add(sp);
|
|
1412
|
+
});
|
|
1413
|
+
}
|
|
1414
|
+
// Live.lean's reqC: each input named by its term, two parts per and.
|
|
1415
|
+
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 } });
|
|
1416
|
+
// Witnesses for a helpful request's free inputs, for its allowed goal: all free first, then each free input fixed to 0, a
|
|
1417
|
+
// state variable or a param (a history's query, say, which its relations bound), the first combination that proves.
|
|
1418
|
+
export function withWitness(m, request, attempt) {
|
|
1419
|
+
const free = m.inputs.filter(i => !(i in request));
|
|
1420
|
+
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 }))];
|
|
1421
|
+
const combos = [[]];
|
|
1422
|
+
if (free.length <= 2) {
|
|
1423
|
+
const go = (i, acc) => { if (i === free.length) {
|
|
1424
|
+
if (acc.length)
|
|
1425
|
+
combos.push(acc);
|
|
1426
|
+
return;
|
|
1427
|
+
} for (const c of cands)
|
|
1428
|
+
go(i + 1, c ? [...acc, [free[i], c]] : acc); };
|
|
1429
|
+
go(0, []);
|
|
1430
|
+
}
|
|
1431
|
+
for (const wit of combos) {
|
|
1432
|
+
const out = attempt(wit);
|
|
1433
|
+
if (out !== undefined)
|
|
1434
|
+
return { wit, out };
|
|
1435
|
+
}
|
|
1436
|
+
return undefined;
|
|
740
1437
|
}
|