gdp-wired 0.16.0 → 0.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,7 @@
1
1
  import type { C, E, Law, Machine, Recurrence } from '../core/machine.js';
2
2
  import { type Lin, type Step } from '../core/solver.js';
3
3
  import { type Fold, type Goal, type Instances, type Liveness, type Loop, type Path, type StepLaw, type Tree } from '../kernel/kernel.js';
4
+ import { type Cyclic } from '../kernel/cyclic.js';
4
5
  import { leanLibrary } from './library.js';
5
6
  export { leanLibrary };
6
7
  export type Exported = {
@@ -36,6 +37,7 @@ export declare function readable(id: string, laws: Law[], vars: string[], maps:
36
37
  binders: string;
37
38
  hyp: string;
38
39
  call: string;
40
+ have?: (j: number) => string;
39
41
  }): string;
40
42
  export type ProgressCase = {
41
43
  id: string;
@@ -60,6 +62,10 @@ export declare function mmachineToLean(id: string, m: Machine, laws: Law[], opts
60
62
  stepLaws?: StepLaw[];
61
63
  }): string;
62
64
  export declare const hasSets: (m: Machine) => boolean;
65
+ export declare function machineLit(mm: Machine, t: {
66
+ e: (x: E) => string;
67
+ c: (x: C) => string;
68
+ }, ix: (name: string) => number, mx: (name: string) => number): string;
63
69
  export declare function smachineToLean(id: string, given: Machine, laws: Law[], opts?: {
64
70
  stepLaws?: StepLaw[];
65
71
  liveness?: Liveness[];
@@ -143,6 +149,21 @@ export type LeanItem = {
143
149
  } | {
144
150
  id: string;
145
151
  goal: Goal;
152
+ } | {
153
+ id: string;
154
+ cyclic: Cyclic;
155
+ tampered?: {
156
+ what: string;
157
+ proof: Cyclic;
158
+ }[];
159
+ after?: (ix: (name: string) => number, mx: (map: string) => number) => string;
160
+ bridge?: CycBridge;
161
+ recur?: CycRecur;
162
+ fold?: {
163
+ fold: Fold;
164
+ laws: Law[];
165
+ };
166
+ data?: true;
146
167
  };
147
168
  export declare function toLean(items: LeanItem[], opts?: {
148
169
  title?: string;
@@ -151,3 +172,55 @@ export declare function toLean(items: LeanItem[], opts?: {
151
172
  export declare function leanProject(file: string, opts?: {
152
173
  name?: string;
153
174
  }): Record<string, string>;
175
+ export type CycBridge = {
176
+ machine: Machine;
177
+ laws: Law[];
178
+ tampered?: {
179
+ what: string;
180
+ machine: Machine;
181
+ }[];
182
+ };
183
+ export type CycRecur = {
184
+ name: string;
185
+ args: string[];
186
+ zero: E;
187
+ next: E;
188
+ lemmas: {
189
+ name: string;
190
+ holds: C;
191
+ }[];
192
+ between: {
193
+ name: string;
194
+ holds: C;
195
+ }[];
196
+ };
197
+ export declare function cyclicToLean(id: string, p: Cyclic, tampered?: {
198
+ what: string;
199
+ proof: Cyclic;
200
+ }[], after?: (ix: (name: string) => number, mx: (map: string) => number) => string, bridge?: CycBridge, recur?: CycRecur, fold?: {
201
+ fold: Fold;
202
+ laws: Law[];
203
+ }): string;
204
+ export declare function cycEncodingLean(id: string, facts: {
205
+ name: string;
206
+ keys: string[];
207
+ holds: C;
208
+ }[], nodes: {
209
+ name: string;
210
+ hyps: C[];
211
+ concl: C;
212
+ }[], b: CycBridge): string;
213
+ export declare function cycRecurCheckLean(id: string, facts: {
214
+ name: string;
215
+ keys: string[];
216
+ holds: C;
217
+ }[], r: CycRecur): string;
218
+ export declare function cycFoldCheckLean(id: string, facts: {
219
+ name: string;
220
+ keys: string[];
221
+ holds: C;
222
+ }[], nodes: {
223
+ name: string;
224
+ hyps: C[];
225
+ concl: C;
226
+ }[], f: Fold, laws: Law[]): string;
@@ -1,9 +1,10 @@
1
1
  import { atMost, n, not, pointWrite } from "../core/machine.js";
2
2
  import { add, k, scale, sub } from "../core/solver.js";
3
3
  import { boundOf, canon, factorsOf, isCell, isMonomial, columnTerm, stateName, termColumn, historyMachine, foldObligations, leafConstraints, loopLiveness, loopMachine, linear, livenessHelpful, livenessParts, livenessRanks, loopObligations, trueC, obligations, progressObligations, stepLawObligations, substC, witnessesOf, withProducts } from "../kernel/kernel.js";
4
- import { grow, proveBuilt, proveLaws, proveStepLaws } from "../kernel/prove.js";
4
+ import { grow, minimal, proveBuilt, proveLaws, proveStepLaws } from "../kernel/prove.js";
5
5
  import { refinement, refinementSides } from "../core/compose.js";
6
6
  import { leanSurface, leanTerms } from "./lean-terms.js";
7
+ import { descentGoal, mainGoal, premiseGoal } from "../kernel/cyclic.js";
7
8
  import { leanLibrary } from "./library.js";
8
9
  export { leanLibrary };
9
10
  // --- to Lean ------------------------------------------------------------------------------------------------------------
@@ -130,12 +131,24 @@ export function encode(ex, tamper = false, dropStep = true, globalVars, globalMa
130
131
  // monomial with a cell among its factors after the cells.
131
132
  const degree = (a, b) => factorsOf(a).length - factorsOf(b).length;
132
133
  const plainMonos = [...monos].filter(m => factorsOf(m).every(f => !isCell(f))).sort(degree), cellMonos = [...monos].filter(m => factorsOf(m).some(isCell)).sort(degree);
133
- const cols = [...vars, ...plainMonos, ...[...terms].sort((a, b) => Number(a.slice(1, -1)) - Number(b.slice(1, -1))), ...[...cells].sort((a, b) => depth(a) - depth(b)), ...cellMonos];
134
+ // A cell whose key reads a product of cells (a cyclic proof's map at u + k·Ω, its key k read from a map over time)
135
+ // comes after that product, and the product after its cells: each column placed after the columns it reads.
136
+ const late = [], placed = new Set();
137
+ const place = (name) => {
138
+ if (placed.has(name))
139
+ return;
140
+ placed.add(name);
141
+ for (const d of isMonomial(name) ? factorsOf(name) : Object.keys(cellKey(name)))
142
+ if (cells.has(d) || cellMonos.includes(d))
143
+ place(d);
144
+ late.push(name);
145
+ };
146
+ for (const cell of [...cells].sort((a, b) => depth(a) - depth(b)))
147
+ place(cell);
148
+ for (const mono of cellMonos)
149
+ place(mono);
150
+ const cols = [...vars, ...plainMonos, ...[...terms].sort((a, b) => Number(a.slice(1, -1)) - Number(b.slice(1, -1))), ...late];
134
151
  const col = new Map(cols.map((name, j) => [name, j]));
135
- for (const cell of cells)
136
- for (const key of Object.keys(cellKey(cell)))
137
- if (cellMonos.includes(key))
138
- throw new Error(`${ex.name}: the key of ${cell} reads a product of cells`);
139
152
  const maps = globalMaps ?? [...new Set([...[...cells].map(cellMap), ...goal.hyps.flatMap(h => [...JSON.stringify(h).matchAll(/"op":"get","map":"([^"]+)"/g)].map(m => m[1]))])];
140
153
  const dense = (lin) => {
141
154
  const coefs = new Array(cols.length).fill(0);
@@ -320,6 +333,7 @@ theorem ${id}_laws : ∀ maps ρ, evalC ρ maps ${id}_fold.pre = true → ∀ t,
320
333
  // Readable corollaries: each law as a plain Lean statement over named variables (ρ the state, μ the maps, each name an
321
334
  // abbreviation for its column), proven from the machine's theorem by unfolding the law's own definition. The statement
322
335
  // is what a Lean development cites; the column numbers stay inside the namespace.
336
+ // `have`: the line that gives each law as a hypothesis hl (an SEntry's Holds by default; a cyclic proof's, holdsOver).
323
337
  export function readable(id, laws, vars, maps, reach) {
324
338
  for (const name of [...vars, ...maps])
325
339
  if (name === 'ρ' || name === 'μ' || name.includes('«') || name.includes('»'))
@@ -362,8 +376,8 @@ export function readable(id, laws, vars, maps, reach) {
362
376
  ${[...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')}
363
377
  ${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, '- /')} -/
364
378
  theorem law${j} ${reach.binders} (h : ${reach.hyp}) : ${statement(law)} := by
365
- ${law.keys?.length ? `have hl : ${id}_e${j}.Holds ρ μ := ${reach.call} ${id}_e${j} (by simp [${id}_proof])
366
- 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)` : `have hl : ${id}_l${j}.Holds ρ μ := ${reach.call} ${id}_e${j} (by simp [${id}_proof])
379
+ ${law.keys?.length ? `${reach.have ? reach.have(j) : `have hl : ${id}_e${j}.Holds ρ μ := ${reach.call} ${id}_e${j} (by simp [${id}_proof])`}
380
+ 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])`}
367
381
  ${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')}
368
382
  end ${id}`;
369
383
  }
@@ -510,6 +524,25 @@ const boundKeys = (m) => [...Object.values(m.step.flags ?? {}).flatMap(c => (c.o
510
524
  // the relations assumed), built in Lean from the machine and its history (History.lean's HMachine.plain), so every claim
511
525
  // below is about it; with the relations' record goals, its laws hold over histories too (hmachine_sound).
512
526
  // `historyTampers`: the history with other relations, each refused.
527
+ // A machine as Sets.lean's SMachine literal: its variables and maps numbered by `ix` and `mx`, its terms by `t`.
528
+ export function machineLit(mm, t, ix, mx) {
529
+ const { e, c } = t;
530
+ const fl = flagsOfM(mm), fs = Object.entries(mm.step.flags ?? {}), ws = Object.entries(mm.step.maps ?? {});
531
+ return `⟨⟨⟨${list(mm.inputs.map(name => String(ix(name))))},
532
+ ${list(Object.entries(mm.state).map(([name, e2]) => `(${ix(name)}, ${e(e2)})`))},
533
+ ${list(Object.entries(mm.maps ?? {}).map(([name, c2]) => `(${mx(name)}, ${c2})`))},
534
+ ${list(Object.entries(mm.step.state ?? {}).map(([name, e2]) => `(${ix(name)}, ${e(e2)})`))},
535
+ ${list(ws.flatMap(([name, w]) => ('each' in w ? [] : [`(${mx(name)}, ${e(w.key)}, ${e(w.value)})`])))},
536
+ ${mm.pre ? c(mm.pre) : TRUE},
537
+ ${mm.assume ? c(mm.assume) : TRUE}⟩,
538
+ ${list(fs.flatMap(([f, c2]) => (c2.op === 'all' ? [] : [`(${ix(`?${f}`)}, ${c(fl[f])})`])))}⟩,
539
+ ${list((mm.inputMaps ?? []).map(mp => String(mx(mp))))},
540
+ ${list(ws.flatMap(([name, w]) => ('each' in w ? [`(${mx(name)}, ${ix(w.each)}, ${e(w.value)})`] : [])))},
541
+ ${list(fs.flatMap(([f, c2]) => (c2.op === 'all' ? [`(${ix(`?${f}`)}, ${list(boundOf(c2).map(key => String(ix(key))))}, ${c(c2.c)})`] : [])))},
542
+ ${list(fs.flatMap(([f, c2]) => (c2.op === 'all' ? [`(${ix(`?${f}`)}, ${list(witnessesOf(f, c2).map(w => String(ix(w))))})`] : [])))},
543
+ ${list((mm.facts ?? []).filter(l => !l.keys?.length).map(l => `⟨${c(l.holds)}, ${l.forall ? `some ${ix(l.forall)}` : 'none'}⟩`))},
544
+ ${list((mm.facts ?? []).filter(l => l.keys?.length).map(l => `(⟨${c(l.holds)}, some ${ix(l.forall)}⟩, ${list(l.keys.map(k2 => String(ix(k2))))})`))}⟩`;
545
+ }
513
546
  export function smachineToLean(id, given, laws, opts = {}) {
514
547
  const hm = given.history ? { ...given, history: { ...given.history, prune: undefined } } : undefined;
515
548
  const hplain = hm ? historyMachine(hm) : undefined, stepLaws = opts.stepLaws ?? [], rf = opts.refined, gv = opts.ghosts ?? [];
@@ -697,23 +730,7 @@ theorem ${id}_orig_laws : ∀ ρ M, SReach ${id}_ghosted.orig ρ M → ∀ e ∈
697
730
  const mx = (name) => { const j = mapNames.indexOf(name); if (j < 0)
698
731
  throw new Error(`${id}: no map for ${name}`); return j; };
699
732
  const inline = (c2) => substC(c2, { vars: {}, maps: {}, flags: flagsOfM(m) });
700
- const lit = (mm) => {
701
- const fl = flagsOfM(mm), fs = Object.entries(mm.step.flags ?? {}), ws = Object.entries(mm.step.maps ?? {});
702
- return `⟨⟨⟨${list(mm.inputs.map(name => String(ix(name))))},
703
- ${list(Object.entries(mm.state).map(([name, e2]) => `(${ix(name)}, ${e(e2)})`))},
704
- ${list(Object.entries(mm.maps ?? {}).map(([name, c2]) => `(${mx(name)}, ${c2})`))},
705
- ${list(Object.entries(mm.step.state ?? {}).map(([name, e2]) => `(${ix(name)}, ${e(e2)})`))},
706
- ${list(ws.flatMap(([name, w]) => ('each' in w ? [] : [`(${mx(name)}, ${e(w.key)}, ${e(w.value)})`])))},
707
- ${mm.pre ? c(mm.pre) : TRUE},
708
- ${mm.assume ? c(mm.assume) : TRUE}⟩,
709
- ${list(fs.flatMap(([f, c2]) => (c2.op === 'all' ? [] : [`(${ix(`?${f}`)}, ${c(fl[f])})`])))}⟩,
710
- ${list((mm.inputMaps ?? []).map(mp => String(mx(mp))))},
711
- ${list(ws.flatMap(([name, w]) => ('each' in w ? [`(${mx(name)}, ${ix(w.each)}, ${e(w.value)})`] : [])))},
712
- ${list(fs.flatMap(([f, c2]) => (c2.op === 'all' ? [`(${ix(`?${f}`)}, ${list(boundOf(c2).map(key => String(ix(key))))}, ${c(c2.c)})`] : [])))},
713
- ${list(fs.flatMap(([f, c2]) => (c2.op === 'all' ? [`(${ix(`?${f}`)}, ${list(witnessesOf(f, c2).map(w => String(ix(w))))})`] : [])))},
714
- ${list((mm.facts ?? []).filter(l => !l.keys?.length).map(l => `⟨${c(l.holds)}, ${l.forall ? `some ${ix(l.forall)}` : 'none'}⟩`))},
715
- ${list((mm.facts ?? []).filter(l => l.keys?.length).map(l => `(⟨${c(l.holds)}, some ${ix(l.forall)}⟩, ${list(l.keys.map(k2 => String(ix(k2))))})`))}⟩`;
716
- };
733
+ const lit = (mm) => machineLit(mm, { e, c }, ix, mx);
717
734
  // A loop as Live.lean's record: inputs, start, body, while, precondition, assumption.
718
735
  const loopLit = (lp) => `⟨${list(lp.inputs.map(name => String(ix(name))))},
719
736
  ${list(Object.entries(lp.state).map(([name, e2]) => `(${ix(name)}, ${e(e2)})`))},
@@ -1055,11 +1072,15 @@ export function toLean(items, opts = {}) {
1055
1072
  const outer = checkBy;
1056
1073
  const env = globalThis.process?.env;
1057
1074
  checkBy = (opts.native ?? env?.GDP_LEAN_NATIVE === '1') ? 'native_decide' : 'decide +kernel';
1075
+ // The proofs found with only the instances their leaves use: the same laws, from fewer hypotheses for Lean to build.
1076
+ const wasMinimal = minimal.on;
1077
+ minimal.on = true;
1058
1078
  try {
1059
1079
  return toLeanWith(items, opts);
1060
1080
  }
1061
1081
  finally {
1062
1082
  checkBy = outer;
1083
+ minimal.on = wasMinimal;
1063
1084
  }
1064
1085
  }
1065
1086
  function toLeanWith(items, opts) {
@@ -1119,6 +1140,12 @@ function toLeanWith(items, opts) {
1119
1140
  parts.push(`-- ${item.id}\n${loopToLean(id, item.loop, item.laws ?? [], item.rank)}`);
1120
1141
  theorems.push(`${id}_halts`);
1121
1142
  }
1143
+ else if ('cyclic' in item) {
1144
+ parts.push(`-- ${item.id}\n${cyclicToLean(id, item.cyclic, item.tampered ?? [], item.after, item.bridge, item.recur, item.fold)}`);
1145
+ theorems.push(`${id}_sound`, ...(item.bridge?.laws ?? item.fold?.laws ?? []).map((_, j) => `${id}_law${j}_holds`), ...(item.recur ? [`${id}_values`] : []), ...(item.recur ? [...item.recur.lemmas.map((_, j) => `${id}_lemma${j}`), ...item.recur.between.map((_, j) => `${id}_between${j}`)] : []),
1146
+ // A datatype law's theorem is in a namespace of the item's (its datatypes and functions are named as declared).
1147
+ ...(item.after ? (ts => (item.data ? ts.slice(-1).map(t => `${id}.${t}`) : ts))([...item.after(() => 0, () => 0).matchAll(/^theorem (\S+)/gm)].map(x2 => x2[1])) : []));
1148
+ }
1122
1149
  else if ('fold' in item) {
1123
1150
  parts.push(`-- ${item.id}\n${foldToLean(id, item.fold, item.laws)}`);
1124
1151
  theorems.push(`${id}_laws`);
@@ -1132,7 +1159,7 @@ function toLeanWith(items, opts) {
1132
1159
  }
1133
1160
  // The file the items need: Live.lean for a liveness or a loop, Sets.lean (which imports Steps.lean) for any machine.
1134
1161
  return `import History
1135
- 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'}
1162
+ ${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'}
1136
1163
  open Kern Cert
1137
1164
  set_option maxRecDepth 100000
1138
1165
  set_option maxHeartbeats 0
@@ -1164,3 +1191,200 @@ lean_lib ${name} where
1164
1191
  `,
1165
1192
  };
1166
1193
  }
1194
+ export function cyclicToLean(id, p, tampered = [], after, bridge, recur, fold) {
1195
+ const all = [p, ...tampered.map(t => t.proof)];
1196
+ const v = (name) => ({ op: 'var', name });
1197
+ const fnames = fold ? Object.keys(fold.fold.leaf) : [];
1198
+ const { vars, maps } = cycScope(all, bridge, [...(recur ? [recur.zero, recur.next, v('x'), v('prev'), v('k$'), ...recur.args.map(v)] : []),
1199
+ ...(fold ? [...fold.fold.params, ...fold.fold.inputs, ...fnames, ...fnames.map(x => `l_${x}`), ...fnames.map(x => `r_${x}`), 'n$', 'N$'].map(v) : []),
1200
+ ...(fold ? [...Object.values(fold.fold.leaf), ...Object.values(fold.fold.node), ...fold.laws.map((l) => ({ op: 'if', c: l.holds, then: v('n$'), else: v('n$') }))] : []),
1201
+ ...(fold ? ['Kind', 'Left', 'Right', ...fnames, ...fold.fold.inputs].map((x) => ({ op: 'get', map: `${x}@`, key: v('n$') })) : [])]);
1202
+ const { e, c } = leanTerms(vars, maps, id);
1203
+ const ix = (name) => vars.indexOf(name);
1204
+ const facts = list(p.facts.map(f => `⟨${list(f.keys.map(key => String(ix(key))))}, ${c(f.holds)}⟩`));
1205
+ // A tampered proof keeps the proof's tables and trees (made for the proof's own goals) under its changed nodes.
1206
+ const enc = (g, tree) => { const x = encode({ name: `${id}: ${g.name}`, goal: g, tree }, false, true, vars, maps); return `(${x.tab}, ${x.tree})`; };
1207
+ const nodeLits = (q) => q.nodes.map((n, i) => {
1208
+ const o = p.nodes[i];
1209
+ const uses = n.uses.map((u, j) => {
1210
+ const ou = o.uses[j], om = p.nodes[ou.node];
1211
+ return `⟨${u.node}, ${list(Object.entries(u.subst).map(([name, t]) => `(${ix(name)}, ${e(t)})`))}, ${c(u.guard)},\n ${list(om.hyps.map((h, k2) => enc(premiseGoal(p.facts, o, ou, h, k2), ou.premises[k2])))},\n ${enc(descentGoal(p.facts, o, ou, om), ou.descent)}⟩`;
1212
+ });
1213
+ 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)}` };
1214
+ });
1215
+ const nodes = (q) => list(nodeLits(q).map(x => `⟨${x.hyps}, ${x.concl}, ${x.rest}⟩`));
1216
+ const mx = (map) => maps.indexOf(map);
1217
+ return `-- ${p.nodes.map(n => n.name).join('; ')} (variables ${vars.map((name, j) => `${j} ${name}`).join(', ')}; maps ${maps.map((name, j) => `${j} ${name}`).join(', ')})
1218
+ def ${id}_facts : List Fact := ${facts}
1219
+ ${nodeLits(p).map((x, i) => `def ${id}_hyps${i} : List C := ${x.hyps}
1220
+ def ${id}_concl${i} : C := ${x.concl}
1221
+ def ${id}_node${i} : CNode := ⟨${id}_hyps${i}, ${id}_concl${i}, ${x.rest}⟩
1222
+ theorem ${id}_node${i}_hyps : ${id}_node${i}.hyps = ${id}_hyps${i} := rfl
1223
+ theorem ${id}_node${i}_concl : ${id}_node${i}.concl = ${id}_concl${i} := rfl`).join('\n')}
1224
+ def ${id}_nodes : List CNode := ${list(p.nodes.map((_, i) => `${id}_node${i}`))}
1225
+ ${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')}
1226
+ theorem ${id}_check : checkCyclic ${id}_facts ${id}_nodes = true := by ${checkBy}
1227
+ 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 :=
1228
+ cyclic_sound ${id}_facts ${id}_nodes ${id}_check
1229
+ ${tampered.map((t, j) => `-- TAMPERED: ${t.what}. Lean computes the check to false.
1230
+ def ${id}_tampered${j} : List CNode := ${nodes(t.proof)}
1231
+ example : checkCyclic ${id}_facts ${id}_tampered${j} = false := by ${checkBy}`).join('\n')}${bridge ? `\n${cycBridgeLean(id, p, bridge, vars, maps, { e, c })}` : ''}${recur ? `\n${cycRecurLean(id, p, recur, vars, maps, { e, c })}` : ''}${fold ? `\n${cycFoldLean(id, fold.fold, fold.laws, vars, maps, { e, c })}` : ''}${after ? `\n${after(ix, mx)}` : ''}`;
1232
+ }
1233
+ function cycScope(all, bridge, extra = []) {
1234
+ const names = (t) => [...JSON.stringify(t).matchAll(/"op":"var","name":"([^"]+)"/g)].map(m => m[1]);
1235
+ const ms = bridge ? [bridge.machine, ...(bridge.tampered ?? []).map(t => t.machine)] : [];
1236
+ const keysOf = (l) => [...(l.forall ? [l.forall] : []), ...(l.keys ?? [])];
1237
+ const traced = (m) => [...Object.keys(m.state), ...m.inputs], tracedMaps = (m) => [...Object.keys(m.maps ?? {}), ...(m.inputMaps ?? [])];
1238
+ const machineVars = ms.flatMap(m => [...m.params, ...m.inputs, ...Object.keys(m.state), ...Object.keys(m.step.flags ?? {}).map(f => `?${f}`),
1239
+ ...Object.values(m.step.maps ?? {}).flatMap(w => ('each' in w ? [w.each] : [])), ...(m.facts ?? []).flatMap(keysOf), ...names([m.state, m.step, m.pre, m.assume, m.facts])]);
1240
+ const vars = [...new Set([...all.flatMap(q => [...q.facts.flatMap(f => [...f.keys, ...names(f.holds)]), ...q.nodes.flatMap(n => [...names([n.hyps, n.concl, n.rank, n.insts]), ...n.uses.flatMap(u => [...Object.keys(u.subst), ...names([u.subst, u.guard])])])]),
1241
+ ...machineVars, ...(bridge ? [...bridge.laws.flatMap(l => [...keysOf(l), ...names(l.holds)]), 'u$', 'k$', 'T$', 'O$', 't$'] : []), ...names(extra)])];
1242
+ const maps = [...new Set([...[...JSON.stringify([all, ms, bridge?.laws ?? []]).matchAll(/"op":"get","map":"([^"]+)"/g)].map(m => m[1]),
1243
+ ...ms.flatMap(m => [...tracedMaps(m), ...Object.keys(m.fns ?? {}), ...traced(m).map(x => `${x}@`), ...tracedMaps(m).map(x => `${x}@`)])])];
1244
+ return { vars, maps };
1245
+ }
1246
+ // Bridge.lean's cycMOk alone, part by part, for each law of a machine (the sweep research/cyclic/bridge.ts runs over the
1247
+ // examples): the facts and nodes machineNodes makes, each node with no proof (cycMOk reads only its hypotheses and
1248
+ // conclusion), against the machine's encoding. Each part an example, named in a comment before it.
1249
+ export function cycEncodingLean(id, facts, nodes, b) {
1250
+ const shape = { facts, nodes: nodes.map(n => ({ hyps: n.hyps, concl: n.concl, rank: [], insts: [], uses: [] })) };
1251
+ const { vars, maps } = cycScope([shape], b);
1252
+ const t = leanTerms(vars, maps, id);
1253
+ const ix = (name) => { const j = vars.indexOf(name); if (j < 0)
1254
+ throw new Error(`${id}: no variable for ${name}`); return j; };
1255
+ const mx = (name) => { const j = maps.indexOf(name); if (j < 0)
1256
+ throw new Error(`${id}: no map for ${name}`); return j; };
1257
+ const m = b.machine, keysOf = (l) => list([...(l.forall ? [l.forall] : []), ...(l.keys ?? [])].map(k2 => String(ix(k2))));
1258
+ const by = checkBy;
1259
+ return `-- ${id}: ${m.name}
1260
+ def ${id}_machine : SMachine := ${machineLit(m, t, ix, mx)}
1261
+ def ${id}_enc : CycEnc := ⟨${['u$', 'k$', 'T$', 'O$', 't$'].map(ix).join(', ')},
1262
+ ${list([...Object.keys(m.state), ...m.inputs].map(x => `(${ix(x)}, ${mx(`${x}@`)})`))},
1263
+ ${list([...Object.keys(m.maps ?? {}), ...(m.inputMaps ?? [])].map(x => `(${mx(x)}, ${mx(`${x}@`)})`))}⟩
1264
+ ${facts.map((f, i) => `-- CHECK ${id} fact ${i}: ${f.name}
1265
+ example : (natC (${t.c(f.holds)}) || (cycCanon ${id}_machine ${id}_enc).any (factBeq ⟨${list(f.keys.map(k2 => String(ix(k2))))}, ${t.c(f.holds)}⟩)) = true := by ${by}`).join('\n')}
1266
+ ${b.laws.map((l, j) => `-- CHECK ${id} law ${j}: shape: ${l.name}
1267
+ example : cycShapeOk ${id}_machine ${id}_enc ${keysOf(l)} = true := by ${by}
1268
+ -- CHECK ${id} law ${j}: terms: ${l.name}
1269
+ example : cycTermsOk ${id}_machine ${id}_enc ${keysOf(l)} = true := by ${by}
1270
+ -- CHECK ${id} law ${j}: law: ${l.name}
1271
+ example : cycLawOk ${id}_machine ${id}_enc (${t.c(l.holds)}) = true := by ${by}
1272
+ -- CHECK ${id} law ${j}: node: ${l.name}
1273
+ example : cycNodeOk ${id}_machine ${id}_enc (${t.c(l.holds)}) ⟨${list(nodes[j].hyps.map(t.c))}, ${t.c(nodes[j].concl)}, [], [], [], ([], .leaf [] [] [] [])⟩ = true := by ${by}`).join('\n')}`;
1274
+ }
1275
+ // Bridge.lean's recOk and foldOk alone, for the sweep (research/cyclic/bridge.ts): recurNodes' and foldNodes' facts (and
1276
+ // a fold's nodes, with no proof: foldOk reads only their hypotheses and conclusions) against the recurrence's equations
1277
+ // and the fold's table. Each an example, named in a comment before it.
1278
+ export function cycRecurCheckLean(id, facts, r) {
1279
+ const v = (name) => ({ op: 'var', name });
1280
+ const { vars, maps } = cycScope([{ facts, nodes: [] }], undefined, [r.zero, r.next, v('x'), v('prev'), v('k$'), ...r.args.map(v), { op: 'get', map: `${r.name}$`, key: v('x') }]);
1281
+ const t = leanTerms(vars, maps, id), ix = (n2) => vars.indexOf(n2), mx = (n2) => maps.indexOf(n2);
1282
+ return `-- CHECK ${id} recurrence: ${r.name}
1283
+ example : recOk ⟨${mx(`${r.name}$`)}, ${ix('x')}, ${ix('prev')}, ${ix('k$')}⟩ (${t.e(r.zero)}) (${t.e(r.next)}) []
1284
+ ${list(facts.map(f => `⟨${list(f.keys.map(k2 => String(ix(k2))))}, ${t.c(f.holds)}⟩`))} = true := by ${checkBy}`;
1285
+ }
1286
+ export function cycFoldCheckLean(id, facts, nodes, f, laws) {
1287
+ const v = (name) => ({ op: 'var', name }), st = Object.keys(f.leaf);
1288
+ const shape = { facts, nodes: nodes.map(n => ({ hyps: n.hyps, concl: n.concl, rank: [], insts: [], uses: [] })) };
1289
+ const { vars, maps } = cycScope([shape], undefined, [...[...f.params, ...f.inputs, ...st, ...st.map(x => `l_${x}`), ...st.map(x => `r_${x}`), 'n$', 'N$'].map(v),
1290
+ ...Object.values(f.leaf), ...Object.values(f.node), ...['Kind', 'Left', 'Right', ...st, ...f.inputs].map((x) => ({ op: 'get', map: `${x}@`, key: v('n$') }))]);
1291
+ const t = leanTerms(vars, maps, id);
1292
+ return `${cycFoldLean(id, f, [], vars, maps, t)}
1293
+ def ${id}_facts : List Fact := ${list(facts.map(g => `⟨${list(g.keys.map(k2 => String(vars.indexOf(k2))))}, ${t.c(g.holds)}⟩`))}
1294
+ ${laws.map((l, j) => `-- CHECK ${id} fold law ${j}: ${l.name}
1295
+ example : foldOk ${id}_fold ${id}_fenc ${id}_facts (${t.c(l.holds)}) ⟨${list(nodes[j].hyps.map(t.c))}, ${t.c(nodes[j].concl)}, [], [], [], ([], .leaf [] [] [] [])⟩ = true := by ${checkBy}`).join('\n')}`;
1296
+ }
1297
+ // Bridge.lean's cyc_fold_sound for each law of a cyclic proof made by foldNodes: the fold as Kernel.lean's Fold, the
1298
+ // table (its added variables and maps), each law's check and its theorem over trees, as fold_sound states the usual one.
1299
+ function cycFoldLean(id, f, laws, vars, maps, t) {
1300
+ const ix = (name) => { const j = vars.indexOf(name); if (j < 0)
1301
+ throw new Error(`${id}: no variable for ${name}`); return j; };
1302
+ const mx = (name) => { const j = maps.indexOf(name); if (j < 0)
1303
+ throw new Error(`${id}: no map for ${name}`); return j; };
1304
+ const st = Object.keys(f.leaf);
1305
+ return `def ${id}_fold : Fold := ⟨${list(f.inputs.map(name => String(ix(name))))},
1306
+ ${list(st.map(n2 => `(${ix(n2)}, ${ix(`l_${n2}`)}, ${ix(`r_${n2}`)})`))},
1307
+ ${list(Object.entries(f.leaf).map(([name, e2]) => `(${ix(name)}, ${t.e(e2)})`))},
1308
+ ${list(Object.entries(f.node).map(([name, e2]) => `(${ix(name)}, ${t.e(e2)})`))},
1309
+ ${f.pre ? t.c(f.pre) : TRUE},
1310
+ ${f.assume ? t.c(f.assume) : TRUE}⟩
1311
+ def ${id}_fenc : FoldEnc := ⟨${ix('n$')}, ${ix('N$')}, ${mx('Kind@')}, ${mx('Left@')}, ${mx('Right@')}, ${list([...st, ...f.inputs].map(x => `(${ix(x)}, ${mx(`${x}@`)})`))}⟩
1312
+ ${laws.map((l, j) => `-- ${l.name}
1313
+ def ${id}_law${j} : C := ${t.c(l.holds)}
1314
+ theorem ${id}_law${j}_ok : foldOk ${id}_fold ${id}_fenc ${id}_facts ${id}_law${j} ${id}_node${j} = true := by ${checkBy}
1315
+ theorem ${id}_law${j}_holds : ∀ (maps : Nat → Nat → Nat) ρ, evalC ρ maps ${id}_fold.pre = true → ∀ t, Allowed ${id}_fold ρ maps t →
1316
+ evalC (foldT ${id}_fold ρ maps t) maps ${id}_law${j} = true :=
1317
+ cyc_fold_sound ${id}_fold ${id}_fenc ${id}_facts ${id}_nodes ${id}_check ${id}_law${j} ${id}_node${j} ${id}_node${j}_mem ${id}_law${j}_ok`).join('\n')}`;
1318
+ }
1319
+ // Bridge.lean's cyc_recur_sound for a cyclic proof made by recurNodes: the recurrence's equations checked against the
1320
+ // proof's facts, the nodes of its values (recF), its Lean definition (as recurToLean writes it) shown to be those values,
1321
+ // and each lemma and law between two points plainly of the definition.
1322
+ function cycRecurLean(id, p, r, vars, maps, t) {
1323
+ const ix = (name) => { const j = vars.indexOf(name); if (j < 0)
1324
+ throw new Error(`${id}: no variable for ${name}`); return j; };
1325
+ const mx = (name) => { const j = maps.indexOf(name); if (j < 0)
1326
+ throw new Error(`${id}: no map for ${name}`); return j; };
1327
+ const fn = `${id}_fn`, args = r.args.map(recurArg), binders = args.map(a => `(${a} : Nat)`).join(' ');
1328
+ const call = (x) => `(${[fn, ...args, x].join(' ')})`;
1329
+ const surface = (names) => leanSurface({ ...Object.fromEntries(r.args.map((a, i) => [a, args[i]])), ...names }, id);
1330
+ const renc = `⟨${mx(`${r.name}$`)}, ${ix('x')}, ${ix('prev')}, ${ix('k$')}⟩`;
1331
+ const recF = `recF ${id}_zero ${id}_next ${ix('x')} ${ix('prev')}`;
1332
+ const envAt = (pts) => `(fun i => ${[...pts, ...r.args.map((a, i) => [a, args[i]])].map(([v, val]) => `if i = ${ix(v)} then ${val} else `).join('')}0)`;
1333
+ const node = (j) => `${id}_node${j}`;
1334
+ const n = r.lemmas.length;
1335
+ return `def ${id}_renc : RecEnc := ${renc}
1336
+ def ${id}_zero : E := ${t.e(r.zero)}
1337
+ def ${id}_next : E := ${t.e(r.next)}
1338
+ theorem ${id}_rok : recOk ${id}_renc ${id}_zero ${id}_next [] ${id}_facts = true := by ${checkBy}
1339
+ theorem ${id}_values : ∀ maps env, ∀ n ∈ ${id}_nodes, (∀ c ∈ n.hyps, evalC env (withMap maps ${mx(`${r.name}$`)} (${recF} env maps)) c = true) →
1340
+ evalC env (withMap maps ${mx(`${r.name}$`)} (${recF} env maps)) n.concl = true :=
1341
+ fun maps env => cyc_recur_sound ${id}_renc ${id}_zero ${id}_next [] ${id}_facts ${id}_nodes ${id}_check ${id}_rok maps env (by intro f hf; simp at hf)
1342
+ -- ${r.name} as Lean defines it (recurToLean's recursion), and the same as the values the proof is about.
1343
+ def ${fn} ${binders} : Nat → Nat
1344
+ | 0 => ${surface({}).e(r.zero)}
1345
+ | x + 1 => ${surface({ x: 'x', prev: call('x') }).e(r.next)}
1346
+ theorem ${fn}_eq (maps : Nat → Nat → Nat) (env : Nat → Nat) : ∀ n, ${recF} env maps n = ${[fn, ...r.args.map(a => `(env ${ix(a)})`)].join(' ')} n := by
1347
+ intro n
1348
+ induction n with
1349
+ | zero => simp [recF, ${id}_zero, ${fn}, evalE]
1350
+ | succ n ih => simp only [recF]; rw [ih]; simp [${id}_next, ${fn}, evalE, upd]
1351
+ ${r.lemmas.map((l, j) => `/-- ${r.name}: ${l.name} -/
1352
+ theorem ${id}_lemma${j} ${binders} (x : Nat) : ${surface({ x: 'x', fx: call('x') }).c(l.holds)} := by
1353
+ have h := ${id}_values (fun _ _ => 0) ${envAt([['x', 'x']])} _ ${node(j)}_mem (by rw [${node(j)}_hyps]; simp [${id}_hyps${j}])
1354
+ rw [${node(j)}_concl] at h
1355
+ simpa [${id}_concl${j}, evalC, evalE, withMap, ${fn}_eq] using h`).join('\n')}
1356
+ ${r.between.map((b, j) => `/-- ${r.name}: ${b.name}, for every x ≤ y -/
1357
+ theorem ${id}_between${j} ${binders} (x y : Nat) (hxy : x ≤ y) : ${surface({ x: 'x', fx: call('x'), y: 'y', fy: call('y') }).c(b.holds)} := by
1358
+ have h := ${id}_values (fun _ _ => 0) ${envAt([['x', 'x'], ['y', 'y']])} _ ${node(n + j)}_mem (by rw [${node(n + j)}_hyps]; simp [${id}_hyps${n + j}])
1359
+ rw [${node(n + j)}_concl] at h
1360
+ simp [${id}_concl${n + j}, evalC, evalE, withMap, ${fn}_eq] at h
1361
+ first | exact h hxy | omega | exact h.resolve_left (by omega)`).join('\n')}`;
1362
+ }
1363
+ // Bridge.lean's cyc_machine_sound for each law of a cyclic proof made by machineNodes: the machine as Sets.lean's
1364
+ // SMachine, the encoding (its added variables and maps over time), each law's check and theorem, and the laws plainly.
1365
+ function cycBridgeLean(id, p, b, vars, maps, t) {
1366
+ const ix = (name) => { const j = vars.indexOf(name); if (j < 0)
1367
+ throw new Error(`${id}: no variable for ${name}`); return j; };
1368
+ const mx = (name) => { const j = maps.indexOf(name); if (j < 0)
1369
+ throw new Error(`${id}: no map for ${name}`); return j; };
1370
+ if (b.laws.length > p.nodes.length)
1371
+ throw new Error(`${id}: more laws than nodes`);
1372
+ const keysOf = (l) => [...(l.forall ? [l.forall] : []), ...(l.keys ?? [])];
1373
+ const enc = (m) => `⟨${['u$', 'k$', 'T$', 'O$', 't$'].map(ix).join(', ')},
1374
+ ${list([...Object.keys(m.state), ...m.inputs].map(x => `(${ix(x)}, ${mx(`${x}@`)})`))},
1375
+ ${list([...Object.keys(m.maps ?? {}), ...(m.inputMaps ?? [])].map(x => `(${mx(x)}, ${mx(`${x}@`)})`))}⟩`;
1376
+ const keysL = (l) => list(keysOf(l).map(k2 => String(ix(k2))));
1377
+ return `def ${id}_machine : SMachine := ${machineLit(b.machine, t, ix, mx)}
1378
+ def ${id}_enc : CycEnc := ${enc(b.machine)}
1379
+ ${b.laws.map((l, j) => `-- ${l.name}
1380
+ def ${id}_law${j} : C := ${t.c(l.holds)}
1381
+ def ${id}_l${j} : MLaw := ⟨${id}_law${j}, ${l.forall ? `some ${ix(l.forall)}` : 'none'}⟩
1382
+ theorem ${id}_law${j}_ok : cycMOk ${id}_machine ${id}_enc ${id}_facts ${id}_law${j} ${keysL(l)} ${id}_node${j} = true := by ${checkBy}
1383
+ theorem ${id}_law${j}_holds : ∀ ρ M, SReach ${id}_machine ρ M → ∀ ρ' : Nat → Nat, (∀ i, i ∉ ${keysL(l)} → ρ' i = ρ i) → evalC ρ' M ${id}_law${j} = true :=
1384
+ cyc_machine_sound ${id}_machine ${id}_enc ${id}_facts ${id}_nodes ${id}_check ${id}_law${j} ${keysL(l)} ${id}_node${j} ${id}_node${j}_mem ${id}_law${j}_ok`).join('\n')}
1385
+ ${(b.tampered ?? []).map((tm, j) => `-- TAMPERED: ${tm.what}. Lean computes the check against the proof to false.
1386
+ def ${id}_tamperedm${j} : SMachine := ${machineLit(tm.machine, t, ix, mx)}
1387
+ example : cycMOk ${id}_tamperedm${j} ${id}_enc ${id}_facts ${id}_law0 ${keysL(b.laws[0])} ${id}_node0 = false := by ${checkBy}`).join('\n')}
1388
+ ${readable(id, b.laws, vars, maps, { binders: '(ρ : Nat → Nat) (μ : Nat → Nat → Nat)', hyp: `SReach ${id}_machine ρ μ`, call: '',
1389
+ have: j => `have hl : holdsOver ${id}_l${j} μ ${list((b.laws[j].keys ?? []).map(k2 => String(ix(k2))))} ρ := cyc_machine_holds (${id}_law${j}_holds ρ μ h)` })}`;
1390
+ }