gdp-wired 0.8.0 → 0.10.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.
@@ -92,6 +92,7 @@ export declare function smachineToLean(id: string, given: Machine, laws: Law[],
92
92
  loop: Loop;
93
93
  };
94
94
  };
95
+ noFacts?: boolean;
95
96
  }): string;
96
97
  type Uses = Record<string, {
97
98
  lean?: string;
@@ -134,6 +135,10 @@ export type LeanItem = {
134
135
  loop: Loop;
135
136
  rank: E | E[];
136
137
  laws?: Law[];
138
+ } | {
139
+ id: string;
140
+ fold: Fold;
141
+ laws: Law[];
137
142
  } | {
138
143
  id: string;
139
144
  goal: Goal;
@@ -475,7 +475,7 @@ const boundKeys = (m) => [...Object.values(m.step.flags ?? {}).flatMap(c => (c.o
475
475
  export function smachineToLean(id, given, laws, opts = {}) {
476
476
  const hm = given.history ? { ...given, history: { ...given.history, prune: undefined } } : undefined;
477
477
  const hplain = hm ? historyMachine(hm) : undefined, stepLaws = opts.stepLaws ?? [], rf = opts.refined, gv = opts.ghosts ?? [];
478
- const { machine: m, origins } = specializeFacts(hplain ? hplain.machine : given, laws, stepLaws);
478
+ const m = hplain ? hplain.machine : given;
479
479
  const claims = [];
480
480
  const claimSteps = () => claims.flatMap(cl => cl.steps);
481
481
  // The machine's own step laws, one step or k (Sets.lean's skstep_sound).
@@ -497,6 +497,7 @@ def ${id}_step${j} : SKStep := ⟨⟨${list(Array.from({ length: kk }, (_, s2) =
497
497
  ${list(qinsts(inst))},
498
498
  ${list(finsts(inst))},
499
499
  ${list(pinsts(inst))},
500
+ ${list(pfinsts(inst))},
500
501
  [],
501
502
  ${list(Array.from({ length: kk - 1 }, (_, s2) => list(Object.keys(m.state).map(n2 => `(${ix(n2)}, ${ix(stateName(n2, s2 + 1))})`))))}⟩
502
503
  theorem ${id}_step${j}_holds : SKStepHolds ${id}_machine ${id}_step${j} :=
@@ -608,7 +609,15 @@ theorem ${id}_orig_laws : ∀ ρ M, SReach ${id}_ghosted.orig ρ M → ∀ e ∈
608
609
  const factsF = m.facts ?? [];
609
610
  const base = [m.pre ?? trueC,
610
611
  ...factsF.filter(f => !f.forall).map(f => f.holds),
611
- ...Object.entries(inst).flatMap(([name, keys]) => { const f = factsF.find(f2 => f2.name === name); return f?.forall ? keys.map(key => sub1(f.holds, { [f.forall]: key })) : []; }),
612
+ ...Object.entries(inst).flatMap(([name, keys]) => { const f = factsF.find(f2 => f2.name === name); return f?.forall && !f.keys?.length ? keys.map(key => sub1(f.holds, { [f.forall]: key })) : []; }),
613
+ // A fact about several keys at each tuple (kept flattened), as Lean reads it: the further keys, then the first.
614
+ ...Object.entries(inst).flatMap(([name, keys]) => {
615
+ const f = factsF.find(f2 => f2.name === name);
616
+ if (!f?.keys?.length)
617
+ return [];
618
+ const k = f.keys.length + 1;
619
+ return Array.from({ length: Math.floor(keys.length / k) }, (_, t) => sub1(sub1(f.holds, Object.fromEntries(f.keys.map((kn, i) => [kn, keys[t * k + 1 + i]]))), { [f.forall]: keys[t * k] }));
620
+ }),
612
621
  ...laws.filter(lw => !lw.forall).map(lw => lw.holds),
613
622
  ...Object.entries(inst).flatMap(([name, keys]) => { const lw = laws.find(l2 => l2.name === name); return lw?.forall && !lw.keys?.length ? keys.map(key => sub1(lw.holds, { [lw.forall]: key })) : []; }),
614
623
  ...Object.entries(inst).flatMap(([name, keys]) => {
@@ -658,7 +667,8 @@ theorem ${id}_orig_laws : ∀ ρ M, SReach ${id}_ghosted.orig ρ M → ∀ e ∈
658
667
  ${list(ws.flatMap(([name, w]) => ('each' in w ? [`(${mx(name)}, ${ix(w.each)}, ${e(w.value)})`] : [])))},
659
668
  ${list(fs.flatMap(([f, c2]) => (c2.op === 'all' ? [`(${ix(`?${f}`)}, ${ix(c2.key)}, ${c(c2.c)})`] : [])))},
660
669
  ${list(fs.flatMap(([f, c2]) => (c2.op === 'all' ? [`(${ix(`?${f}`)}, ${ix(`w?${f}`)})`] : [])))},
661
- ${list((mm.facts ?? []).map(l => `⟨${c(l.holds)}, ${l.forall ? `some ${ix(l.forall)}` : 'none'}⟩`))}⟩`;
670
+ ${list((mm.facts ?? []).filter(l => !l.keys?.length).map(l => `⟨${c(l.holds)}, ${l.forall ? `some ${ix(l.forall)}` : 'none'}⟩`))},
671
+ ${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))))})`))}⟩`;
662
672
  };
663
673
  // A loop as Live.lean's record: inputs, start, body, while, precondition, assumption.
664
674
  const loopLit = (lp) => `⟨${list(lp.inputs.map(name => String(ix(name))))},
@@ -700,12 +710,21 @@ ${readable(id, laws, vars, mapNames, { binders: '(ρ : Nat → Nat) (μ : Nat
700
710
  const n = laws[lawIx(name)].keys.length + 1;
701
711
  return Array.from({ length: Math.floor(keys.length / n) }, (_, t) => `(${lawIx(name)}, ${e(keys[t * n])}, ${list(keys.slice(t * n + 1, t * n + n).map(e))})`);
702
712
  });
703
- const finsts = (inst) => Object.entries(inst).flatMap(([name, keys]) => { const j = facts.findIndex(f => f.name === name); return j < 0 ? [] : keys.map(key => `(${j}, ${e(key)})`); });
713
+ // The facts about one key, and those about several (each instance a tuple, kept flattened: its first key, the rest).
714
+ const facts1 = facts.filter(f => !f.keys?.length), factsN = facts.filter(f => f.keys?.length);
715
+ const finsts = (inst) => Object.entries(inst).flatMap(([name, keys]) => { const j = facts1.findIndex(f => f.name === name); return j < 0 ? [] : keys.map(key => `(${j}, ${e(key)})`); });
716
+ const pfinsts = (inst) => Object.entries(inst).flatMap(([name, keys]) => {
717
+ const j = factsN.findIndex(f => f.name === name);
718
+ if (j < 0)
719
+ return [];
720
+ const n = factsN[j].keys.length + 1;
721
+ return Array.from({ length: Math.floor(keys.length / n) }, (_, t) => `(${j}, ${e(keys[t * n])}, ${list(keys.slice(t * n + 1, t * n + n).map(e))})`);
722
+ });
704
723
  const qinsts = (inst) => Object.entries(inst).filter(([name]) => name.startsWith('?')).flatMap(([f, keys]) => keys.map(key => `(${ix(f)}, ${e(key)})`));
705
724
  const entries = laws.map((law, j) => {
706
725
  const g0 = goalsL[2 * j], g1 = goalsL[2 * j + 1], a = enc(g0, lp.trees[g0.name]), b = enc(g1, lp.trees[g1.name]), inst = lp.instances[law.name] ?? {};
707
726
  return `def ${id}_l${j} : MLaw := ⟨${c(law.holds)}, ${law.forall ? `some ${ix(law.forall)}` : 'none'}⟩
708
- def ${id}_e${j} : SEntry := ⟨${id}_l${j}, ${ix(law.forall ? `${law.forall}#` : '#')}, ${list((law.keys ?? []).map(key => String(ix(key))))}, ${list(insts(inst))}, ${list(qinsts(inst))}, ${list(finsts(inst))}, ${list(pinsts(inst))},
727
+ def ${id}_e${j} : SEntry := ⟨${id}_l${j}, ${ix(law.forall ? `${law.forall}#` : '#')}, ${list((law.keys ?? []).map(key => String(ix(key))))}, ${list(insts(inst))}, ${list(qinsts(inst))}, ${list(finsts(inst))}, ${list(pinsts(inst))}, ${list(pfinsts(inst))},
709
728
  (${a.tab}, [], ${a.tree}),
710
729
  (${b.tab}, [], ${b.tree})⟩`;
711
730
  });
@@ -714,13 +733,13 @@ def ${id}_e${j} : SEntry := ⟨${id}_l${j}, ${ix(law.forall ? `${law.forall}#` :
714
733
  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))) } };
715
734
  const h = {
716
735
  step: sl => { const g = goalsS[allSteps.indexOf(sl)]; return { pr: enc(g, r.proof.trees[g.name]), inst: instOf(sl) }; },
717
- sproof: sl => { const { pr, inst } = h.step(sl); return `(${list(insts(inst))}, ${list(qinsts(inst))}, ${list(finsts(inst))}, ${list(pinsts(inst))}, (${pr.tab}, [], ${pr.tree}))`; },
736
+ sproof: sl => { const { pr, inst } = h.step(sl); return `(${list(insts(inst))}, ${list(qinsts(inst))}, ${list(finsts(inst))}, ${list(pinsts(inst))}, ${list(pfinsts(inst))}, (${pr.tab}, [], ${pr.tree}))`; },
718
737
  allow: a => {
719
738
  const f2 = allowed.get(a);
720
739
  if (!f2)
721
740
  return undefined;
722
741
  const ar = f2.out, pr = enc(ar.goal, ar.tree), inst = ar.instances;
723
- return `(${list(f2.wit.map(([inp, t]) => `(${ix(inp)}, ${e(t)})`))}, (${list(insts(inst))}, ${list(qinsts(inst))}, ${list(finsts(inst))}, ${list(pinsts(inst))}, (${pr.tab}, ${list(ar.goal.hyps.map(c))}, ${pr.tree})), ${list(ar.goal.zkeys.map(e))})`;
742
+ return `(${list(f2.wit.map(([inp, t]) => `(${ix(inp)}, ${e(t)})`))}, (${list(insts(inst))}, ${list(qinsts(inst))}, ${list(finsts(inst))}, ${list(pinsts(inst))}, ${list(pfinsts(inst))}, (${pr.tab}, ${list(ar.goal.hyps.map(c))}, ${pr.tree})), ${list(ar.goal.zkeys.map(e))})`;
724
743
  },
725
744
  };
726
745
  const records = claims.map(cl => cl.render(h)).filter(t => t.length);
@@ -741,48 +760,11 @@ def ${id}_proof : List SEntry := ${list(laws.map((_, j) => `${id}_e${j}`))}
741
760
  theorem ${id}_check : checkSMachine ${id}_machine ${id}_proof = true := by decide +kernel
742
761
  theorem ${id}_laws : ∀ ρ M, SReach ${id}_machine ρ M → ∀ e ∈ ${id}_proof, e.Holds ρ M :=
743
762
  smachine_sound ${id}_machine ${id}_proof ${id}_check
744
- ${hm ? histBlock() : readable(id, laws, vars, mapNames, { binders: '(ρ : Nat → Nat) (μ : Nat → Nat → Nat)', hyp: `SReach ${id}_machine ρ μ`, call: `${id}_laws ρ μ h` })}${factsProof(id, m, mapNames, vars, opts.simp, origins)}${opts.tamper ? `
763
+ ${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 ? `
745
764
  -- TAMPERED: ${opts.tamper.what}. Lean computes the check to false.
746
765
  def ${id}_tampered : SMachine := ${lit(opts.tamper.machine)}
747
766
  example : checkSMachine ${id}_tampered ${id}_proof = false := by decide +kernel` : ''}${records.length ? `\n\n${records.join('\n\n')}` : ''}${opts.after ? `\n\n${opts.after(ix)}` : ''}`;
748
767
  }
749
- function specializeFacts(m, laws, stepLaws) {
750
- const origins = new Map(), facts = m.facts ?? [];
751
- if (!facts.some(f => f.keys?.length))
752
- return { machine: m, origins };
753
- const r = proveStepLaws(m, laws, stepLaws);
754
- if (!r.proven || !r.proof)
755
- throw new Error(`${m.name}: not proven (${r.failed?.stuck ?? ''})`);
756
- const all = [...Object.values(r.proof.laws.instances), r.proof.instances, ...Object.values(r.proof.perStep ?? {})];
757
- const overParams = (t) => varsOf({ op: '=', a: t, b: t }).every(x => m.params.includes(x)) && !JSON.stringify(t).includes('"op":"get"');
758
- const out = [];
759
- for (const f of facts) {
760
- if (!f.keys?.length) {
761
- out.push(f);
762
- continue;
763
- }
764
- const vars = [f.forall, ...f.keys], seen = new Set();
765
- for (const inst of all) {
766
- const keys = inst[f.name] ?? [];
767
- for (let t = 0; t + vars.length <= keys.length; t += vars.length) {
768
- const tuple = keys.slice(t, t + vars.length), open = tuple.map((e2, i) => [e2, i]).filter(([e2]) => !overParams(e2)).map(([, i]) => i);
769
- // A tuple with two keys over the state is left out (the prover offers more instances than a proof uses): if the
770
- // proof needs it, proving the machine again without it fails, and says so.
771
- if (open.length > 1)
772
- continue;
773
- const left = open[0] ?? 0, at = Object.fromEntries(vars.flatMap((x, i) => (i === left ? [] : [[x, tuple[i]]])));
774
- const id = JSON.stringify([left, at]);
775
- if (seen.has(id))
776
- continue;
777
- seen.add(id);
778
- const name = `${f.name} #${seen.size}`;
779
- out.push({ name, forall: vars[left], holds: substC(f.holds, { vars: at, maps: {}, flags: {} }) });
780
- origins.set(name, { from: f, at });
781
- }
782
- }
783
- }
784
- return { machine: { ...m, facts: out }, origins };
785
- }
786
768
  // A function defined by a recurrence (core/dsl.ts recur): its Lean definition by the same recurrence, and its lemmas
787
769
  // proven together by induction (each case by omega, its min, max and ifs split first), and its laws between two points
788
770
  // by induction on the distance. A function of several arguments is `<name>_rec`, its further arguments first, and the
@@ -838,6 +820,64 @@ const useLemmas = (uses, t, names, where) => {
838
820
  walk(t);
839
821
  return [...new Set(out)];
840
822
  };
823
+ // The kernel's own proof of a recurrence's lemmas (or of its laws between two points), for Lean to check: the machine
824
+ // it was proven by, exported with its trees (smachine_sound gives its laws at every reachable state), and the run of
825
+ // that machine that reaches x and f(x) (or y = x + d and f(y) from x and f(x)), so each law, read there, is the lemma.
826
+ // The proof needs no tactic beyond simp's arithmetic of the machine's terms: what the kernel proved (products of two
827
+ // terms among it) Lean checks as the kernel's own tree.
828
+ function recurKernel(lean, r, uses, which) {
829
+ const pr = r.proofs?.[which];
830
+ if (!pr)
831
+ return undefined;
832
+ const m = pr.machine, two = which === 'between', id = `${lean.replace(/[^A-Za-z0-9_]/g, '_')}_${two ? 'two' : 'ind'}`;
833
+ const vars = [...m.params, ...m.inputs, ...Object.keys(m.state)], col = (n) => { const j = vars.indexOf(n); if (j < 0)
834
+ throw new Error(`${id}: no column for ${n}`); return j; };
835
+ const fnNames = Object.keys(m.fns ?? {}), args = r.args.map(recurArg), argNames = Object.fromEntries(r.args.map(a => [a, recurArg(a)]));
836
+ const F = (x) => recurCall(lean, r, x);
837
+ const surface = leanSurface(argNames, lean, useRead(uses, lean));
838
+ const binders = [...r.args.map(a => `(${recurArg(a)} : Nat)`), ...(two ? ['(x : Nat)'] : [])].join(' '), ps = [...args, ...(two ? ['x'] : [])].join(' ');
839
+ const start = [...r.args.map(a => [a, recurArg(a)]), ...(m.params.includes('fx0') ? [['fx0', surface.e(r.zero)]] : []),
840
+ ...(two ? [['x', 'x'], ['fx', F('x')]] : [])];
841
+ const chain = (pairs, dflt, v) => pairs.reduceRight((acc, [c, e]) => `if ${v} = ${c} then ${e} else ${acc}`, dflt);
842
+ // What the run holds after n steps, column by column.
843
+ const at = (n) => two
844
+ ? [[col('y'), `x + ${n}`], [col('fy'), F(`(x + ${n})`)], [col('x'), 'x'], [col('fx'), F('x')], ...r.args.map(a => [col(a), recurArg(a)])]
845
+ : [[col('x'), n], [col('fx'), F(n)], ...r.args.map(a => [col(a), recurArg(a)])];
846
+ const hs = at('n').map((_, i) => `h${i}`);
847
+ const recurDefs = [r.args.length ? `${lean}_rec` : lean, ...Object.values(uses).flatMap(u => (u.recur ? (u.recur.args.length ? [`${u.lean}_rec`, `${u.lean}_key`] : [u.lean]) : []))];
848
+ const one = (m.facts ?? []).some(f => !f.keys?.length), several = (m.facts ?? []).some(f => f.keys?.length);
849
+ // A fact of the params at the start of the laws between: the lemma at x and f(x), its own conjunct.
850
+ const proj = (j, k) => (k === 1 ? '' : j < k - 1 ? `${'.2'.repeat(j)}.1` : '.2'.repeat(k - 1));
851
+ const cite = (f) => { const j = r.lemmas.findIndex(l => f.name === `${l.name} at x`); return two && j >= 0 ? `(${lean}_lemmas ${args.join(' ')} x)${proj(j, r.lemmas.length)}` : undefined; };
852
+ const factsAt = factsProof(id, m, fnNames, vars, [`${id}_ρ`, `${id}_μ`, ...recurDefs], { binders, rho: `(${id}_ρ ${ps})`, mu: `${id}_μ`, cite });
853
+ const unfold = [`${id}_machine`, `${id}_μ`, 'applyS', 'assoc', 'lookup', 'evalE', 'evalC', ...recurDefs].join(', ');
854
+ const pre = `
855
+ ${smachineToLean(id, m, pr.laws, { noFacts: true })}
856
+ def ${id}_ρ ${binders} : Nat → Nat := fun i => ${chain(start.map(([n, e]) => [col(n), e]), '0', 'i')}
857
+ noncomputable def ${id}_μ : Nat → Nat → Nat := fun j => ${chain(fnNames.map((n, j) => [j, `(${m.fns[n].lean})`]), 'fun _ => 0', 'j')}${factsAt}
858
+ -- The run of ${m.name} that ${two ? 'counts y up from x' : 'counts x up from 0'}: reachable, and holding ${two ? 'y = x + n and f(y)' : 'x = n and f(x)'}.
859
+ theorem ${id}_run ${binders} : ∀ n, ∃ ρ, SReach ${id}_machine ρ ${id}_μ ∧ ${at('n').map(([c, e]) => `ρ ${c} = ${e}`).join(' ∧ ')} := by
860
+ intro n
861
+ induction n with
862
+ | zero =>
863
+ refine ⟨_, SReach.start (${id}_ρ ${ps}) ${id}_μ ?_ ?_ ?_ ?_, ${at('0').map(() => '?_').join(', ')}⟩
864
+ · intro p hp; simp [${id}_machine] at hp
865
+ · simp [${id}_machine, evalC, evalE]
866
+ · ${one ? `exact ${id}_facts_at ${ps}` : `intro l hl; simp [${id}_machine] at hl`}
867
+ · ${several ? `exact ${id}_pfacts_at ${ps}` : `intro p hp; simp [${id}_machine] at hp`}
868
+ all_goals simp [${id}_ρ, ${unfold}]
869
+ | succ n ih =>
870
+ obtain ⟨ρ, hr, ${hs.join(', ')}⟩ := ih
871
+ refine ⟨_, SReach.step ρ ${id}_μ (fun _ => 0) (fun _ _ => 0) hr ?_, ${at('0').map(() => '?_').join(', ')}⟩
872
+ · simp [sView, ${id}_machine, evalC, evalE]
873
+ all_goals (try rw [show x + (n + 1) = x + n + 1 from by omega])
874
+ all_goals simp [sNext, sView, decideFlags, decideQ, flagSub, withInputs, withCarried, ${hs.join(', ')}, ${unfold}]`;
875
+ // The lemmas (or the laws between, after the lemmas at y): each the machine's law read on the run.
876
+ const own = two ? r.between.map((_, j) => r.lemmas.length + j) : r.lemmas.map((_, j) => j);
877
+ const each = (j) => `by have h := ${id}_laws ρ ${id}_μ hr ${id}_e${j} (by simp [${id}_proof]); simp [SEntry.Holds, holdsOver, ${id}_e${j}, ${id}_l${j}, MLaw.Holds, evalC, evalE, ${hs.join(', ')}] at h; simpa using h`;
878
+ const proof = (from) => `(obtain ⟨ρ, hr, ${hs.join(', ')}⟩ := ${id}_run ${from}; exact ${own.length === 1 ? `(${each(own[0])})` : `⟨${own.map(j => `(${each(j)})`).join(', ')}⟩`})`;
879
+ return { pre, proof };
880
+ }
841
881
  export function recurToLean(lean, r, uses = {}) {
842
882
  const binders = r.args.map(a => `(${recurArg(a)} : Nat)`).join(' '), argNames = Object.fromEntries(r.args.map(a => [a, recurArg(a)]));
843
883
  const self = r.args.length ? `${lean}_rec` : lean;
@@ -847,22 +887,28 @@ export function recurToLean(lean, r, uses = {}) {
847
887
  const lemmaAt = (x) => r.lemmas.map(l => `(${surface({ x, fx: recurCall(lean, r, x) }).c(l.holds)})`).join(' ∧ ');
848
888
  const close = (haves) => `${haves.map(h => `${h}; `).join('')}(repeat' split) <;> omega`;
849
889
  const step = (n) => useLemmas(uses, r.next, { ...argNames, x: n, prev: recurCall(lean, r, n) }, lean);
850
- const lemmas = r.lemmas.length ? `
890
+ // Each theorem by the kernel's proof (its machine and the run), or by induction with omega where that fails.
891
+ const kl = recurKernel(lean, r, uses, 'lemmas'), kb = recurKernel(lean, r, uses, 'between');
892
+ const lemmas = r.lemmas.length ? `${kl?.pre ?? ''}
851
893
  -- ${r.lemmas.map(l => l.name).join('; ')}
852
894
  theorem ${lean}_lemmas ${binders} (x : Nat) : ${lemmaAt('x')} := by
853
- induction x with
854
- | zero => (try simp only [${unfold}]); ${close(useLemmas(uses, { op: 'and', xs: r.lemmas.map(l => l.holds) }, { ...argNames, x: '0', fx: recurCall(lean, r, '0') }, lean))}
855
- | succ n ih => (try simp only [${unfold}] at ih ⊢); ${close([...step('n'), ...useLemmas(uses, { op: 'and', xs: r.lemmas.map(l => l.holds) }, { ...argNames, x: '(n + 1)', fx: recurCall(lean, r, '(n + 1)') }, lean)])}` : '';
895
+ first
896
+ | ${kl ? kl.proof(`${[...r.args.map(recurArg), 'x'].join(' ')}`) : 'fail'}
897
+ | (induction x with
898
+ | zero => (try simp only [${unfold}]); ${close(useLemmas(uses, { op: 'and', xs: r.lemmas.map(l => l.holds) }, { ...argNames, x: '0', fx: recurCall(lean, r, '0') }, lean))}
899
+ | succ n ih => (try simp only [${unfold}] at ih ⊢); ${close([...step('n'), ...useLemmas(uses, { op: 'and', xs: r.lemmas.map(l => l.holds) }, { ...argNames, x: '(n + 1)', fx: recurCall(lean, r, '(n + 1)') }, lean)])})` : '';
856
900
  const args = r.args.map(recurArg).join(' ');
857
901
  const pairAt = (x, y) => r.between.map(l => `(${surface({ x, fx: recurCall(lean, r, x), y, fy: recurCall(lean, r, y) }).c(l.holds)})`).join(' ∧ ');
858
- const between = r.between.length ? `
902
+ const between = r.between.length ? `${kb?.pre ?? ''}
859
903
  -- ${r.between.map(l => l.name).join('; ')}, for every x <= y
860
904
  theorem ${lean}_between_by ${binders} (x d : Nat) : ${pairAt('x', '(x + d)')} := by
861
- induction d with
862
- | zero => simp only [Nat.add_zero]; (repeat' split) <;> omega
863
- | succ n ih =>
864
- rw [show x + (n + 1) = (x + n) + 1 from by omega]
865
- ${r.lemmas.length ? `have := ${lean}_lemmas ${args} (x + n); ` : ''}(try simp only [${unfold}] at ih this ⊢); ${close(step('(x + n)'))}
905
+ first
906
+ | ${kb ? kb.proof(`${[...r.args.map(recurArg), 'x', 'd'].join(' ')}`) : 'fail'}
907
+ | (induction d with
908
+ | zero => simp only [Nat.add_zero]; (repeat' split) <;> omega
909
+ | succ n ih =>
910
+ rw [show x + (n + 1) = (x + n) + 1 from by omega]
911
+ ${r.lemmas.length ? `have := ${lean}_lemmas ${args} (x + n); ` : ''}(try simp only [${unfold}] at ih this ⊢); ${close(step('(x + n)'))})
866
912
  theorem ${lean}_between ${binders} (x y : Nat) (h : x ≤ y) : ${pairAt('x', 'y')} := by
867
913
  obtain ⟨d, rfl⟩ := Nat.exists_eq_add_of_le h
868
914
  exact ${lean}_between_by ${args} x d` : '';
@@ -899,45 +945,45 @@ theorem Recur.unpair_pair (a b : Nat) : Recur.unpair ((a + b) * (a + b) + b) = (
899
945
  have h := Classical.epsilon_spec (p := fun p : Nat × Nat => (p.1 + p.2) * (p.1 + p.2) + p.2 = (a + b) * (a + b) + b) ⟨(a, b), rfl⟩
900
946
  obtain ⟨h1, h2⟩ := Recur.pair_inj h
901
947
  exact Prod.ext h1 h2`;
902
- // The facts, proven in Lean of the functions' Lean definitions, when every function has one: a run that starts with
903
- // those functions starts where SReach asks the facts to hold. A recurrence's equations unfold its definition (a function
904
- // of several arguments read at its key first); its lemmas and laws between two points are its theorems at the keys.
905
- function factsProof(id, m, maps, vars, simp = [], origins = new Map()) {
948
+ function factsProof(id, m, maps, vars, simp = [], at) {
906
949
  const fns = Object.entries(m.fns ?? {}), facts = m.facts ?? [];
907
- if (!facts.length || !fns.length || fns.some(([, f]) => !f.lean))
950
+ if (!facts.length || (!at && !fns.length) || fns.some(([, f]) => !f.lean))
908
951
  return '';
909
952
  const recurs = fns.filter(([, f]) => f.recur);
910
953
  simp = [...simp, ...recurs.flatMap(([, f]) => (f.recur.args.length ? [`${f.lean}_rec`, `${f.lean}_key`] : [f.lean]))];
911
- const at = leanSurface(Object.fromEntries(vars.map((x, j) => [x, `(ρ ${j})`])), `${id}'s facts`);
912
954
  // Each recurrence's key lemma rewrites the function at its key before anything else simplifies the key.
913
955
  const keyed = recurs.filter(([, f]) => f.recur.args.length).map(([, f]) => `${f.lean}_key`);
956
+ const hs = fns.map((_, j) => `h${j}`);
957
+ // A fact's proof, its keys named: the first `v`, the further ones `w1`, `w2`...: the recurrence theorem it is an
958
+ // instance of cited first (so that it and the goal simplify alike), then the facts' terms read and omega.
914
959
  const tactic = (fact) => {
915
- // The theorem a fact is an instance of, cited first so that both it and the goal are simplified alike.
916
- const cite = () => {
917
- const o = origins.get(fact.name), general = o?.from ?? fact;
918
- const owner = recurs.find(([n]) => general.name.startsWith(`${n}: `)), what = owner ? general.name.slice(owner[0].length + 2) : '';
919
- if (!owner || !general.forall)
920
- return undefined;
921
- const [, f] = owner, r = f.recur, keys = [general.forall, ...(general.keys ?? [])];
922
- // Each key: the one left (v), or its term, read at the columns.
923
- const arg = keys.map(x => (x === fact.forall ? 'v' : `(${at.e(o?.at[x] ?? { op: 'var', name: x })})`));
924
- if (r.lemmas.some(l => l.name === what))
925
- return `${f.lean}_lemmas ${[...arg.slice(1), arg[0]].join(' ')}`;
926
- if (r.between.some(l => l.name === what))
927
- return `${f.lean}_between ${[...arg.slice(2), arg[0], arg[1]].join(' ')}`;
928
- return undefined;
929
- };
930
- const c = cite(), hs = [...fns.map((_, j) => `h${j}`)];
931
- return `simp only [MLaw.Holds]; (try intro v); ${c ? `have hc := ${c}; ` : ''}simp only [evalC, evalE, upd, ${hs.join(', ')}]${keyed.length ? `; (try simp only [${keyed.join(', ')}])` : ''}; (try simp [${[...hs, ...simp].join(', ')}]${c ? ' at hc ⊢' : ''}); all_goals omega`;
960
+ const keys = fact.forall ? [fact.forall, ...(fact.keys ?? [])] : [], arg = keys.map((_, i) => (i === 0 ? 'v' : `w${i}`));
961
+ const owner = recurs.find(([n]) => fact.name.startsWith(`${n}: `)), what = owner ? fact.name.slice(owner[0].length + 2) : '';
962
+ const r = owner?.[1].recur, lean = owner?.[1].lean;
963
+ const cite = at?.cite?.(fact) ?? (!r || !keys.length ? undefined : r.lemmas.some(l => l.name === what) ? `${lean}_lemmas ${[...arg.slice(1), arg[0]].join(' ')}`
964
+ : r.between.some(l => l.name === what) ? `${lean}_between ${[...arg.slice(2), arg[0], arg[1]].join(' ')}` : undefined);
965
+ const intro = keys.length > 1 ? `simp only [holdsOver]; intro ${arg.slice(1).join(' ')}; ` : '';
966
+ return `${intro}simp only [MLaw.Holds]; (try intro v); ${cite ? `have hc := ${cite}; ` : ''}simp only [${['evalC', 'evalE', 'upd', ...hs].join(', ')}]${keyed.length ? `; (try simp only [${keyed.join(', ')}])` : ''}; (try simp [${[...hs, ...simp].join(', ')}]${cite ? ' at hc ⊢' : ''}); all_goals ${cite ? 'first | omega | (simpa using hc)' : 'omega'}`;
932
967
  };
968
+ const one = facts.filter(f => !f.keys?.length), several = facts.filter(f => f.keys?.length);
969
+ const binders = at ? at.binders : `(ρ : Nat → Nat) (μ : Nat → Nat → Nat) ${fns.map(([n, f], j) => `(h${j} : μ ${maps.indexOf(n)} = ${f.lean})`).join(' ')}`;
970
+ const rho = at ? at.rho : 'ρ', mu = at ? at.mu : 'μ', name = at ? 'at' : 'hold';
971
+ // At one state and maps, each function's equation is the maps' own definition.
972
+ const given = at ? fns.map(([n, f], j) => `have h${j} : ${mu} ${maps.indexOf(n)} = (${f.lean}) := rfl\n `).join('') : '';
973
+ const cases = (fs, h) => `rcases ${h} with ${fs.map(() => 'rfl').join(' | ')}\n${fs.map(f => ` · ${tactic(f)}`).join('\n')}`;
933
974
  return `
934
- -- The facts hold of the functions' Lean definitions: ${fns.map(([n, f]) => `${n} := ${f.lean}`).join('; ')}.
935
- theorem ${id}_facts_hold (ρ : Nat → Nat) (μ : Nat → Nat → Nat) ${fns.map(([n, f], j) => `(h${j} : μ ${maps.indexOf(n)} = ${f.lean})`).join(' ')} :
936
- ∀ l ∈ ${id}_machine.facts, l.Holds ρ μ := by
937
- intro l hl
975
+ -- The facts hold of the functions' Lean definitions: ${fns.map(([n, f]) => `${n} := ${f.lean}`).join('; ')}.${one.length ? `
976
+ theorem ${id}_facts_${name} ${binders} :
977
+ ∀ l ∈ ${id}_machine.facts, l.Holds ${rho} ${mu} := by
978
+ ${given}intro l hl
938
979
  simp only [${id}_machine, List.mem_cons, List.not_mem_nil, or_false] at hl
939
- rcases hl with ${facts.map(() => 'rfl').join(' | ')}
940
- ${facts.map(f => ` · ${tactic(f)}`).join('\n')}`;
980
+ ${cases(one, 'hl')}` : ''}${several.length ? `
981
+ -- The facts about several keys, at every value of their further keys.
982
+ theorem ${id}_pfacts_${name} ${binders} :
983
+ ${id}_machine.PFacts ${rho} ${mu} := by
984
+ ${given}intro p hp
985
+ simp only [${id}_machine, List.mem_cons, List.not_mem_nil, or_false] at hp
986
+ ${cases(several, 'hp')}` : ''}`;
941
987
  }
942
988
  // Machines with a history through Kern.hmachine_sound (History.lean): Lean builds the plain machine (sums as state,
943
989
  // relations in the assumption) and each relation's record goal; the trees and the instance keys are the kernel's.
@@ -1009,6 +1055,10 @@ export function toLean(items, opts = {}) {
1009
1055
  parts.push(`-- ${item.id}\n${loopToLean(id, item.loop, item.laws ?? [], item.rank)}`);
1010
1056
  theorems.push(`${id}_halts`);
1011
1057
  }
1058
+ else if ('fold' in item) {
1059
+ parts.push(`-- ${item.id}\n${foldToLean(id, item.fold, item.laws)}`);
1060
+ theorems.push(`${id}_laws`);
1061
+ }
1012
1062
  else {
1013
1063
  // goalToLean names its theorem t<i>; here it is the item's id.
1014
1064
  const n = goals++;