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