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