gdp-wired 0.17.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.
@@ -131,12 +131,24 @@ export function encode(ex, tamper = false, dropStep = true, globalVars, globalMa
131
131
  // monomial with a cell among its factors after the cells.
132
132
  const degree = (a, b) => factorsOf(a).length - factorsOf(b).length;
133
133
  const plainMonos = [...monos].filter(m => factorsOf(m).every(f => !isCell(f))).sort(degree), cellMonos = [...monos].filter(m => factorsOf(m).some(isCell)).sort(degree);
134
- 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];
135
151
  const col = new Map(cols.map((name, j) => [name, j]));
136
- for (const cell of cells)
137
- for (const key of Object.keys(cellKey(cell)))
138
- if (cellMonos.includes(key))
139
- throw new Error(`${ex.name}: the key of ${cell} reads a product of cells`);
140
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]))])];
141
153
  const dense = (lin) => {
142
154
  const coefs = new Array(cols.length).fill(0);
@@ -321,6 +333,7 @@ theorem ${id}_laws : ∀ maps ρ, evalC ρ maps ${id}_fold.pre = true → ∀ t,
321
333
  // Readable corollaries: each law as a plain Lean statement over named variables (ρ the state, μ the maps, each name an
322
334
  // abbreviation for its column), proven from the machine's theorem by unfolding the law's own definition. The statement
323
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).
324
337
  export function readable(id, laws, vars, maps, reach) {
325
338
  for (const name of [...vars, ...maps])
326
339
  if (name === 'ρ' || name === 'μ' || name.includes('«') || name.includes('»'))
@@ -363,8 +376,8 @@ export function readable(id, laws, vars, maps, reach) {
363
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')}
364
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, '- /')} -/
365
378
  theorem law${j} ${reach.binders} (h : ${reach.hyp}) : ${statement(law)} := by
366
- ${law.keys?.length ? `have hl : ${id}_e${j}.Holds ρ μ := ${reach.call} ${id}_e${j} (by simp [${id}_proof])
367
- 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])`}
368
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')}
369
382
  end ${id}`;
370
383
  }
@@ -511,6 +524,25 @@ const boundKeys = (m) => [...Object.values(m.step.flags ?? {}).flatMap(c => (c.o
511
524
  // the relations assumed), built in Lean from the machine and its history (History.lean's HMachine.plain), so every claim
512
525
  // below is about it; with the relations' record goals, its laws hold over histories too (hmachine_sound).
513
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
+ }
514
546
  export function smachineToLean(id, given, laws, opts = {}) {
515
547
  const hm = given.history ? { ...given, history: { ...given.history, prune: undefined } } : undefined;
516
548
  const hplain = hm ? historyMachine(hm) : undefined, stepLaws = opts.stepLaws ?? [], rf = opts.refined, gv = opts.ghosts ?? [];
@@ -698,23 +730,7 @@ theorem ${id}_orig_laws : ∀ ρ M, SReach ${id}_ghosted.orig ρ M → ∀ e ∈
698
730
  const mx = (name) => { const j = mapNames.indexOf(name); if (j < 0)
699
731
  throw new Error(`${id}: no map for ${name}`); return j; };
700
732
  const inline = (c2) => substC(c2, { vars: {}, maps: {}, flags: flagsOfM(m) });
701
- const lit = (mm) => {
702
- const fl = flagsOfM(mm), fs = Object.entries(mm.step.flags ?? {}), ws = Object.entries(mm.step.maps ?? {});
703
- return `⟨⟨⟨${list(mm.inputs.map(name => String(ix(name))))},
704
- ${list(Object.entries(mm.state).map(([name, e2]) => `(${ix(name)}, ${e(e2)})`))},
705
- ${list(Object.entries(mm.maps ?? {}).map(([name, c2]) => `(${mx(name)}, ${c2})`))},
706
- ${list(Object.entries(mm.step.state ?? {}).map(([name, e2]) => `(${ix(name)}, ${e(e2)})`))},
707
- ${list(ws.flatMap(([name, w]) => ('each' in w ? [] : [`(${mx(name)}, ${e(w.key)}, ${e(w.value)})`])))},
708
- ${mm.pre ? c(mm.pre) : TRUE},
709
- ${mm.assume ? c(mm.assume) : TRUE}⟩,
710
- ${list(fs.flatMap(([f, c2]) => (c2.op === 'all' ? [] : [`(${ix(`?${f}`)}, ${c(fl[f])})`])))}⟩,
711
- ${list((mm.inputMaps ?? []).map(mp => String(mx(mp))))},
712
- ${list(ws.flatMap(([name, w]) => ('each' in w ? [`(${mx(name)}, ${ix(w.each)}, ${e(w.value)})`] : [])))},
713
- ${list(fs.flatMap(([f, c2]) => (c2.op === 'all' ? [`(${ix(`?${f}`)}, ${list(boundOf(c2).map(key => String(ix(key))))}, ${c(c2.c)})`] : [])))},
714
- ${list(fs.flatMap(([f, c2]) => (c2.op === 'all' ? [`(${ix(`?${f}`)}, ${list(witnessesOf(f, c2).map(w => String(ix(w))))})`] : [])))},
715
- ${list((mm.facts ?? []).filter(l => !l.keys?.length).map(l => `⟨${c(l.holds)}, ${l.forall ? `some ${ix(l.forall)}` : 'none'}⟩`))},
716
- ${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))))})`))}⟩`;
717
- };
733
+ const lit = (mm) => machineLit(mm, { e, c }, ix, mx);
718
734
  // A loop as Live.lean's record: inputs, start, body, while, precondition, assumption.
719
735
  const loopLit = (lp) => `⟨${list(lp.inputs.map(name => String(ix(name))))},
720
736
  ${list(Object.entries(lp.state).map(([name, e2]) => `(${ix(name)}, ${e(e2)})`))},
@@ -1125,8 +1141,10 @@ function toLeanWith(items, opts) {
1125
1141
  theorems.push(`${id}_halts`);
1126
1142
  }
1127
1143
  else if ('cyclic' in item) {
1128
- parts.push(`-- ${item.id}\n${cyclicToLean(id, item.cyclic, item.tampered ?? [], item.after)}`);
1129
- theorems.push(`${id}_sound`, ...(item.after ? [...item.after(() => 0, () => 0).matchAll(/^theorem (\S+)/gm)].map(x2 => x2[1]) : []));
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])) : []));
1130
1148
  }
1131
1149
  else if ('fold' in item) {
1132
1150
  parts.push(`-- ${item.id}\n${foldToLean(id, item.fold, item.laws)}`);
@@ -1141,7 +1159,7 @@ function toLeanWith(items, opts) {
1141
1159
  }
1142
1160
  // The file the items need: Live.lean for a liveness or a loop, Sets.lean (which imports Steps.lean) for any machine.
1143
1161
  return `import History
1144
- ${items.some(item => 'cyclic' in item) ? 'import Cyclic\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'}
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'}
1145
1163
  open Kern Cert
1146
1164
  set_option maxRecDepth 100000
1147
1165
  set_option maxHeartbeats 0
@@ -1173,14 +1191,14 @@ lean_lib ${name} where
1173
1191
  `,
1174
1192
  };
1175
1193
  }
1176
- // A cyclic proof through Kern.cyclic_sound: the facts and the nodes, each goal's table and tree (Lean builds the goals
1177
- // from the nodes), one theorem for every node; each tampered proof's check computed to false. `after` writes Lean
1178
- // after it (a corollary in plain terms), given the variables' and maps' numbers.
1179
- export function cyclicToLean(id, p, tampered = [], after) {
1194
+ export function cyclicToLean(id, p, tampered = [], after, bridge, recur, fold) {
1180
1195
  const all = [p, ...tampered.map(t => t.proof)];
1181
- const names = (t) => [...JSON.stringify(t).matchAll(/"op":"var","name":"([^"]+)"/g)].map(m => m[1]);
1182
- 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])])])]))];
1183
- const maps = [...new Set([...JSON.stringify(all).matchAll(/"op":"get","map":"([^"]+)"/g)].map(m => m[1]))];
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$') })) : [])]);
1184
1202
  const { e, c } = leanTerms(vars, maps, id);
1185
1203
  const ix = (name) => vars.indexOf(name);
1186
1204
  const facts = list(p.facts.map(f => `⟨${list(f.keys.map(key => String(ix(key))))}, ${c(f.holds)}⟩`));
@@ -1210,5 +1228,163 @@ theorem ${id}_sound : ∀ (maps : Nat → Nat → Nat) env, FactsHold ${id}_fact
1210
1228
  cyclic_sound ${id}_facts ${id}_nodes ${id}_check
1211
1229
  ${tampered.map((t, j) => `-- TAMPERED: ${t.what}. Lean computes the check to false.
1212
1230
  def ${id}_tampered${j} : List CNode := ${nodes(t.proof)}
1213
- example : checkCyclic ${id}_facts ${id}_tampered${j} = false := by ${checkBy}`).join('\n')}${after ? `\n${after(ix, mx)}` : ''}`;
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)` })}`;
1214
1390
  }