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.
@@ -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 afterOn = () => env()?.GDP_CYCLIC_AFTER !== '0';
147
- const stepNodesOn = () => env()?.GDP_CYCLIC_STEP !== '0';
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
- export function proveCyclic(facts, specs, order = [...specs.keys()].sort((i, j) => Number(!specs[i].variants?.some(x => x.weak)) - Number(!specs[j].variants?.some(x => x.weak)))) {
154
- const nodes = [], shapes = [...specs], weak = new Set();
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: performance.now() + Math.min(2_000, later ?? 2_000) }));
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: performance.now() + Math.min(2_000, later ?? 2_000) }));
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: performance.now() + (later ?? Math.max(20_000, 1_000 * g.hyps.length)) }));
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 (before) {
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: performance.now() + Math.min(2_000, later ?? 2_000) })));
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
- later = j ? (refuted ? 1_000 : 3_000) : undefined;
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: [...(l.forall ? [l.forall] : []), ...(l.keys ?? [])], holds: l.holds });
336
- export function recurNodes(r) {
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
- if (!stepNodesOn())
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, no flag holding at every key, and no law that reads a map at a key
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 !m.history && !Object.values(m.step.flags ?? {}).some(c => c.op === 'all') && !laws.some(l => nested(l.holds));
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
- export function machineNodes(m, laws, instances) {
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) && afterOn() ? [[JSON.stringify(inlineE(e)), get(`${s}@`, t)]] : []; }));
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
- const out = [], seen = new Set();
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 = laws.flatMap((l2, j) => {
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 = laws.flatMap((l2, j) => {
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
- return { facts, specs: sliced(stepNodesOn() ? stepNodes(specs) : specs) };
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
- export function foldNodes(f, laws) {
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
- if (!stepNodesOn())
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
- export function crossCyclic(kind, name, usual, run, usualMs) {
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
- const t0 = performance.now(), out = run(), ms = Math.round(performance.now() - t0);
735
- const cyclic = out.proven && out.checked;
736
- const prof = profOn() ? { prof: Object.fromEntries(Object.entries(cyclicProf).map(([key, p]) => [key, Object.fromEntries(Object.entries(p).map(([f, x]) => [f, Math.round(x)]))])) } : {};
737
- console.error(`cyclic: ${JSON.stringify({ kind, name, usual, cyclic, ms, ...(usualMs === undefined ? {} : { usualMs: Math.round(usualMs) }), ...prof, ...(out.proven ? { nodes: out.proof.nodes.length, ...(out.why ? { why: out.why } : {}) } : { goal: out.goal, stuck: out.stuck }) })}`);
738
- if (cyclic !== usual && env()?.GDP_CYCLIC_LAX !== '1')
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
  }