gdp-wired 0.19.0 → 0.20.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/dist/core/cycles.d.ts +23 -2
- package/dist/core/cycles.js +451 -51
- package/dist/core/cyclic.d.ts +18 -4
- package/dist/core/cyclic.js +319 -263
- package/dist/core/data.d.ts +61 -5
- package/dist/core/data.js +487 -113
- package/dist/core/discover.js +54 -33
- package/dist/core/dsl.d.ts +4 -2
- package/dist/core/dsl.js +31 -12
- package/dist/core/solver.d.ts +3 -0
- package/dist/core/solver.js +5 -0
- package/dist/core/translate.js +10 -4
- package/dist/kernel/prove.d.ts +3 -0
- package/dist/kernel/prove.js +95 -50
- package/dist/lean-kernel/emit.d.ts +2 -1
- package/dist/lean-kernel/emit.js +140 -54
- package/package.json +1 -1
|
@@ -16,8 +16,9 @@ export declare function leaves(t: Tree, path: Path, visit: (t: Tree & {
|
|
|
16
16
|
products?: [number, number][];
|
|
17
17
|
squares?: Lin[];
|
|
18
18
|
}, path: Path) => void): void;
|
|
19
|
-
export declare function encode(ex: Exported, tamper?: boolean, dropStep?: boolean, globalVars?: string[], globalMaps?: string[]): {
|
|
19
|
+
export declare function encode(ex: Exported, tamper?: boolean, dropStep?: boolean, globalVars?: string[], globalMaps?: string[], slim?: boolean): {
|
|
20
20
|
tab: string;
|
|
21
|
+
tabCols: string[];
|
|
21
22
|
hyps: string;
|
|
22
23
|
tree: string;
|
|
23
24
|
cols: string[];
|
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);
|
|
@@ -640,7 +674,7 @@ example : (checkSMachine ${id}_tampered ${id}_proof && sliveOk ${id}_tampered ${
|
|
|
640
674
|
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
675
|
sghost_laws ${id}_ghosted ${id}_ghostOk ${id}_proof ${id}_check` });
|
|
642
676
|
const allSteps = claimSteps();
|
|
643
|
-
const r =
|
|
677
|
+
const r = proveStepLawsOnce(m, laws, allSteps);
|
|
644
678
|
if (!r.proven || !r.proof)
|
|
645
679
|
throw new Error(`${id}: not proven`);
|
|
646
680
|
const lp = r.proof.laws, si = r.proof.instances;
|
|
@@ -830,18 +864,31 @@ example : checkSMachine ${id}_tampered ${id}_proof = false := by ${checkBy}` : '
|
|
|
830
864
|
// liveness's), carried to the machine by Bridge.lean's cyc_machine_sound and BridgeRules.lean's cyc_kstep_sound,
|
|
831
865
|
// sleads_of_holds (liveness, loops), hreach_plain (histories) and cyc_ghost_holds (ghosts), and each theorem stated as the
|
|
832
866
|
// 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
|
-
//
|
|
867
|
+
// `<id>.lawN`, `<id>_stepN_holds`, `<id>_liveN_holds`, `<id>_halts`; a machine with flags that hold at every key through
|
|
868
|
+
// its sk machine (BridgeRules.lean's q bridges). What the cyclic route doesn't cover goes the usual way: a law that reads
|
|
869
|
+
// a map at a key read from a map, and whatever no certificate is found for within the deadline (`certReport`).
|
|
870
|
+
// The usual proof of a machine's laws and step laws, made once per toLean: an item exported the usual way after its
|
|
871
|
+
// cyclic certificate wasn't found (cycMachineProof) reads the proof that search started from, not one made again. It
|
|
872
|
+
// is made with no cyclic certificate of its own (quietly): the export makes the certificate it exports from the usual
|
|
873
|
+
// trees (cycMachineProof), and the usual export reads the trees, so a certificate made here is never read.
|
|
874
|
+
const usualMade = new Map();
|
|
875
|
+
function proveStepLawsOnce(m, laws, stepLaws) {
|
|
876
|
+
const key = JSON.stringify([m, laws, stepLaws]), had = usualMade.get(key);
|
|
877
|
+
if (had)
|
|
878
|
+
return had;
|
|
879
|
+
const r = quietly(() => proveStepLaws(m, laws, stepLaws));
|
|
880
|
+
usualMade.set(key, r);
|
|
881
|
+
return r;
|
|
882
|
+
}
|
|
836
883
|
const envOf = () => globalThis.process?.env;
|
|
837
|
-
export const cyclicCerts =
|
|
884
|
+
export const cyclicCerts = certMode;
|
|
838
885
|
let certsOn = false, cycTexts = 0;
|
|
839
886
|
// What the last toLean exported from cyclic certificates, and what went the usual way (and why).
|
|
840
887
|
export const certReport = { cyclic: [], usual: [], translatedSteps: [] };
|
|
841
888
|
// Each item's cyclic certificate is made within one deadline (GDP_CERT_MS, 10 s by default), every goal it grows (the
|
|
842
889
|
// translation's leaves grown again, the searched nodes) bounded by it: past it, the item goes the usual way.
|
|
843
890
|
function cycMachineProof(m, laws, allSteps, allows = []) {
|
|
844
|
-
const r =
|
|
891
|
+
const r = proveStepLawsOnce(m, laws, allSteps);
|
|
845
892
|
if (!r.proven || !r.proof)
|
|
846
893
|
return { why: 'not proven the usual way' };
|
|
847
894
|
const until = performance.now() + certMs();
|
|
@@ -850,13 +897,14 @@ function cycMachineProof(m, laws, allSteps, allows = []) {
|
|
|
850
897
|
function cycMachineProofBy(m, laws, allSteps, allows, r, until) {
|
|
851
898
|
if (!r.proof)
|
|
852
899
|
return { why: 'not proven the usual way' };
|
|
900
|
+
const tStart = performance.now();
|
|
853
901
|
const lp = r.proof.laws, instOf = (sl) => r.proof.perStep?.[sl.name] ?? r.proof.instances;
|
|
854
902
|
// A machine with flags that hold at every key: its sk machine's nodes, the flags' closing facts read where each node
|
|
855
903
|
// reads the run.
|
|
856
904
|
const q = hasQuantified(m), mm = skMachine(m);
|
|
857
905
|
const L = laws.length, mine = (facts, lawNodes) => searchedStepNodes(m, laws, allSteps.map(sl => ({ law: sl, instances: instOf(sl) })), facts, lawNodes);
|
|
858
906
|
const tries = [];
|
|
859
|
-
if (
|
|
907
|
+
if (routeOf() !== 'search' && allSteps.length) {
|
|
860
908
|
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
909
|
const tr = usualSteps.every(u => u.tree) ? machineNodes(m, laws, lp.instances, { trees: lp.trees, steps: usualSteps }) : undefined;
|
|
862
910
|
if (tr && !tr.why && tr.specs.length === 3 * L + 2 * allSteps.length) {
|
|
@@ -870,15 +918,18 @@ function cycMachineProofBy(m, laws, allSteps, allows, r, until) {
|
|
|
870
918
|
tries.push({ facts: tr.facts, specs: tr.specs, lawNodes: tr.specs, stepAt: allSteps.map((_, j) => 3 * L + 2 * j + 1), translated: true });
|
|
871
919
|
}
|
|
872
920
|
}
|
|
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)) {
|
|
921
|
+
for (const base of [...(routeOf() !== 'search' ? [machineNodes(m, laws, lp.instances, { trees: lp.trees })] : []), machineNodes(m, laws, lp.instances)].filter(b => !b.why)) {
|
|
875
922
|
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
923
|
}
|
|
877
924
|
let last = 'no nodes';
|
|
925
|
+
const t00 = tStart;
|
|
878
926
|
for (const { facts, specs: specs0, lawNodes, stepAt, translated } of tries) {
|
|
879
927
|
if (performance.now() > until)
|
|
880
928
|
return { why: `past the certificate's deadline (${certMs()} ms)` };
|
|
929
|
+
const tTry = performance.now();
|
|
881
930
|
let specs = specs0, v = proveCyclic(facts, specs);
|
|
931
|
+
if (envOf()?.GDP_LEAN_TIMES === '1')
|
|
932
|
+
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
933
|
if (!(v.proven && v.checked)) {
|
|
883
934
|
last = performance.now() > until ? `past the certificate's deadline (${certMs()} ms)` : v.proven ? `not checked (${v.why})` : `${v.node}: ${v.goal}: ${v.stuck}`;
|
|
884
935
|
continue;
|
|
@@ -918,8 +969,9 @@ export function cycMachineToLean(id, given, laws, opts = {}) {
|
|
|
918
969
|
const hm = given.history ? { ...given, history: { ...given.history, prune: undefined } } : undefined;
|
|
919
970
|
const hplain = hm ? historyMachine(hm) : undefined, m = hplain ? hplain.machine : given;
|
|
920
971
|
const stepLaws = opts.stepLaws ?? [], gv = opts.ghosts ?? [], lp0 = opts.loop;
|
|
921
|
-
|
|
922
|
-
|
|
972
|
+
const why = uncoveredWhy(m, laws);
|
|
973
|
+
if (why)
|
|
974
|
+
return { why };
|
|
923
975
|
// With flags that hold at every key, the sk machine's encoding and BridgeRules.lean's q bridges.
|
|
924
976
|
const mq = skMachine(m), Q = hasQuantified(m) ? 'Q' : '', q = hasQuantified(m) ? 'q' : '';
|
|
925
977
|
const lives = (opts.liveness ?? []).map(l => ({ l, ...livenessParts(m, l) }));
|
|
@@ -948,6 +1000,15 @@ export function cycMachineToLean(id, given, laws, opts = {}) {
|
|
|
948
1000
|
const state = Object.keys(m.state), carried = m.inputMaps ?? [];
|
|
949
1001
|
const primesL = () => list(state.map(s2 => `(${ix(s2)}, ${ix(`${s2}'`)})`)), primeMapsL = () => list(Object.keys(m.maps ?? {}).map(mp => `(${mx(mp)}, ${mx(`${mp}'`)})`));
|
|
950
1002
|
const node = (i) => `${id}_node${i}`;
|
|
1003
|
+
// What every law's and step law's check reads alike, proven once: the exported facts among the encoding's, and (with
|
|
1004
|
+
// 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
|
|
1005
|
+
// reads those as true (simp only, unfolding the check), not computed again per law.
|
|
1006
|
+
const sharedOk = [`${id}_factsIn`, ...(Q ? [] : [`${id}_shape`, `${id}_terms`])].join(', ');
|
|
1007
|
+
const okLemmas = `theorem ${id}_factsIn : cyc${Q}FactsIn ${id}_machine ${id}_enc ${id}_facts = true := by ${checkBy}${Q ? '' : `
|
|
1008
|
+
theorem ${id}_shape : cycShapeOk ${id}_machine ${id}_enc [] = true := by ${checkBy}
|
|
1009
|
+
theorem ${id}_terms : cycTermsOk ${id}_machine ${id}_enc [] = true := by ${checkBy}`}`;
|
|
1010
|
+
const mOk = `by simp only [cyc${Q}MOk, ${sharedOk}]; ${checkBy}`, kOk = `by simp only [cyc${Q}KOk, cyc${Q}KOkAt, ${sharedOk}]; ${checkBy}`;
|
|
1011
|
+
const aOk = `by simp only [cyc${Q}AOk, ${sharedOk}]; ${checkBy}`;
|
|
951
1012
|
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
1013
|
// The machine as the usual export defines it: with a history its plain machine, with ghosts the ghosted one, a loop's.
|
|
953
1014
|
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 +1046,7 @@ def ${id}_machine : SMachine := ${id}_loop.machine` : `def ${id}_machine : SMach
|
|
|
985
1046
|
const lawBlocks = laws.map((l, j) => `-- ${l.name}
|
|
986
1047
|
def ${id}_law${j} : C := ${c(l.holds)}
|
|
987
1048
|
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 :=
|
|
1049
|
+
theorem ${id}_law${j}_ok : cyc${Q}MOk ${id}_machine ${id}_enc ${id}_facts ${id}_law${j} ${keysL(l)} ${node(j)} = true := ${mOk}
|
|
989
1050
|
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
1051
|
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
1052
|
const keys2 = (l) => list((l.keys ?? []).map(k2 => String(ix(k2))));
|
|
@@ -1026,7 +1087,7 @@ def ${id}_step${j} : SKStep := ⟨⟨${list(kWhens(mq, sl).map(c))},
|
|
|
1026
1087
|
${list(mren)},
|
|
1027
1088
|
[], [], [], [], [],
|
|
1028
1089
|
${list(names)}⟩
|
|
1029
|
-
theorem ${id}_step${j}_ok : cyc${Q}KOk ${id}_machine ${id}_enc ${id}_facts ${id}_step${j} ${node(stepAt[j])} = true :=
|
|
1090
|
+
theorem ${id}_step${j}_ok : cyc${Q}KOk ${id}_machine ${id}_enc ${id}_facts ${id}_step${j} ${node(stepAt[j])} = true := ${kOk}
|
|
1030
1091
|
theorem ${id}_step${j}_holds : SKStepHolds ${id}_machine ${id}_step${j} :=
|
|
1031
1092
|
${kstep(`${id}_step${j}`, stepAt[j])} ${id}_step${j}_ok`;
|
|
1032
1093
|
});
|
|
@@ -1044,7 +1105,7 @@ theorem ${lid}_allows : ∀ h ∈ ${lid}.helps, ∃ wit : List (Nat × E), disti
|
|
|
1044
1105
|
intro h hh
|
|
1045
1106
|
simp only [${lid}, List.mem_cons, List.not_mem_nil, or_false] at hh
|
|
1046
1107
|
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 (
|
|
1108
|
+
${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
1109
|
/-- ${l.name}, under weak fairness: along every run that takes each helpful request whenever, from some point on, it
|
|
1049
1110
|
stays allowed, \`from\` is followed by \`to\`. -/
|
|
1050
1111
|
theorem ${lid}_holds_wf (run : SRun ${id}_machine)
|
|
@@ -1056,13 +1117,13 @@ theorem ${lid}_holds_wf (run : SRun ${id}_machine)
|
|
|
1056
1117
|
return `-- Liveness: ${l.name}
|
|
1057
1118
|
def ${lid} : Live := ${liveLit(l, { e, c }, ix, primesL(), primeMapsL())}
|
|
1058
1119
|
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)} (
|
|
1120
|
+
theorem ${lid}_stay : SKStepHolds ${id}_machine (liveK ${lid} ${lid}.pend (liveStayC ${lid})) := ${kstep('_', s0)} (${kOk})
|
|
1121
|
+
theorem ${lid}_cover : SKStepHolds ${id}_machine (liveK ${lid} ${lid}.pend (liveCoverC ${lid})) := ${kstep('_', c0)} (${kOk})
|
|
1061
1122
|
theorem ${lid}_helps : ∀ h ∈ ${lid}.helps, SKStepHolds ${id}_machine (liveK ${lid} (liveHelpWhen ${lid} h) (liveHelpThen ${lid})) := by
|
|
1062
1123
|
intro h hh
|
|
1063
1124
|
simp only [${lid}, List.mem_cons, List.not_mem_nil, or_false] at hh
|
|
1064
1125
|
rcases hh with ${helps.map(() => 'rfl').join(' | ')}
|
|
1065
|
-
${helps.map((_, i) => `· exact ${kstep('_', hAt(i))} (
|
|
1126
|
+
${helps.map((_, i) => `· exact ${kstep('_', hAt(i))} (${kOk})`).join('\n ')}
|
|
1066
1127
|
/-- ${l.name}: along every run that takes each helpful request infinitely often, \`from\` is followed by \`to\`. -/
|
|
1067
1128
|
theorem ${lid}_holds (run : SRun ${id}_machine)
|
|
1068
1129
|
(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 +1140,7 @@ theorem ${id}_orig_laws : ∀ ρ M, SReach ${id}_ghosted.orig ρ M → ∀ e ∈
|
|
|
1079
1140
|
${laws.length ? `${oneOf('he')}
|
|
1080
1141
|
rcases he with ${laws.map(() => 'rfl').join(' | ')}
|
|
1081
1142
|
${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])} (
|
|
1143
|
+
const refineBlock = rf ? `theorem ${id}_allowed : SKStepHolds ${id}_machine (srStep ${id}_refined ${primesL()} ${primeMapsL()} noSProof) := ${kstep('_', stepAt[0])} (${kOk})
|
|
1083
1144
|
theorem ${id}_refines : ∀ ρ M, SReach ${id}_refined.impl ρ M → ∃ ρc Mc σ Ms, SReach ${id}_refined.full ρc Mc ∧ Agree ${id}_refined.SV ρc ρ
|
|
1084
1145
|
∧ (∀ mp, mp ∉ ${id}_refined.SM → Mc mp = M mp) ∧ SReach ${id}_refined.spec σ Ms ∧ Agree ${id}_refined.X σ ρc
|
|
1085
1146
|
∧ (∀ 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,6 +1154,7 @@ theorem ${id}_refines : ∀ ρ M, SReach ${id}_refined.impl ρ M → ∃ ρc Mc
|
|
|
1093
1154
|
${machineDef}
|
|
1094
1155
|
def ${id}_enc : CycEnc := ${enc}
|
|
1095
1156
|
${cycProofText(id, p, [], vars, maps)}
|
|
1157
|
+
${okLemmas}
|
|
1096
1158
|
${lawBlocks}
|
|
1097
1159
|
${lawsThm}
|
|
1098
1160
|
${hm ? histBlock() : readable(id, laws, vars, maps, { binders: '(ρ : Nat → Nat) (μ : Nat → Nat → Nat)', hyp: `SReach ${id}_machine ρ μ`, call: '',
|
|
@@ -1395,6 +1457,7 @@ export function toLean(items, opts = {}) {
|
|
|
1395
1457
|
certReport.usual = [];
|
|
1396
1458
|
certReport.translatedSteps = [];
|
|
1397
1459
|
cycTexts = 0;
|
|
1460
|
+
usualMade.clear();
|
|
1398
1461
|
// The proofs found with only the instances their leaves use: the same laws, from fewer hypotheses for Lean to build.
|
|
1399
1462
|
const wasMinimal = minimal.on;
|
|
1400
1463
|
minimal.on = true;
|
|
@@ -1431,13 +1494,13 @@ function toLeanWith(items, opts) {
|
|
|
1431
1494
|
return `${pairing}${text}\n`;
|
|
1432
1495
|
}).join('');
|
|
1433
1496
|
for (let item of items) {
|
|
1434
|
-
const id = ident(item.id);
|
|
1497
|
+
const id = ident(item.id), tItem = performance.now();
|
|
1435
1498
|
if ('machine' in item) {
|
|
1436
1499
|
const m = item.machine;
|
|
1437
1500
|
// Laws with some key go in as the prover proved them: with its witness in place (the stronger law).
|
|
1438
1501
|
const someKey = (c) => JSON.stringify(c).includes('"op":"all"');
|
|
1439
1502
|
if ([...item.laws.map(l => l.holds), ...(item.stepLaws ?? []).map(sl => sl.then)].some(someKey)) {
|
|
1440
|
-
const r =
|
|
1503
|
+
const r = proveStepLawsOnce(m, item.laws, item.stepLaws ?? []);
|
|
1441
1504
|
if (!r.proven)
|
|
1442
1505
|
throw new Error(`${item.id}: not proven (${r.failed?.stuck ?? ''})`);
|
|
1443
1506
|
item = { ...item, laws: r.laws ?? item.laws, ...(item.stepLaws ? { stepLaws: r.stepLaws ?? item.stepLaws } : {}) };
|
|
@@ -1506,6 +1569,8 @@ function toLeanWith(items, opts) {
|
|
|
1506
1569
|
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
1570
|
theorems.push(id);
|
|
1508
1571
|
}
|
|
1572
|
+
if (envOf()?.GDP_LEAN_TIMES === '1')
|
|
1573
|
+
console.error(`lean item ${item.id}: ${Math.round(performance.now() - tItem)} ms, ${parts.at(-1)?.length ?? 0} bytes`);
|
|
1509
1574
|
}
|
|
1510
1575
|
// The file the items need: Live.lean for a liveness or a loop, Sets.lean (which imports Steps.lean) for any machine.
|
|
1511
1576
|
return `import History
|
|
@@ -1558,10 +1623,27 @@ ${cycProofText(id, p, tampered, vars, maps)}${bridge ? `\n${cycBridgeLean(id, p,
|
|
|
1558
1623
|
// node's membership, the check and cyclic_sound's theorem; each tampered proof's check computed to false.
|
|
1559
1624
|
function cycProofText(id, p, tampered, vars, maps) {
|
|
1560
1625
|
const { e, c } = leanTerms(vars, maps, id);
|
|
1626
|
+
// (data, not code: no compiled code for Lean to make, unless native_decide runs it)
|
|
1627
|
+
const d = checkBy === 'native_decide' ? 'def' : 'noncomputable def';
|
|
1561
1628
|
const ix = (name) => vars.indexOf(name);
|
|
1562
1629
|
const facts = list(p.facts.map(f => `⟨${list(f.keys.map(key => String(ix(key))))}, ${c(f.holds)}⟩`));
|
|
1563
1630
|
// A tampered proof keeps the proof's tables and trees (made for the proof's own goals) under its changed nodes.
|
|
1564
|
-
|
|
1631
|
+
// Each table once (`<id>_tabN`): a node's goals (its own, each use's premises and descent) share its hypotheses, so
|
|
1632
|
+
// most of their tables are the same columns, and Lean elaborates each list once instead of once per goal.
|
|
1633
|
+
// So too each term column's term (an open choice the tables share) and each goal's table and tree where two goals
|
|
1634
|
+
// have the same (a premise read at the same terms by two nodes).
|
|
1635
|
+
const shared = (prefix) => {
|
|
1636
|
+
const seen = new Map();
|
|
1637
|
+
return { seen, name: (text) => { let name = seen.get(text); if (!name)
|
|
1638
|
+
seen.set(text, (name = `${id}_${prefix}${seen.size}`)); return name; } };
|
|
1639
|
+
};
|
|
1640
|
+
const terms = shared('term'), tabs = shared('tab'), goals = shared('goal');
|
|
1641
|
+
const enc = (g, tree) => {
|
|
1642
|
+
const x = encode({ name: `${id}: ${g.name}`, goal: g, tree }, false, true, vars, maps, true);
|
|
1643
|
+
const tab = list(x.tabCols.map(col => (col.startsWith('.term (') && col.length > 48 ? `.term ${terms.name(col.slice(6))}` : col)));
|
|
1644
|
+
const pair = `(${tabs.name(tab)}, ${x.tree})`;
|
|
1645
|
+
return pair.length > 200 ? goals.name(pair) : pair;
|
|
1646
|
+
};
|
|
1565
1647
|
const nodeLits = (q) => q.nodes.map((n, i) => {
|
|
1566
1648
|
const o = p.nodes[i];
|
|
1567
1649
|
const uses = n.uses.map((u, j) => {
|
|
@@ -1571,19 +1653,23 @@ function cycProofText(id, p, tampered, vars, maps) {
|
|
|
1571
1653
|
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
1654
|
});
|
|
1573
1655
|
const nodes = (q) => list(nodeLits(q).map(x => `⟨${x.hyps}, ${x.concl}, ${x.rest}⟩`));
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1656
|
+
const own = nodeLits(p), bad = tampered.map(t => nodes(t.proof));
|
|
1657
|
+
return `${d} ${id}_facts : List Fact := ${facts}
|
|
1658
|
+
${[...terms.seen].map(([t, name]) => `${d} ${name} : E := ${t}`).join('\n')}
|
|
1659
|
+
${[...tabs.seen].map(([tab, name]) => `${d} ${name} : List VDef := ${tab}`).join('\n')}
|
|
1660
|
+
${[...goals.seen].map(([g, name]) => `${d} ${name} : List VDef × Tree := ${g}`).join('\n')}
|
|
1661
|
+
${own.map((x, i) => `${d} ${id}_hyps${i} : List C := ${x.hyps}
|
|
1662
|
+
${d} ${id}_concl${i} : C := ${x.concl}
|
|
1663
|
+
${d} ${id}_node${i} : CNode := ⟨${id}_hyps${i}, ${id}_concl${i}, ${x.rest}⟩
|
|
1578
1664
|
theorem ${id}_node${i}_hyps : ${id}_node${i}.hyps = ${id}_hyps${i} := rfl
|
|
1579
1665
|
theorem ${id}_node${i}_concl : ${id}_node${i}.concl = ${id}_concl${i} := rfl`).join('\n')}
|
|
1580
|
-
|
|
1666
|
+
${d} ${id}_nodes : List CNode := ${list(p.nodes.map((_, i) => `${id}_node${i}`))}
|
|
1581
1667
|
${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
1668
|
theorem ${id}_check : checkCyclic ${id}_facts ${id}_nodes = true := by ${checkBy}
|
|
1583
1669
|
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
1670
|
cyclic_sound ${id}_facts ${id}_nodes ${id}_check
|
|
1585
1671
|
${tampered.map((t, j) => `-- TAMPERED: ${t.what}. Lean computes the check to false.
|
|
1586
|
-
|
|
1672
|
+
${d} ${id}_tampered${j} : List CNode := ${bad[j]}
|
|
1587
1673
|
example : checkCyclic ${id}_facts ${id}_tampered${j} = false := by ${checkBy}`).join('\n')}`;
|
|
1588
1674
|
}
|
|
1589
1675
|
function cycScope(all, bridge, extra = []) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gdp-wired",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.20.0",
|
|
4
4
|
"description": "An automatic prover for stateful rules (budgets, caps, windows, workflows, policies): laws proven with no proof to write, checked again in Lean, counterexamples found on the GPU, the rule shipped as code",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"verification",
|