gdp-wired 0.19.0 → 0.21.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -5,7 +5,7 @@ import { grow, minimal, proveBuilt, proveLaws, proveStepLaws, withDeadline } fro
5
5
  import { refinement, refinementSides } from "../core/compose.js";
6
6
  import { leanSurface, leanTerms } from "./lean-terms.js";
7
7
  import { descentGoal, mainGoal, premiseGoal } from "../kernel/cyclic.js";
8
- import { allowNode, certMs, closingInsts, reqTS, withWitness, cyclicMachine, foldNodes, hasQuantified, kWhens, machineNodes, proveCyclic, qflagNodes, routeOf, searchedStepNodes, skMachine, stepLawNodes, tBack, tSub } from "../core/cyclic.js";
8
+ import { allowNode, certMs, closingInsts, reqTS, withWitness, foldNodes, certMode, quietly, uncoveredWhy, hasQuantified, kWhens, machineNodes, proveCyclic, routeOf, searchedStepNodes, skMachine, stepLawNodes, tBack, tSub } from "../core/cyclic.js";
9
9
  import { leanLibrary } from "./library.js";
10
10
  export { leanLibrary };
11
11
  // --- to Lean ------------------------------------------------------------------------------------------------------------
@@ -49,8 +49,34 @@ export function leaves(t, path, visit) {
49
49
  leaves(t.yes, new Map(path).set(id, { lin: t.split, holds: true }), visit);
50
50
  leaves(t.no, new Map(path).set(id, { lin: t.split, holds: false }), visit);
51
51
  }
52
- export function encode(ex, tamper = false, dropStep = true, globalVars, globalMaps) {
52
+ export function encode(ex, tamper = false, dropStep = true, globalVars, globalMaps, slim = false) {
53
53
  const { goal, tree } = ex;
54
+ // `slim`: the table only has the columns Lean reads. Each row a leaf uses has a hint (where Lean looks for it first),
55
+ // and Lean computes only the hinted hypotheses' linear forms (Kernel.lean's srcRows), so the columns are the
56
+ // variables, those hypotheses' choices and the cells they read, a hinted term column's, and every row's (the path's
57
+ // atoms, the leaf's rows, its squares and products). A leaf with a row of no hint (Lean then reads every
58
+ // hypothesis's rows) keeps the whole table. A premise or descent of a cyclic node reads one or two of its
59
+ // hypotheses, so its table is a few columns, not every choice of the node's facts.
60
+ // Each leaf with its path and rows, found once (the passes below and the tree's text read them in the same order).
61
+ const leafList = [];
62
+ leaves(tree, new Map(), (leaf, path) => { leafList.push({ leaf, path, base: leafConstraints(goal, path) }); });
63
+ const reads = new Set(), hintTerms = new Set();
64
+ let whole = !slim;
65
+ if (slim)
66
+ for (const { leaf, base } of leafList) {
67
+ const n0 = base.length;
68
+ for (const i of [...leaf.leaf.flatMap(st => (st.kind === 'input' && st.index < n0 ? [st.index] : [])), ...(leaf.products ?? []).flat().filter(i => i < n0)]) {
69
+ const src = base[i].src;
70
+ if (!src)
71
+ whole = true;
72
+ else if ('fact' in src)
73
+ reads.add(src.fact);
74
+ else if ('term' in src)
75
+ hintTerms.add(src.term);
76
+ }
77
+ }
78
+ const readHyps = whole ? goal.hyps : goal.hyps.filter((_, i) => reads.has(i));
79
+ const readTerms = whole ? [] : [...hintTerms].map(name => columnTerm(name));
54
80
  // Columns: every variable of the hypotheses, then every cell any leaf uses (and the cells inside their keys),
55
81
  // shallower cells first so a key reads only earlier columns.
56
82
  const vars = globalVars ?? [...new Set(goal.hyps.flatMap(varsOf))];
@@ -102,9 +128,15 @@ export function encode(ex, tamper = false, dropStep = true, globalVars, globalMa
102
128
  else if (typeof val === 'object' && val !== null)
103
129
  choices(val);
104
130
  };
105
- goal.hyps.forEach(choices);
106
- leaves(tree, new Map(), (leaf, path) => {
107
- for (const r of [...withProducts(leafConstraints(goal, path), leaf.products ?? [], leaf.squares), ...(leaf.squares ?? []).map(lin => ({ lin }))])
131
+ readHyps.forEach(choices);
132
+ readTerms.forEach(choices);
133
+ const readSubterms = [...readHyps, ...readTerms.map((t) => ({ op: '<=', a: t, b: t }))].flatMap(subterms).filter(e => e.op === 'prod' || e.op === 'get');
134
+ for (const { leaf, path, base } of leafList) {
135
+ const n0 = base.length;
136
+ const used = whole ? undefined : new Set([...leaf.leaf.flatMap(st => (st.kind === 'input' && st.index < n0 ? [st.index] : [])), ...(leaf.products ?? []).flat().filter(i => i < n0)]);
137
+ const rowsHere = whole ? withProducts(base, leaf.products ?? [], leaf.squares)
138
+ : [...base.filter((_, i) => used.has(i)), ...withProducts(base, leaf.products ?? [], leaf.squares).slice(n0), ...[...path.values()].map(a => ({ lin: a.lin }))];
139
+ for (const r of [...rowsHere, ...(leaf.squares ?? []).map(lin => ({ lin }))])
108
140
  for (const key of Object.keys(r.lin)) {
109
141
  if (isMonomial(key))
110
142
  addMono(key);
@@ -115,18 +147,17 @@ export function encode(ex, tamper = false, dropStep = true, globalVars, globalMa
115
147
  }
116
148
  // Each cell a hypothesis reads under the path, kept or not: two reads of one cell cancel in a row (T − T), yet
117
149
  // Lean's linear form of each still needs the cell's column to see the comparison decided.
118
- for (const h of goal.hyps)
119
- for (const e of subterms(h)) {
120
- if (e.op === 'prod')
121
- for (const key of Object.keys(linear(e, path) ?? {}))
122
- if (isMonomial(key))
123
- addMono(key);
124
- if (e.op === 'get')
125
- for (const key of Object.keys(linear(e, path) ?? {}))
126
- if (isCell(key))
127
- addCell(key);
128
- }
129
- });
150
+ for (const e of readSubterms) {
151
+ if (e.op === 'prod')
152
+ for (const key of Object.keys(linear(e, path) ?? {}))
153
+ if (isMonomial(key))
154
+ addMono(key);
155
+ if (e.op === 'get')
156
+ for (const key of Object.keys(linear(e, path) ?? {}))
157
+ if (isCell(key))
158
+ addCell(key);
159
+ }
160
+ }
130
161
  const depth = (name) => name.split('[').length;
131
162
  // Term columns (open choices, each standing for its term's value) before the cells, whose keys may read them; a
132
163
  // monomial with a cell among its factors after the cells.
@@ -173,13 +204,11 @@ export function encode(ex, tamper = false, dropStep = true, globalVars, globalMa
173
204
  : name.includes('[') ? `.cell ${maps.indexOf(cellMap(name))} ${row(cellKey(name))}`
174
205
  : name.startsWith('⟦') ? `.term (${e(columnTerm(name))})`
175
206
  : `.var ${vars.indexOf(name)}`));
176
- let leafNo = 0;
177
- const lean = (t, path) => {
178
- if (!('leaf' in t)) {
179
- const id = canon(t.split);
180
- return `.split ${row(t.split)}\n (${lean(t.yes, new Map(path).set(id, { lin: t.split, holds: true }))})\n (${lean(t.no, new Map(path).set(id, { lin: t.split, holds: false }))})`;
181
- }
182
- const base = leafConstraints(goal, path), prods = t.products ?? [], squares = t.squares ?? [];
207
+ let leafNo = 0, leafAt = 0;
208
+ const lean = (t) => {
209
+ if (!('leaf' in t))
210
+ return `.split ${row(t.split)}\n (${lean(t.yes)})\n (${lean(t.no)})`;
211
+ const { path, base } = leafList[leafAt++], prods = t.products ?? [], squares = t.squares ?? [];
183
212
  const rows = withProducts(base, prods, squares).map(r => r.lin), n0 = base.length;
184
213
  // Only the rows the derivation and the products read, renumbered (the squares follow them, then the products, in
185
214
  // order); cuts carry their quotient row (Cert's format).
@@ -226,12 +255,17 @@ export function encode(ex, tamper = false, dropStep = true, globalVars, globalMa
226
255
  if ('fact' in src)
227
256
  return `.fact ${src.fact}`;
228
257
  const j = 'pair' in src ? pairs.indexOf([...src.pair].sort().join('\n')) : col.get('nat' in src ? src.nat : src.term) ?? -1;
229
- return j < 0 ? '.any' : 'pair' in src ? `.eq ${j}` : 'nat' in src ? `.nat ${j}` : `.term ${j}`;
258
+ return j < 0 ? '.any' : 'pair' in src ? `.eq ${eqAt.get(j)}` : 'nat' in src ? `.nat ${j}` : `.term ${j}`;
230
259
  };
231
- const at2 = list(used.map(u => `(${row(rows[u])}, ${hint(base[u].src)})`)), rest = `${list(steps)} ${list(eqs)} ${list(prods.map(([i, j]) => `(${idx(i)}, ${idx(j)})`))}`;
260
+ // (slim: only the pairs a row's hint names, renumbered; Lean reads the rest only for a row of no hint, which a slim
261
+ // table has none of)
262
+ const pairAt = (src) => (src && 'pair' in src ? pairs.indexOf([...src.pair].sort().join('\n')) : -1);
263
+ const keepEqs = slim && !whole ? [...new Set(used.map(u => pairAt(base[u].src)).filter(j => j >= 0))] : eqs.map((_, j) => j);
264
+ const eqAt = new Map(keepEqs.map((j, i) => [j, i]));
265
+ const at2 = list(used.map(u => `(${row(rows[u])}, ${hint(base[u].src)})`)), rest = `${list(steps)} ${list(keepEqs.map(j => eqs[j]))} ${list(prods.map(([i, j]) => `(${idx(i)}, ${idx(j)})`))}`;
232
266
  return squares.length ? `.leafSq ${at2} ${list(squares.map(row))} ${rest}` : `.leafAt ${at2} ${rest}`;
233
267
  };
234
- return { tab: list(tab), hyps: list(goal.hyps.map(c)), tree: lean(tree, new Map()), cols, c, e };
268
+ return { tab: list(tab), tabCols: tab, hyps: list(goal.hyps.map(c)), tree: lean(tree), cols, c, e };
235
269
  }
236
270
  export function goalToLean(ex, i, tamper = false, dropStep = true) {
237
271
  const { tab, hyps, tree, cols } = encode(ex, tamper, dropStep);
@@ -335,17 +369,30 @@ theorem ${id}_laws : ∀ maps ρ, evalC ρ maps ${id}_fold.pre = true → ∀ t,
335
369
  // abbreviation for its column), proven from the machine's theorem by unfolding the law's own definition. The statement
336
370
  // is what a Lean development cites; the column numbers stay inside the namespace.
337
371
  // `have`: the line that gives each law as a hypothesis hl (an SEntry's Holds by default; a cyclic proof's, holdsOver).
372
+ // A law with "there exists" (core/exists.ts, `ex` on the law the kernel proved) is stated as the existential it is:
373
+ // `<id>_someN`, ∃ w1 .. wn, P with the bound naturals set to them (updAll, Ghost.lean), from the law proven with its
374
+ // witness terms in place (some_of_subst) or over the machine's ghosts at every reachable state of the machine without
375
+ // them (some_of_ghost); and `<id>.lawN` the same over named variables, each bound natural as w1, w2, ... (`lit`: the
376
+ // condition printer, for P's literal).
338
377
  export function readable(id, laws, vars, maps, reach) {
339
378
  for (const name of [...vars, ...maps])
340
379
  if (name === 'ρ' || name === 'μ' || name.includes('«') || name.includes('»'))
341
380
  throw new Error(`${id}: the name ${name} can't be a Lean abbreviation here`);
342
381
  const q = (name) => `«${name}»`;
343
- const statement = (law) => {
382
+ // Division and remainder by a constant (div$k, mod$k): Lean's x / k and x % k, under the hypothesis that the map is
383
+ // that function (what the facts' theorem, <id>_facts_hold, takes too), rewritten into the statement.
384
+ const arithOf = (map) => { const m = /^(div|mod)\$(\d+)$/.exec(map); return m ? { op: m[1] === 'div' ? '/' : '%', k: Number(m[2]) } : undefined; };
385
+ const arithIn = (law) => [...new Set([...JSON.stringify(law.holds).matchAll(/"op":"get","map":"((?:div|mod)\$\d+)"/g)].map(m => m[1]))].filter(mp => maps.includes(mp)).sort();
386
+ const hypName = (map) => `h_${map.replace('$', '')}`;
387
+ const arithBinders = (law) => arithIn(law).map(mp => ` (${hypName(mp)} : μ ${q(mp)} = fun x => x ${arithOf(mp).op} ${arithOf(mp).k})`).join('');
388
+ const keyName = (law, name) => { const j = (law.keys ?? []).indexOf(name); return name === law.forall ? 'k' : j >= 0 ? `k${j + 2}` : undefined; };
389
+ const statement = (law, plain = true) => {
390
+ const bound = law.ex?.vars ?? [];
344
391
  const t = (e) => {
345
392
  switch (e.op) {
346
393
  case 'var': {
347
- const j = (law.keys ?? []).indexOf(e.name);
348
- return e.name === law.forall ? 'k' : j >= 0 ? `k${j + 2}` : `ρ ${q(e.name)}`;
394
+ const b = bound.indexOf(e.name);
395
+ return b >= 0 ? `w${b + 1}` : keyName(law, e.name) ?? `ρ ${q(e.name)}`;
349
396
  }
350
397
  case 'const': return String(e.value);
351
398
  case 'add': return `(${t(e.a)} + ${t(e.b)})`;
@@ -355,7 +402,10 @@ export function readable(id, laws, vars, maps, reach) {
355
402
  case 'min':
356
403
  case 'max': return `(${e.op} ${t(e.a)} ${t(e.b)})`;
357
404
  case 'if': return `(if ${p(e.c)} then ${t(e.then)} else ${t(e.else)})`;
358
- case 'get': return `(μ ${q(e.map)} (${t(e.key)}))`;
405
+ case 'get': {
406
+ const a = plain ? arithOf(e.map) : undefined;
407
+ return a ? `(${t(e.key)} ${a.op} ${a.k})` : `(μ ${q(e.map)} (${t(e.key)}))`;
408
+ }
359
409
  }
360
410
  };
361
411
  const p = (c) => {
@@ -370,16 +420,170 @@ export function readable(id, laws, vars, maps, reach) {
370
420
  case 'all': throw new Error(`${id}: a law has no quantifier inside`);
371
421
  }
372
422
  };
373
- return law.keys?.length ? `∀ k ${law.keys.map((_, j) => `k${j + 2}`).join(' ')} : Nat, ${p(law.holds)}` : law.forall ? `∀ k : Nat, ${p(law.holds)}` : p(law.holds);
423
+ return quantified(law, law.ex ? `∃ ${bound.map((_, i) => `w${i + 1}`).join(' ')} : Nat, ${p(law.ex.body)}` : p(law.holds));
424
+ };
425
+ const haveOf = (j) => (reach.have ? reach.have(j) : `have hl : ${id}_l${j}.Holds ρ μ := ${reach.call} ${id}_e${j} (by simp [${id}_proof])`);
426
+ // The existential of law j (one with `ex`): its theorem over the machine's own variables, then over named ones.
427
+ const someOf = (law, j) => {
428
+ const ex = law.ex, lit = reach.lit, eLit = reach.eLit;
429
+ if (!lit || !eLit)
430
+ throw new Error(`${id}: ${law.name} has "there exists" and no printer for its condition`);
431
+ if (law.keys?.length || (law.forall && ex.ghost))
432
+ throw new Error(`${id}: ${law.name}: "there exists" past one key, or through a ghost under a key, isn't exported`);
433
+ const ix = (name) => { const i = vars.indexOf(name); if (i < 0)
434
+ throw new Error(`${id}: no variable for ${name}`); return i; };
435
+ const ws = ex.vars.map((_, i) => `w${i + 1}`), at = (env, vals) => `updAll ${env} [${ex.vars.map((n, i) => `(${ix(n)}, ${vals[i]})`).join(', ')}]`;
436
+ const P = `(${lit(ex.body)})`, key = law.forall ? ix(law.forall) : undefined;
437
+ const sigma = ex.sub ? `[${ex.vars.map(n => `(${ix(n)}, ${eLit(ex.sub[n])})`).join(', ')}]` : '';
438
+ const machine = ex.ghost ? `${id}_ghosted.orig` : `${id}_machine`;
439
+ const head = `theorem some${j} : ∀ ρ μ, SReach ${machine} ρ μ → ${key !== undefined ? '∀ k : Nat, ' : ''}∃ ${ws.join(' ')} : Nat, evalC (${at(key !== undefined ? `(upd ρ ${key} k)` : 'ρ', ws)}) μ ${P} = true`;
440
+ const proof = ex.ghost
441
+ ? ` intro ρ μ h
442
+ obtain ⟨ρg, hg⟩ := some_of_ghost ${id}_ghosted ${id}_ghostOk [${ex.vars.map(ix).join(', ')}] ${P} (by ${checkBy}) (fun ρ μ h => by
443
+ ${haveOf(j)}
444
+ exact hl) ρ μ h
445
+ exact ⟨${ex.vars.map(n => `ρg ${ix(n)}`).join(', ')}, hg⟩`
446
+ : ` intro ρ μ h${key !== undefined ? ' k' : ''}
447
+ ${haveOf(j)}
448
+ have hs : substC ${sigma} ${P} = ${id}_l${j}.holds := beqC_eq (by ${checkBy})
449
+ have hq : evalC ${key !== undefined ? `(upd ρ ${key} k)` : 'ρ'} μ (substC ${sigma} ${P}) = true := by rw [hs]; exact hl${key !== undefined ? ' k' : ''}
450
+ exact ⟨${ex.vars.map(n => `applyS ${sigma} ${key !== undefined ? `(upd ρ ${key} k)` : 'ρ'} μ ${ix(n)}`).join(', ')}, some_of_subst ${sigma} ${P} _ μ hq⟩`;
451
+ const plain = JSON.stringify(ex.body).includes('"op":"if"') ? `-- ${law.name}: its terms read an if, so no plain statement here (some${j} holds it)` : `/-- ${law.name.replace(/-\//g, '- /')} -/
452
+ theorem law${j} ${reach.binders} (h : SReach ${machine} ρ μ) : ${statement(law)} := by
453
+ ${key !== undefined ? 'intro k\n ' : ''}obtain ⟨${ws.join(', ')}, hg⟩ := some${j} ρ μ h${key !== undefined ? ' k' : ''}
454
+ exact ⟨${ws.join(', ')}, (evalC_iff (${at(key !== undefined ? `(upd ρ ${key} k)` : 'ρ', ws)}) μ ${P}).mp hg⟩`;
455
+ return `-- ${law.name}: there exists, proven ${ex.ghost ? 'through ghosts the machine carries' : 'with its witness terms in place'}
456
+ ${head} := by
457
+ ${proof}
458
+ ${plain}`;
459
+ };
460
+ const quantified = (law, body) => (law.keys?.length ? `∀ k ${law.keys.map((_, j) => `k${j + 2}`).join(' ')} : Nat, ${body}` : law.forall ? `∀ k : Nat, ${body}` : body);
461
+ // Where the source says integer (a variable x beside x$neg: the value x − x$neg, core/dsl.ts's Int), the law again
462
+ // over Int: each comparison's two sides moved to one, each integer's two naturals read together as its value, x div k
463
+ // and x mod k of an integer as Lean's Int / and % (pos + (k − 1)·neg's quotient is the value's plus neg, its remainder
464
+ // the value's). Proven from the law over the naturals by omega. Only for laws whose terms are sums (no if, min, max,
465
+ // truncation or product): those have no plain statement over Int to give.
466
+ const ints = vars.filter(x => vars.includes(`${x}$neg`));
467
+ const intStatement = (law) => {
468
+ const plus = (a, b, f = 1) => { const out = new Map([...a].map(([key, v]) => [key, { ...v }])); for (const [key, v] of b) {
469
+ const w = out.get(key);
470
+ if (w)
471
+ w.c += f * v.c;
472
+ else
473
+ out.set(key, { c: f * v.c, show: v.show });
474
+ } return out; };
475
+ const natShow = (e) => { const r = lin(e); if (!r)
476
+ return undefined; return statementOfE(e); };
477
+ const statementOfE = (e) => { const l2 = { ...law, holds: { op: '=', a: e, b: e } }; return statement({ ...l2, forall: undefined, keys: undefined }).split(' = ')[0]; };
478
+ const value = (x) => `((ρ ${q(x)} : Int) - ρ ${q(`${x}$neg`)})`;
479
+ // An integer's pos + (k − 1)·neg, read back as the integer.
480
+ const intKey = (key, k) => {
481
+ const l = lin(key);
482
+ if (!l)
483
+ return undefined;
484
+ const terms = [...l].filter(([, v]) => v.c !== 0);
485
+ const x = ints.find(i => l.get(`v:${i}`)?.c === 1);
486
+ return x && terms.length === (k > 1 ? 2 : 1) && (k === 1 || l.get(`v:${x}$neg`)?.c === k - 1) && !l.has('1') ? x : undefined;
487
+ };
488
+ const lin = (e) => {
489
+ switch (e.op) {
490
+ case 'var': {
491
+ const kn = keyName(law, e.name);
492
+ return new Map([[`v:${e.name}`, { c: 1, show: kn ? `(${kn} : Int)` : `(ρ ${q(e.name)} : Int)` }]]);
493
+ }
494
+ case 'const': return e.value ? new Map([['1', { c: e.value, show: '' }]]) : new Map();
495
+ case 'add': {
496
+ const a = lin(e.a), b = lin(e.b);
497
+ return a && b && plus(a, b);
498
+ }
499
+ case 'mul': {
500
+ const a = lin(e.a);
501
+ return a && new Map([...a].map(([key, v]) => [key, { c: v.c * e.k, show: v.show }]));
502
+ }
503
+ case 'get': {
504
+ const a = arithOf(e.map), x = a ? intKey(e.key, a.k) : undefined;
505
+ if (a && x)
506
+ return a.op === '%' ? new Map([[`mod:${a.k}:${x}`, { c: 1, show: `(${value(x)} % ${a.k})` }]]) : new Map([[`div:${a.k}:${x}`, { c: 1, show: `(${value(x)} / ${a.k})` }], [`v:${x}$neg`, { c: 1, show: `(ρ ${q(`${x}$neg`)} : Int)` }]]);
507
+ const shown = natShow(e.key) === undefined ? undefined : statementOfE(e);
508
+ return shown === undefined ? undefined : new Map([[`get:${shown}`, { c: 1, show: `(${shown} : Int)` }]]);
509
+ }
510
+ case 'sub':
511
+ case 'min':
512
+ case 'max':
513
+ case 'prod':
514
+ case 'if': return undefined;
515
+ }
516
+ };
517
+ const side = (ts) => (ts.length ? ts.map(([show, c]) => (c === 1 ? show : `${c} * ${show}`)).join(' + ') : '0');
518
+ const cmp = (c) => {
519
+ const a = lin(c.a), b = lin(c.b);
520
+ if (!a || !b)
521
+ return undefined;
522
+ const l = plus(a, b, -1);
523
+ // Each integer's two naturals together: c·x − c·x$neg is c times its value.
524
+ for (const x of ints) {
525
+ const pos = l.get(`v:${x}`), neg = l.get(`v:${x}$neg`);
526
+ if (pos && neg && pos.c === -neg.c && pos.c !== 0) {
527
+ l.delete(`v:${x}`);
528
+ l.delete(`v:${x}$neg`);
529
+ l.set(`int:${x}`, { c: pos.c, show: value(x) });
530
+ }
531
+ }
532
+ const k0 = l.get('1')?.c ?? 0;
533
+ l.delete('1');
534
+ const terms = [...l.values()].filter(v => v.c !== 0);
535
+ const left = [...terms.filter(v => v.c > 0).map(v => [v.show, v.c]), ...(k0 > 0 ? [[String(k0), 1]] : [])];
536
+ const right = [...terms.filter(v => v.c < 0).map(v => [v.show, -v.c]), ...(k0 < 0 ? [[String(-k0), 1]] : [])];
537
+ return `${side(left)} ${c.op === '<=' ? '≤' : c.op} ${side(right)}`;
538
+ };
539
+ const p = (c) => {
540
+ switch (c.op) {
541
+ case '<=':
542
+ case '<':
543
+ case '=': return cmp(c);
544
+ case 'and':
545
+ case 'or': {
546
+ const xs = c.xs.map(p);
547
+ return xs.some(x => x === undefined) ? undefined : xs.length ? `(${xs.join(c.op === 'and' ? ' ∧ ' : ' ∨ ')})` : c.op === 'and' ? 'True' : 'False';
548
+ }
549
+ case 'not': {
550
+ const x = p(c.x);
551
+ return x === undefined ? undefined : `¬(${x})`;
552
+ }
553
+ case 'flag':
554
+ case 'all': return undefined;
555
+ }
556
+ };
557
+ const reads = new Set([...JSON.stringify(law.holds).matchAll(/"name":"([^"]+)"/g)].map(m => m[1]));
558
+ if (!ints.some(x => reads.has(x) || reads.has(`${x}$neg`)))
559
+ return undefined;
560
+ const body = p(law.holds);
561
+ return body === undefined ? undefined : quantified(law, body);
374
562
  };
563
+ const argNames = (binders) => [...binders.matchAll(/\((\S+) :/g)].map(m => m[1]);
375
564
  const used = new Set(laws.flatMap(l => [...JSON.stringify(l.holds).matchAll(/"name":"([^"]+)"|"map":"([^"]+)"/g)].map(m => m[1] ?? m[2]).filter(name => name !== l.forall && !(l.keys ?? []).includes(name))));
376
565
  return `namespace ${id}
377
566
  ${[...vars.filter(v => used.has(v)).map(v => `abbrev ${q(v)} : Nat := ${vars.indexOf(v)}`), ...maps.filter(mp => used.has(mp)).map(mp => `abbrev ${q(mp)} : Nat := ${maps.indexOf(mp)}`)].join('\n')}
378
- ${laws.map((law, j) => (law.stated ? `-- ${law.name}: as written, with some key; proven (below) with the prover's witness in place\n` : '') + (JSON.stringify(law.holds).includes('"op":"if"') ? `-- ${law.name}: its terms read an if, so no plain statement here (${id}_laws holds it)` : `/-- ${law.name.replace(/-\//g, '- /')} -/
379
- theorem law${j} ${reach.binders} (h : ${reach.hyp}) : ${statement(law)} := by
380
- ${law.keys?.length ? `${reach.have ? reach.have(j) : `have hl : ${id}_e${j}.Holds ρ μ := ${reach.call} ${id}_e${j} (by simp [${id}_proof])`}
381
- exact fun k ${law.keys.map((_, i) => `k${i + 2}`).join(' ')} => (evalC_iff (upd (${law.keys.reduce((acc, key, i) => `upd (${acc}) ${vars.indexOf(key)} k${i + 2}`, 'ρ')}) ${vars.indexOf(law.forall)} k) μ ${id}_l${j}.holds).mp (hl ${law.keys.map((_, i) => `k${i + 2}`).join(' ')} k)` : `${reach.have ? reach.have(j) : `have hl : ${id}_l${j}.Holds ρ μ := ${reach.call} ${id}_e${j} (by simp [${id}_proof])`}
382
- ${law.forall ? `exact fun k => (evalC_iff (upd ρ ${vars.indexOf(law.forall)} k) μ ${id}_l${j}.holds).mp (hl k)` : `exact (evalC_iff ρ μ ${id}_l${j}.holds).mp hl`}`}`)).join('\n')}
567
+ ${laws.map((law, j) => {
568
+ if (law.ex)
569
+ return someOf(law, j);
570
+ if (JSON.stringify(law.holds).includes('"op":"if"'))
571
+ return `-- ${law.name}: its terms read an if, so no plain statement here (${id}_laws holds it)`;
572
+ const hyps = arithBinders(law), names = arithIn(law).map(hypName), rw = names.length ? `have hμ : ${statement(law, false)} := hl'\n rw [${names.join(', ')}] at hμ\n exact hμ` : `exact hl'`;
573
+ const own = law.keys?.length ? `${reach.have ? reach.have(j) : `have hl : ${id}_e${j}.Holds ρ μ := ${reach.call} ${id}_e${j} (by simp [${id}_proof])`}
574
+ have hl' := fun k ${law.keys.map((_, i) => `k${i + 2}`).join(' ')} => (evalC_iff (upd (${law.keys.reduce((acc, key, i) => `upd (${acc}) ${vars.indexOf(key)} k${i + 2}`, 'ρ')}) ${vars.indexOf(law.forall)} k) μ ${id}_l${j}.holds).mp (hl ${law.keys.map((_, i) => `k${i + 2}`).join(' ')} k)
575
+ ${rw}` : `${reach.have ? reach.have(j) : `have hl : ${id}_l${j}.Holds ρ μ := ${reach.call} ${id}_e${j} (by simp [${id}_proof])`}
576
+ ${law.forall ? `have hl' := fun k => (evalC_iff (upd ρ ${vars.indexOf(law.forall)} k) μ ${id}_l${j}.holds).mp (hl k)` : `have hl' := (evalC_iff ρ μ ${id}_l${j}.holds).mp hl`}
577
+ ${rw}`;
578
+ const int = intStatement(law);
579
+ return (law.stated ? `-- ${law.name}: as written, with some key; proven (below) with the prover's witness in place\n` : '') + `/-- ${law.name.replace(/-\//g, '- /')} -/
580
+ theorem law${j} ${reach.binders}${hyps} (h : ${reach.hyp}) : ${statement(law)} := by
581
+ ${own}` + (int === undefined ? '' : `
582
+ /-- ${law.name.replace(/-\//g, '- /')}, over Int -/
583
+ theorem law${j}_int ${reach.binders}${hyps} (h : ${reach.hyp}) : ${int} := by
584
+ have hn := law${j} ${[...argNames(reach.binders), ...names, 'h'].join(' ')}
585
+ ${law.forall ? `intro k${(law.keys ?? []).map((_, i) => ` k${i + 2}`).join('')}\n have hn := hn k${(law.keys ?? []).map((_, i) => ` k${i + 2}`).join('')}\n ` : ''}omega`);
586
+ }).join('\n')}
383
587
  end ${id}`;
384
588
  }
385
589
  // A liveness as Live.lean's record: from, to, the rank's parts, each helpful request with its region, the primes.
@@ -539,7 +743,9 @@ function machineNames(m, laws, allSteps, stepLaws, opts) {
539
743
  const primes = allSteps.length ? Object.keys(m.state).map(s2 => `${s2}'`) : [];
540
744
  const vars = [...new Set([...m.params, ...m.inputs, ...Object.keys(m.state), ...keysOf, ...keysOf.map(k2 => `${k2}#`), '#', ...flags.map(([f]) => `?${f}`),
541
745
  ...quantified.flatMap(qwits), ...boundKeys(m), ...(opts.tamper ? boundKeys(opts.tamper.machine) : []), ...stepLaws.flatMap(sl => (sl.forall ? [sl.forall] : [])), ...(opts.free ?? []),
542
- ...primes, ...later, ...Array.from({ length: k - 1 }, (_, j) => Object.keys(m.state).map(n2 => stateName(n2, j + 1))).flat(), ...(opts.record ?? []).map(f => `r.${f}`)])];
746
+ ...primes, ...later, ...Array.from({ length: k - 1 }, (_, j) => Object.keys(m.state).map(n2 => stateName(n2, j + 1))).flat(), ...(opts.record ?? []).map(f => `r.${f}`),
747
+ // (the naturals a law with "there exists" binds, which its statement sets: core/exists.ts)
748
+ ...laws.flatMap(l => l.ex?.vars ?? [])])];
543
749
  const baseMaps = [...new Set([...Object.keys(m.maps ?? {}), ...Object.keys(m.step.maps ?? {}), ...Object.keys(m.fns ?? {})])];
544
750
  const maps = [...new Set([...baseMaps, ...carried, ...(allSteps.length ? Object.keys(m.maps ?? {}).map(mp => `${mp}'`) : []), ...laterMaps])];
545
751
  return { vars, maps };
@@ -640,7 +846,7 @@ example : (checkSMachine ${id}_tampered ${id}_proof && sliveOk ${id}_tampered ${
640
846
  theorem ${id}_orig_laws : ∀ ρ M, SReach ${id}_ghosted.orig ρ M → ∀ e ∈ ${id}_proof, avoids ${id}_ghosted.vars e.law.holds = true → e.Holds ρ M :=
641
847
  sghost_laws ${id}_ghosted ${id}_ghostOk ${id}_proof ${id}_check` });
642
848
  const allSteps = claimSteps();
643
- const r = proveStepLaws(m, laws, allSteps);
849
+ const r = proveStepLawsOnce(m, laws, allSteps);
644
850
  if (!r.proven || !r.proof)
645
851
  throw new Error(`${id}: not proven`);
646
852
  const lp = r.proof.laws, si = r.proof.instances;
@@ -818,7 +1024,7 @@ def ${id}_proof : List SEntry := ${list(laws.map((_, j) => `${id}_e${j}`))}
818
1024
  theorem ${id}_check : checkSMachine ${id}_machine ${id}_proof = true := by ${checkBy}
819
1025
  theorem ${id}_laws : ∀ ρ M, SReach ${id}_machine ρ M → ∀ e ∈ ${id}_proof, e.Holds ρ M :=
820
1026
  smachine_sound ${id}_machine ${id}_proof ${id}_check
821
- ${hm ? histBlock() : readable(id, laws, vars, mapNames, { binders: '(ρ : Nat → Nat) (μ : Nat → Nat → Nat)', hyp: `SReach ${id}_machine ρ μ`, call: `${id}_laws ρ μ h` })}${opts.noFacts ? '' : factsProof(id, m, mapNames, vars, opts.simp)}${opts.tamper ? `
1027
+ ${hm ? histBlock() : readable(id, laws, vars, mapNames, { binders: '(ρ : Nat → Nat) (μ : Nat → Nat → Nat)', hyp: `SReach ${id}_machine ρ μ`, call: `${id}_laws ρ μ h`, lit: c, eLit: e })}${opts.noFacts ? '' : factsProof(id, m, mapNames, vars, opts.simp)}${opts.tamper ? `
822
1028
  -- TAMPERED: ${opts.tamper.what}. Lean computes the check to false.
823
1029
  def ${id}_tampered : SMachine := ${lit(opts.tamper.machine)}
824
1030
  example : checkSMachine ${id}_tampered ${id}_proof = false := by ${checkBy}` : ''}${records.length ? `\n\n${records.join('\n\n')}` : ''}${opts.after ? `\n\n${opts.after(ix)}` : ''}`;
@@ -830,18 +1036,31 @@ example : checkSMachine ${id}_tampered ${id}_proof = false := by ${checkBy}` : '
830
1036
  // liveness's), carried to the machine by Bridge.lean's cyc_machine_sound and BridgeRules.lean's cyc_kstep_sound,
831
1037
  // sleads_of_holds (liveness, loops), hreach_plain (histories) and cyc_ghost_holds (ghosts), and each theorem stated as the
832
1038
  // usual export states it: the same machine, laws, step laws and liveness (machineNames numbers them alike), the same
833
- // `<id>.lawN`, `<id>_stepN_holds`, `<id>_liveN_holds`, `<id>_halts`. What the cyclic route doesn't cover goes the usual
834
- // way: a flag that holds at every key, a law that reads a map at a key read from a map, a refinement, and a liveness's
835
- // weak fairness (its helpful requests proven allowed: a claim about requests the run doesn't take).
1039
+ // `<id>.lawN`, `<id>_stepN_holds`, `<id>_liveN_holds`, `<id>_halts`; a machine with flags that hold at every key through
1040
+ // its sk machine (BridgeRules.lean's q bridges). What the cyclic route doesn't cover goes the usual way: a law that reads
1041
+ // a map at a key read from a map, and whatever no certificate is found for within the deadline (`certReport`).
1042
+ // The usual proof of a machine's laws and step laws, made once per toLean: an item exported the usual way after its
1043
+ // cyclic certificate wasn't found (cycMachineProof) reads the proof that search started from, not one made again. It
1044
+ // is made with no cyclic certificate of its own (quietly): the export makes the certificate it exports from the usual
1045
+ // trees (cycMachineProof), and the usual export reads the trees, so a certificate made here is never read.
1046
+ const usualMade = new Map();
1047
+ function proveStepLawsOnce(m, laws, stepLaws) {
1048
+ const key = JSON.stringify([m, laws, stepLaws]), had = usualMade.get(key);
1049
+ if (had)
1050
+ return had;
1051
+ const r = quietly(() => proveStepLaws(m, laws, stepLaws));
1052
+ usualMade.set(key, r);
1053
+ return r;
1054
+ }
836
1055
  const envOf = () => globalThis.process?.env;
837
- export const cyclicCerts = () => envOf()?.GDP_CERT !== 'usual';
1056
+ export const cyclicCerts = certMode;
838
1057
  let certsOn = false, cycTexts = 0;
839
1058
  // What the last toLean exported from cyclic certificates, and what went the usual way (and why).
840
1059
  export const certReport = { cyclic: [], usual: [], translatedSteps: [] };
841
1060
  // Each item's cyclic certificate is made within one deadline (GDP_CERT_MS, 10 s by default), every goal it grows (the
842
1061
  // translation's leaves grown again, the searched nodes) bounded by it: past it, the item goes the usual way.
843
1062
  function cycMachineProof(m, laws, allSteps, allows = []) {
844
- const r = proveStepLaws(m, laws, allSteps);
1063
+ const r = proveStepLawsOnce(m, laws, allSteps);
845
1064
  if (!r.proven || !r.proof)
846
1065
  return { why: 'not proven the usual way' };
847
1066
  const until = performance.now() + certMs();
@@ -850,13 +1069,14 @@ function cycMachineProof(m, laws, allSteps, allows = []) {
850
1069
  function cycMachineProofBy(m, laws, allSteps, allows, r, until) {
851
1070
  if (!r.proof)
852
1071
  return { why: 'not proven the usual way' };
1072
+ const tStart = performance.now();
853
1073
  const lp = r.proof.laws, instOf = (sl) => r.proof.perStep?.[sl.name] ?? r.proof.instances;
854
1074
  // A machine with flags that hold at every key: its sk machine's nodes, the flags' closing facts read where each node
855
1075
  // reads the run.
856
1076
  const q = hasQuantified(m), mm = skMachine(m);
857
1077
  const L = laws.length, mine = (facts, lawNodes) => searchedStepNodes(m, laws, allSteps.map(sl => ({ law: sl, instances: instOf(sl) })), facts, lawNodes);
858
1078
  const tries = [];
859
- if (!q && routeOf() !== 'search' && allSteps.length) {
1079
+ if (routeOf() !== 'search' && allSteps.length) {
860
1080
  const usualSteps = allSteps.map(sl => { const inst = instOf(sl), g = stepLawObligations(m, laws, inst, sl)[0]; return { sl, inst, goal: g, tree: r.proof.trees[g.name] }; });
861
1081
  const tr = usualSteps.every(u => u.tree) ? machineNodes(m, laws, lp.instances, { trees: lp.trees, steps: usualSteps }) : undefined;
862
1082
  if (tr && !tr.why && tr.specs.length === 3 * L + 2 * allSteps.length) {
@@ -870,15 +1090,18 @@ function cycMachineProofBy(m, laws, allSteps, allows, r, until) {
870
1090
  tries.push({ facts: tr.facts, specs: tr.specs, lawNodes: tr.specs, stepAt: allSteps.map((_, j) => 3 * L + 2 * j + 1), translated: true });
871
1091
  }
872
1092
  }
873
- for (const base of (q ? [qflagNodes(m, laws, lp.instances)]
874
- : [...(routeOf() !== 'search' ? [machineNodes(m, laws, lp.instances, { trees: lp.trees })] : []), machineNodes(m, laws, lp.instances)]).filter(b => !b.why)) {
1093
+ for (const base of [...(routeOf() !== 'search' ? [machineNodes(m, laws, lp.instances, { trees: lp.trees })] : []), machineNodes(m, laws, lp.instances)].filter(b => !b.why)) {
875
1094
  tries.push({ facts: base.facts, specs: [...base.specs, ...mine(base.facts, base.specs)], lawNodes: base.specs, stepAt: allSteps.map((_, j) => base.specs.length + j) });
876
1095
  }
877
1096
  let last = 'no nodes';
1097
+ const t00 = tStart;
878
1098
  for (const { facts, specs: specs0, lawNodes, stepAt, translated } of tries) {
879
1099
  if (performance.now() > until)
880
1100
  return { why: `past the certificate's deadline (${certMs()} ms)` };
1101
+ const tTry = performance.now();
881
1102
  let specs = specs0, v = proveCyclic(facts, specs);
1103
+ if (envOf()?.GDP_LEAN_TIMES === '1')
1104
+ console.error(`lean try ${m.name}: ${translated ? 'translated' : 'laws + searched steps'}, ${specs.length} nodes, ${v.proven && v.checked ? 'proven' : 'not'}, ${Math.round(performance.now() - tTry)} ms (${Math.round(tTry - t00)} ms building before)`);
882
1105
  if (!(v.proven && v.checked)) {
883
1106
  last = performance.now() > until ? `past the certificate's deadline (${certMs()} ms)` : v.proven ? `not checked (${v.why})` : `${v.node}: ${v.goal}: ${v.stuck}`;
884
1107
  continue;
@@ -918,8 +1141,9 @@ export function cycMachineToLean(id, given, laws, opts = {}) {
918
1141
  const hm = given.history ? { ...given, history: { ...given.history, prune: undefined } } : undefined;
919
1142
  const hplain = hm ? historyMachine(hm) : undefined, m = hplain ? hplain.machine : given;
920
1143
  const stepLaws = opts.stepLaws ?? [], gv = opts.ghosts ?? [], lp0 = opts.loop;
921
- if (!cyclicMachine(skMachine(m), laws))
922
- return { why: 'a law reading a map at a key read from a map' };
1144
+ const why = uncoveredWhy(m, laws);
1145
+ if (why)
1146
+ return { why };
923
1147
  // With flags that hold at every key, the sk machine's encoding and BridgeRules.lean's q bridges.
924
1148
  const mq = skMachine(m), Q = hasQuantified(m) ? 'Q' : '', q = hasQuantified(m) ? 'q' : '';
925
1149
  const lives = (opts.liveness ?? []).map(l => ({ l, ...livenessParts(m, l) }));
@@ -948,6 +1172,15 @@ export function cycMachineToLean(id, given, laws, opts = {}) {
948
1172
  const state = Object.keys(m.state), carried = m.inputMaps ?? [];
949
1173
  const primesL = () => list(state.map(s2 => `(${ix(s2)}, ${ix(`${s2}'`)})`)), primeMapsL = () => list(Object.keys(m.maps ?? {}).map(mp => `(${mx(mp)}, ${mx(`${mp}'`)})`));
950
1174
  const node = (i) => `${id}_node${i}`;
1175
+ // What every law's and step law's check reads alike, proven once: the exported facts among the encoding's, and (with
1176
+ // no flag at every key) the encoding's shape and terms at no keys, which a law's at its keys implies. Each check then
1177
+ // reads those as true (simp only, unfolding the check), not computed again per law.
1178
+ const sharedOk = [`${id}_factsIn`, ...(Q ? [] : [`${id}_shape`, `${id}_terms`])].join(', ');
1179
+ const okLemmas = `theorem ${id}_factsIn : cyc${Q}FactsIn ${id}_machine ${id}_enc ${id}_facts = true := by ${checkBy}${Q ? '' : `
1180
+ theorem ${id}_shape : cycShapeOk ${id}_machine ${id}_enc [] = true := by ${checkBy}
1181
+ theorem ${id}_terms : cycTermsOk ${id}_machine ${id}_enc [] = true := by ${checkBy}`}`;
1182
+ const mOk = `by simp only [cyc${Q}MOk, ${sharedOk}]; ${checkBy}`, kOk = `by simp only [cyc${Q}KOk, cyc${Q}KOkAt, ${sharedOk}]; ${checkBy}`;
1183
+ const aOk = `by simp only [cyc${Q}AOk, ${sharedOk}]; ${checkBy}`;
951
1184
  const kstep = (sk, i) => `cyc_${q}kstep_sound ${id}_machine ${id}_enc ${id}_facts ${id}_nodes ${id}_check ${sk} ${node(i)} ${node(i)}_mem`;
952
1185
  // The machine as the usual export defines it: with a history its plain machine, with ghosts the ghosted one, a loop's.
953
1186
  const orig = { ...m, state: Object.fromEntries(Object.entries(m.state).filter(([n2]) => !gv.includes(n2))), step: { ...m.step, state: Object.fromEntries(Object.entries(m.step.state ?? {}).filter(([n2]) => !gv.includes(n2))) } };
@@ -985,7 +1218,7 @@ def ${id}_machine : SMachine := ${id}_loop.machine` : `def ${id}_machine : SMach
985
1218
  const lawBlocks = laws.map((l, j) => `-- ${l.name}
986
1219
  def ${id}_law${j} : C := ${c(l.holds)}
987
1220
  def ${id}_l${j} : MLaw := ⟨${id}_law${j}, ${l.forall ? `some ${ix(l.forall)}` : 'none'}⟩
988
- theorem ${id}_law${j}_ok : cyc${Q}MOk ${id}_machine ${id}_enc ${id}_facts ${id}_law${j} ${keysL(l)} ${node(j)} = true := by ${checkBy}
1221
+ theorem ${id}_law${j}_ok : cyc${Q}MOk ${id}_machine ${id}_enc ${id}_facts ${id}_law${j} ${keysL(l)} ${node(j)} = true := ${mOk}
989
1222
  theorem ${id}_law${j}_holds : ∀ ρ M, SReach ${id}_machine ρ M → ∀ ρ' : Nat → Nat, (∀ i, i ∉ ${keysL(l)} → ρ' i = ρ i) → evalC ρ' M ${id}_law${j} = true :=
990
1223
  cyc_${q}machine_sound ${id}_machine ${id}_enc ${id}_facts ${id}_nodes ${id}_check ${id}_law${j} ${keysL(l)} ${node(j)} ${node(j)}_mem ${id}_law${j}_ok`).join('\n');
991
1224
  const keys2 = (l) => list((l.keys ?? []).map(k2 => String(ix(k2))));
@@ -1026,7 +1259,7 @@ def ${id}_step${j} : SKStep := ⟨⟨${list(kWhens(mq, sl).map(c))},
1026
1259
  ${list(mren)},
1027
1260
  [], [], [], [], [],
1028
1261
  ${list(names)}⟩
1029
- theorem ${id}_step${j}_ok : cyc${Q}KOk ${id}_machine ${id}_enc ${id}_facts ${id}_step${j} ${node(stepAt[j])} = true := by ${checkBy}
1262
+ theorem ${id}_step${j}_ok : cyc${Q}KOk ${id}_machine ${id}_enc ${id}_facts ${id}_step${j} ${node(stepAt[j])} = true := ${kOk}
1030
1263
  theorem ${id}_step${j}_holds : SKStepHolds ${id}_machine ${id}_step${j} :=
1031
1264
  ${kstep(`${id}_step${j}`, stepAt[j])} ${id}_step${j}_ok`;
1032
1265
  });
@@ -1044,7 +1277,7 @@ theorem ${lid}_allows : ∀ h ∈ ${lid}.helps, ∃ wit : List (Nat × E), disti
1044
1277
  intro h hh
1045
1278
  simp only [${lid}, List.mem_cons, List.not_mem_nil, or_false] at hh
1046
1279
  rcases hh with ${helps.map(() => 'rfl').join(' | ')}
1047
- ${al.map((a, i) => `· exact ⟨${lid}_wit${i}, by ${checkBy}, by ${checkBy}, cyc_${q}allow_sound ${id}_machine ${id}_enc ${id}_facts ${id}_nodes ${id}_check _ ${node(a.node)} ${node(a.node)}_mem (by ${checkBy})⟩`).join('\n ')}
1280
+ ${al.map((a, i) => `· exact ⟨${lid}_wit${i}, by ${checkBy}, by ${checkBy}, cyc_${q}allow_sound ${id}_machine ${id}_enc ${id}_facts ${id}_nodes ${id}_check _ ${node(a.node)} ${node(a.node)}_mem (${aOk})⟩`).join('\n ')}
1048
1281
  /-- ${l.name}, under weak fairness: along every run that takes each helpful request whenever, from some point on, it
1049
1282
  stays allowed, \`from\` is followed by \`to\`. -/
1050
1283
  theorem ${lid}_holds_wf (run : SRun ${id}_machine)
@@ -1056,13 +1289,13 @@ theorem ${lid}_holds_wf (run : SRun ${id}_machine)
1056
1289
  return `-- Liveness: ${l.name}
1057
1290
  def ${lid} : Live := ${liveLit(l, { e, c }, ix, primesL(), primeMapsL())}
1058
1291
  theorem ${lid}_ok : sliveOk ${id}_machine ${lid} = true := by ${checkBy}
1059
- theorem ${lid}_stay : SKStepHolds ${id}_machine (liveK ${lid} ${lid}.pend (liveStayC ${lid})) := ${kstep('_', s0)} (by ${checkBy})
1060
- theorem ${lid}_cover : SKStepHolds ${id}_machine (liveK ${lid} ${lid}.pend (liveCoverC ${lid})) := ${kstep('_', c0)} (by ${checkBy})
1292
+ theorem ${lid}_stay : SKStepHolds ${id}_machine (liveK ${lid} ${lid}.pend (liveStayC ${lid})) := ${kstep('_', s0)} (${kOk})
1293
+ theorem ${lid}_cover : SKStepHolds ${id}_machine (liveK ${lid} ${lid}.pend (liveCoverC ${lid})) := ${kstep('_', c0)} (${kOk})
1061
1294
  theorem ${lid}_helps : ∀ h ∈ ${lid}.helps, SKStepHolds ${id}_machine (liveK ${lid} (liveHelpWhen ${lid} h) (liveHelpThen ${lid})) := by
1062
1295
  intro h hh
1063
1296
  simp only [${lid}, List.mem_cons, List.not_mem_nil, or_false] at hh
1064
1297
  rcases hh with ${helps.map(() => 'rfl').join(' | ')}
1065
- ${helps.map((_, i) => `· exact ${kstep('_', hAt(i))} (by ${checkBy})`).join('\n ')}
1298
+ ${helps.map((_, i) => `· exact ${kstep('_', hAt(i))} (${kOk})`).join('\n ')}
1066
1299
  /-- ${l.name}: along every run that takes each helpful request infinitely often, \`from\` is followed by \`to\`. -/
1067
1300
  theorem ${lid}_holds (run : SRun ${id}_machine)
1068
1301
  (fair : ∀ h ∈ ${lid}.helps, ∀ n, ∃ m, n ≤ m ∧ ∀ p ∈ h.2, (run.req m).1 p.1 = evalE (run.st m).1 (run.st m).2 p.2) :
@@ -1079,7 +1312,7 @@ theorem ${id}_orig_laws : ∀ ρ M, SReach ${id}_ghosted.orig ρ M → ∀ e ∈
1079
1312
  ${laws.length ? `${oneOf('he')}
1080
1313
  rcases he with ${laws.map(() => 'rfl').join(' | ')}
1081
1314
  ${laws.map((l, j) => `· exact cyc_machine_holds (cyc_ghost_holds ${id}_ghosted ${id}_ghostOk ${id}_law${j} ${keysL(l)} hav ${id}_law${j}_holds ρ M hr)`).join('\n ')}` : `simp [${id}_mlaws] at he`}` : '';
1082
- const refineBlock = rf ? `theorem ${id}_allowed : SKStepHolds ${id}_machine (srStep ${id}_refined ${primesL()} ${primeMapsL()} noSProof) := ${kstep('_', stepAt[0])} (by ${checkBy})
1315
+ const refineBlock = rf ? `theorem ${id}_allowed : SKStepHolds ${id}_machine (srStep ${id}_refined ${primesL()} ${primeMapsL()} noSProof) := ${kstep('_', stepAt[0])} (${kOk})
1083
1316
  theorem ${id}_refines : ∀ ρ M, SReach ${id}_refined.impl ρ M → ∃ ρc Mc σ Ms, SReach ${id}_refined.full ρc Mc ∧ Agree ${id}_refined.SV ρc ρ
1084
1317
  ∧ (∀ mp, mp ∉ ${id}_refined.SM → Mc mp = M mp) ∧ SReach ${id}_refined.spec σ Ms ∧ Agree ${id}_refined.X σ ρc
1085
1318
  ∧ (∀ mp, mp ∉ ${id}_refined.IM → mp ∉ ${id}_refined.spec.carried → Ms mp = Mc mp) ∧ ∀ e ∈ ${id}_mlaws, holdsOver e.1 Mc e.2 ρc :=
@@ -1093,9 +1326,10 @@ theorem ${id}_refines : ∀ ρ M, SReach ${id}_refined.impl ρ M → ∃ ρc Mc
1093
1326
  ${machineDef}
1094
1327
  def ${id}_enc : CycEnc := ${enc}
1095
1328
  ${cycProofText(id, p, [], vars, maps)}
1329
+ ${okLemmas}
1096
1330
  ${lawBlocks}
1097
1331
  ${lawsThm}
1098
- ${hm ? histBlock() : readable(id, laws, vars, maps, { binders: '(ρ : Nat → Nat) (μ : Nat → Nat → Nat)', hyp: `SReach ${id}_machine ρ μ`, call: '',
1332
+ ${hm ? histBlock() : readable(id, laws, vars, maps, { binders: '(ρ : Nat → Nat) (μ : Nat → Nat → Nat)', hyp: `SReach ${id}_machine ρ μ`, call: '', lit: c, eLit: e,
1099
1333
  have: j => `have hl : holdsOver ${id}_l${j} μ ${keys2(laws[j])} ρ := cyc_machine_holds (${id}_law${j}_holds ρ μ h)` })}${opts.noFacts ? '' : factsProof(id, m, maps, vars, opts.simp)}${records.length ? `\n\n${records.join('\n\n')}` : ''}${opts.after ? `\n\n${opts.after(ix)}` : ''}` };
1100
1334
  }
1101
1335
  // A fold from its cyclic proof (Bridge.lean's cyc_fold_sound): the fold numbered as foldToLean numbers it, each law's
@@ -1395,6 +1629,7 @@ export function toLean(items, opts = {}) {
1395
1629
  certReport.usual = [];
1396
1630
  certReport.translatedSteps = [];
1397
1631
  cycTexts = 0;
1632
+ usualMade.clear();
1398
1633
  // The proofs found with only the instances their leaves use: the same laws, from fewer hypotheses for Lean to build.
1399
1634
  const wasMinimal = minimal.on;
1400
1635
  minimal.on = true;
@@ -1431,13 +1666,22 @@ function toLeanWith(items, opts) {
1431
1666
  return `${pairing}${text}\n`;
1432
1667
  }).join('');
1433
1668
  for (let item of items) {
1434
- const id = ident(item.id);
1669
+ const id = ident(item.id), tItem = performance.now();
1435
1670
  if ('machine' in item) {
1671
+ // Laws with "there exists" go in as the kernel proved them (core/exists.ts): the machine with the ghosts their
1672
+ // witnesses are, each law with its witness terms in place or over its ghosts, each stated again as ∃ (readable).
1673
+ if (item.laws.some(l => l.some?.length)) {
1674
+ const it = item, r = proveStepLaws(it.machine, it.laws, it.stepLaws ?? []);
1675
+ if (!r.proven || !r.laws)
1676
+ throw new Error(`${it.id}: not proven (${r.failed?.stuck ?? ''})`);
1677
+ const ghosts = [...(it.ghosts ?? []), ...Object.keys(r.machine?.state ?? {}).filter(n => !(n in it.machine.state))];
1678
+ item = { ...it, machine: r.machine ?? it.machine, laws: r.laws, ...(ghosts.length ? { ghosts } : {}) };
1679
+ }
1436
1680
  const m = item.machine;
1437
1681
  // Laws with some key go in as the prover proved them: with its witness in place (the stronger law).
1438
1682
  const someKey = (c) => JSON.stringify(c).includes('"op":"all"');
1439
1683
  if ([...item.laws.map(l => l.holds), ...(item.stepLaws ?? []).map(sl => sl.then)].some(someKey)) {
1440
- const r = proveStepLaws(m, item.laws, item.stepLaws ?? []);
1684
+ const r = proveStepLawsOnce(m, item.laws, item.stepLaws ?? []);
1441
1685
  if (!r.proven)
1442
1686
  throw new Error(`${item.id}: not proven (${r.failed?.stuck ?? ''})`);
1443
1687
  item = { ...item, laws: r.laws ?? item.laws, ...(item.stepLaws ? { stepLaws: r.stepLaws ?? item.stepLaws } : {}) };
@@ -1454,7 +1698,7 @@ function toLeanWith(items, opts) {
1454
1698
  else if (cyc)
1455
1699
  certReport.usual.push({ id: item.id, why: cyc.why });
1456
1700
  parts.push(`-- ${item.id}\n${defs}${cyc && 'text' in cyc ? cyc.text : smachineToLean(id, m, item.laws, mopts)}`);
1457
- theorems.push(`${id}_laws`, ...(m.history ? [`${id}_hlaws`] : []), ...(item.stepLaws ?? []).map((_, j) => `${id}_step${j}_holds`), ...(item.liveness ?? []).map((_, j) => `${id}_live${j}_holds`));
1701
+ theorems.push(`${id}_laws`, ...item.laws.flatMap((l, j) => (l.ex ? [`${id}.some${j}`] : [])), ...(m.history ? [`${id}_hlaws`] : []), ...(item.stepLaws ?? []).map((_, j) => `${id}_step${j}_holds`), ...(item.liveness ?? []).map((_, j) => `${id}_live${j}_holds`));
1458
1702
  if (item.lean?.after)
1459
1703
  theorems.push(...[...item.lean.after(() => 0).matchAll(/^theorem (\S+)/gm)].map(x2 => x2[1]));
1460
1704
  }
@@ -1506,10 +1750,12 @@ function toLeanWith(items, opts) {
1506
1750
  parts.push(goalToLean({ name: item.id, goal: item.goal, tree: grown(item.goal) }, n, false).replace(new RegExp(`\\bt${n}(_goal|_tree)?\\b`, 'g'), (_, suffix) => `${id}${suffix ?? ''}`));
1507
1751
  theorems.push(id);
1508
1752
  }
1753
+ if (envOf()?.GDP_LEAN_TIMES === '1')
1754
+ console.error(`lean item ${item.id}: ${Math.round(performance.now() - tItem)} ms, ${parts.at(-1)?.length ?? 0} bytes`);
1509
1755
  }
1510
1756
  // The file the items need: Live.lean for a liveness or a loop, Sets.lean (which imports Steps.lean) for any machine.
1511
1757
  return `import History
1512
- ${cycTexts ? 'import BridgeRules\n' : ''}${items.some(item => 'cyclic' in item) ? (items.some(item => 'cyclic' in item && (item.bridge || item.recur || item.fold)) ? 'import Bridge\n' : 'import Cyclic\n') : ''}${items.some(item => 'cyclic' in item && item.data) ? 'import Data\n' : ''}import ${items.some(item => 'refinement' in item) ? 'Refine' : items.some(item => 'machine' in item && item.ghosts?.length) ? 'Ghost' : items.some(item => ('machine' in item && item.liveness?.length) || 'loop' in item) ? 'Live' : items.some(item => 'machine' in item) ? 'Sets' : 'Steps'}
1758
+ ${cycTexts ? 'import BridgeRules\n' : ''}${items.some(item => 'cyclic' in item) ? (items.some(item => 'cyclic' in item && (item.bridge || item.recur || item.fold)) ? 'import Bridge\n' : 'import Cyclic\n') : ''}${items.some(item => 'cyclic' in item && item.data) ? 'import Data\n' : ''}import ${items.some(item => 'refinement' in item) ? 'Refine' : items.some(item => 'machine' in item && (item.ghosts?.length || item.laws.some(l => l.some?.length))) ? 'Ghost' : items.some(item => ('machine' in item && item.liveness?.length) || 'loop' in item) ? 'Live' : items.some(item => 'machine' in item) ? 'Sets' : 'Steps'}
1513
1759
  open Kern Cert
1514
1760
  set_option maxRecDepth 100000
1515
1761
  set_option maxHeartbeats 0
@@ -1558,10 +1804,27 @@ ${cycProofText(id, p, tampered, vars, maps)}${bridge ? `\n${cycBridgeLean(id, p,
1558
1804
  // node's membership, the check and cyclic_sound's theorem; each tampered proof's check computed to false.
1559
1805
  function cycProofText(id, p, tampered, vars, maps) {
1560
1806
  const { e, c } = leanTerms(vars, maps, id);
1807
+ // (data, not code: no compiled code for Lean to make, unless native_decide runs it)
1808
+ const d = checkBy === 'native_decide' ? 'def' : 'noncomputable def';
1561
1809
  const ix = (name) => vars.indexOf(name);
1562
1810
  const facts = list(p.facts.map(f => `⟨${list(f.keys.map(key => String(ix(key))))}, ${c(f.holds)}⟩`));
1563
1811
  // A tampered proof keeps the proof's tables and trees (made for the proof's own goals) under its changed nodes.
1564
- const enc = (g, tree) => { const x = encode({ name: `${id}: ${g.name}`, goal: g, tree }, false, true, vars, maps); return `(${x.tab}, ${x.tree})`; };
1812
+ // Each table once (`<id>_tabN`): a node's goals (its own, each use's premises and descent) share its hypotheses, so
1813
+ // most of their tables are the same columns, and Lean elaborates each list once instead of once per goal.
1814
+ // So too each term column's term (an open choice the tables share) and each goal's table and tree where two goals
1815
+ // have the same (a premise read at the same terms by two nodes).
1816
+ const shared = (prefix) => {
1817
+ const seen = new Map();
1818
+ return { seen, name: (text) => { let name = seen.get(text); if (!name)
1819
+ seen.set(text, (name = `${id}_${prefix}${seen.size}`)); return name; } };
1820
+ };
1821
+ const terms = shared('term'), tabs = shared('tab'), goals = shared('goal');
1822
+ const enc = (g, tree) => {
1823
+ const x = encode({ name: `${id}: ${g.name}`, goal: g, tree }, false, true, vars, maps, true);
1824
+ const tab = list(x.tabCols.map(col => (col.startsWith('.term (') && col.length > 48 ? `.term ${terms.name(col.slice(6))}` : col)));
1825
+ const pair = `(${tabs.name(tab)}, ${x.tree})`;
1826
+ return pair.length > 200 ? goals.name(pair) : pair;
1827
+ };
1565
1828
  const nodeLits = (q) => q.nodes.map((n, i) => {
1566
1829
  const o = p.nodes[i];
1567
1830
  const uses = n.uses.map((u, j) => {
@@ -1571,19 +1834,23 @@ function cycProofText(id, p, tampered, vars, maps) {
1571
1834
  return { hyps: list(n.hyps.map(c)), concl: c(n.concl), rest: `${list(n.rank.map(e))}, ${list(n.insts.map(t => `(${t.fact}, ${list(t.at.map(e))})`))},\n ${list(uses)},\n ${enc(mainGoal(p.facts, p.nodes, o), o.main)}` };
1572
1835
  });
1573
1836
  const nodes = (q) => list(nodeLits(q).map(x => `⟨${x.hyps}, ${x.concl}, ${x.rest}⟩`));
1574
- return `def ${id}_facts : List Fact := ${facts}
1575
- ${nodeLits(p).map((x, i) => `def ${id}_hyps${i} : List C := ${x.hyps}
1576
- def ${id}_concl${i} : C := ${x.concl}
1577
- def ${id}_node${i} : CNode := ⟨${id}_hyps${i}, ${id}_concl${i}, ${x.rest}⟩
1837
+ const own = nodeLits(p), bad = tampered.map(t => nodes(t.proof));
1838
+ return `${d} ${id}_facts : List Fact := ${facts}
1839
+ ${[...terms.seen].map(([t, name]) => `${d} ${name} : E := ${t}`).join('\n')}
1840
+ ${[...tabs.seen].map(([tab, name]) => `${d} ${name} : List VDef := ${tab}`).join('\n')}
1841
+ ${[...goals.seen].map(([g, name]) => `${d} ${name} : List VDef × Tree := ${g}`).join('\n')}
1842
+ ${own.map((x, i) => `${d} ${id}_hyps${i} : List C := ${x.hyps}
1843
+ ${d} ${id}_concl${i} : C := ${x.concl}
1844
+ ${d} ${id}_node${i} : CNode := ⟨${id}_hyps${i}, ${id}_concl${i}, ${x.rest}⟩
1578
1845
  theorem ${id}_node${i}_hyps : ${id}_node${i}.hyps = ${id}_hyps${i} := rfl
1579
1846
  theorem ${id}_node${i}_concl : ${id}_node${i}.concl = ${id}_concl${i} := rfl`).join('\n')}
1580
- def ${id}_nodes : List CNode := ${list(p.nodes.map((_, i) => `${id}_node${i}`))}
1847
+ ${d} ${id}_nodes : List CNode := ${list(p.nodes.map((_, i) => `${id}_node${i}`))}
1581
1848
  ${p.nodes.map((_, i) => `theorem ${id}_node${i}_mem : ${id}_node${i} ∈ ${id}_nodes := by simp only [${id}_nodes, List.mem_cons, true_or, or_true]`).join('\n')}
1582
1849
  theorem ${id}_check : checkCyclic ${id}_facts ${id}_nodes = true := by ${checkBy}
1583
1850
  theorem ${id}_sound : ∀ (maps : Nat → Nat → Nat) env, FactsHold ${id}_facts env maps → ∀ n ∈ ${id}_nodes, (∀ c ∈ n.hyps, evalC env maps c = true) → evalC env maps n.concl = true :=
1584
1851
  cyclic_sound ${id}_facts ${id}_nodes ${id}_check
1585
1852
  ${tampered.map((t, j) => `-- TAMPERED: ${t.what}. Lean computes the check to false.
1586
- def ${id}_tampered${j} : List CNode := ${nodes(t.proof)}
1853
+ ${d} ${id}_tampered${j} : List CNode := ${bad[j]}
1587
1854
  example : checkCyclic ${id}_facts ${id}_tampered${j} = false := by ${checkBy}`).join('\n')}`;
1588
1855
  }
1589
1856
  function cycScope(all, bridge, extra = []) {