gdp-wired 0.20.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 +12 -0
- package/dist/core/cycles.js +7 -0
- package/dist/core/data.d.ts +138 -13
- package/dist/core/data.js +842 -145
- package/dist/core/dsl.d.ts +27 -1
- package/dist/core/dsl.js +137 -26
- 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/transfer.d.ts +17 -0
- package/dist/core/transfer.js +468 -0
- package/dist/core/watch.js +5 -1
- package/dist/kernel/prove.d.ts +5 -1
- package/dist/kernel/prove.js +11 -5
- package/dist/lean-kernel/emit.d.ts +2 -0
- package/dist/lean-kernel/emit.js +196 -15
- package/dist/lean-kernel/library.js +2 -2
- package/package.json +2 -2
|
@@ -0,0 +1,468 @@
|
|
|
1
|
+
import { Bool, Integer, Nat, Val, argsOf, bindName, define, encX, encY, evalX, factsOf, fnNamed, programOf, schemesOf, searchIn } from "./data.js";
|
|
2
|
+
import { proveCyclic } from "./cyclic.js";
|
|
3
|
+
import { clock, pairedKeys, withDeadline } from "../kernel/prove.js";
|
|
4
|
+
import { substE } from "../kernel/kernel.js";
|
|
5
|
+
const nat = (value) => ({ op: 'nat', value });
|
|
6
|
+
const cond = (c) => ({ op: 'bool', c });
|
|
7
|
+
const OPS = [
|
|
8
|
+
{ name: '+', args: 2, ret: 'nat', x: (a, b) => ({ op: 'add', a, b }), at: (a, b) => a + b },
|
|
9
|
+
{ name: '∸', args: 2, ret: 'nat', x: (a, b) => ({ op: 'sub', a, b }), at: (a, b) => Math.max(0, a - b) },
|
|
10
|
+
{ name: 'min', args: 2, ret: 'nat', x: (a, b) => ({ op: 'min', a, b }), at: Math.min },
|
|
11
|
+
{ name: 'max', args: 2, ret: 'nat', x: (a, b) => ({ op: 'max', a, b }), at: Math.max },
|
|
12
|
+
{ name: '≤', args: 2, ret: 'bool', x: (a, b) => cond({ op: '<=', a, b }), at: (a, b) => +(a <= b) },
|
|
13
|
+
{ name: '<', args: 2, ret: 'bool', x: (a, b) => cond({ op: '<', a, b }), at: (a, b) => +(a < b) },
|
|
14
|
+
{ name: '=', args: 2, ret: 'bool', x: (a, b) => cond({ op: '=', a, b }), at: (a, b) => +(a === b) },
|
|
15
|
+
{ name: '≥', args: 2, ret: 'bool', x: (a, b) => cond({ op: '<=', a: b, b: a }), at: (a, b) => +(a >= b) },
|
|
16
|
+
{ name: '>', args: 2, ret: 'bool', x: (a, b) => cond({ op: '<', a: b, b: a }), at: (a, b) => +(a > b) },
|
|
17
|
+
{ name: '≠', args: 2, ret: 'bool', x: (a, b) => cond({ op: 'not', x: { op: '=', a, b } }), at: (a, b) => +(a !== b) },
|
|
18
|
+
{ name: 'x + 1', args: 1, ret: 'nat', x: a => ({ op: 'add', a, b: nat(1) }), at: a => a + 1 },
|
|
19
|
+
{ name: 'x ∸ 1', args: 1, ret: 'nat', x: a => ({ op: 'sub', a, b: nat(1) }), at: a => Math.max(0, a - 1) },
|
|
20
|
+
{ name: '2·x', args: 1, ret: 'nat', x: a => ({ op: 'mul', k: 2, a }), at: a => 2 * a },
|
|
21
|
+
{ name: 'x = 0', args: 1, ret: 'bool', x: a => cond({ op: '=', a, b: nat(0) }), at: a => +(a === 0) },
|
|
22
|
+
];
|
|
23
|
+
const fnOf = (name) => { const f = fnNamed(name); if (!f)
|
|
24
|
+
throw new Error(`no function ${name}`); return f; };
|
|
25
|
+
const recursive = (f) => f.decreases !== undefined;
|
|
26
|
+
// The built-in a recursive function of naturals computes at every argument up to 6 (one argument: up to 12), if one.
|
|
27
|
+
function builtinOf(f) {
|
|
28
|
+
if (!recursive(f) || f.paramFns.length || !(f.ret === 'nat' || f.ret === 'bool') || !f.params.every(q => q.sort === 'nat'))
|
|
29
|
+
return undefined;
|
|
30
|
+
const n = f.params.length;
|
|
31
|
+
return OPS.find(op => {
|
|
32
|
+
if (op.args !== n || op.ret !== f.ret)
|
|
33
|
+
return false;
|
|
34
|
+
try {
|
|
35
|
+
for (let a = 0; a <= (n === 1 ? 12 : 6); a++)
|
|
36
|
+
for (let b = 0; b <= (n === 1 ? 0 : 6); b++) {
|
|
37
|
+
if (evalX({ op: 'call', fn: f.name, args: [nat(a), nat(b)].slice(0, n) }, new Map(), {}) !== op.at(a, b))
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
return true;
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
const callsOf = (t) => { const out = []; walkO(t, o => { if (o.op === 'call')
|
|
48
|
+
out.push(o.fn); }); return out; };
|
|
49
|
+
function walkO(t, f) {
|
|
50
|
+
if (Array.isArray(t)) {
|
|
51
|
+
t.forEach(x => walkO(x, f));
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
if (!t || typeof t !== 'object')
|
|
55
|
+
return;
|
|
56
|
+
f(t);
|
|
57
|
+
for (const v of Object.values(t))
|
|
58
|
+
walkO(v, f);
|
|
59
|
+
}
|
|
60
|
+
export function transferPlan(l) {
|
|
61
|
+
const p = programOf([l]), bridges = new Map();
|
|
62
|
+
for (const f of p.fns) {
|
|
63
|
+
const op = builtinOf(f);
|
|
64
|
+
if (op)
|
|
65
|
+
bridges.set(f.name, op);
|
|
66
|
+
}
|
|
67
|
+
const unfolds = p.fns.some(f => !recursive(f));
|
|
68
|
+
return bridges.size || unfolds ? { bridges, unfolds } : undefined;
|
|
69
|
+
}
|
|
70
|
+
// --- the law said another way ------------------------------------------------------------------------------------------
|
|
71
|
+
// A function's body at arguments (its parameters replaced, each bound name fresh).
|
|
72
|
+
function instantiate(f, args) {
|
|
73
|
+
const sub = new Map(f.params.map((q, i) => [`${f.name}.${q.name}`, args[i]])), ren = new Map();
|
|
74
|
+
const fresh = (b) => { const n = bindName(b.replace(/#\d+$/, '')); ren.set(b, n); return n; };
|
|
75
|
+
const go = (t) => {
|
|
76
|
+
if (Array.isArray(t))
|
|
77
|
+
return t.map(go);
|
|
78
|
+
if (!t || typeof t !== 'object')
|
|
79
|
+
return t;
|
|
80
|
+
const o = t;
|
|
81
|
+
if (o.op === 'var') {
|
|
82
|
+
const name = o.name;
|
|
83
|
+
return sub.get(name) ?? (ren.has(name) ? { op: 'var', name: ren.get(name) } : o);
|
|
84
|
+
}
|
|
85
|
+
if (o.op === 'match') {
|
|
86
|
+
const cases = o.cases.map(c => ({ binds: c.binds.map(fresh), body: c.body }));
|
|
87
|
+
return { ...o, of: go(o.of), cases: cases.map(c => ({ binds: c.binds, body: go(c.body) })) };
|
|
88
|
+
}
|
|
89
|
+
if (o.op === 'natmatch') {
|
|
90
|
+
const pred = fresh(o.pred);
|
|
91
|
+
return { ...o, of: go(o.of), zero: go(o.zero), pred, succ: go(o.succ) };
|
|
92
|
+
}
|
|
93
|
+
return Object.fromEntries(Object.entries(o).map(([key, v]) => [key, go(v)]));
|
|
94
|
+
};
|
|
95
|
+
return go(f.body());
|
|
96
|
+
}
|
|
97
|
+
// A term with each bridged call the built-in and each call to a copied function its copy's; in a law (`unfold`), a call
|
|
98
|
+
// to a function that isn't recursive also its body. `holds` of a condition made a value is the condition.
|
|
99
|
+
function rewriter(bridges, copies, unfold, ren = new Map()) {
|
|
100
|
+
let unfolded = 0;
|
|
101
|
+
const rx = (x) => {
|
|
102
|
+
switch (x.op) {
|
|
103
|
+
case 'var': return ren.has(x.name) ? { op: 'var', name: ren.get(x.name) } : x;
|
|
104
|
+
case 'nat': return x;
|
|
105
|
+
case 'add':
|
|
106
|
+
case 'sub':
|
|
107
|
+
case 'min':
|
|
108
|
+
case 'max': return { op: x.op, a: rx(x.a), b: rx(x.b) };
|
|
109
|
+
case 'mul': return { op: 'mul', k: x.k, a: rx(x.a) };
|
|
110
|
+
case 'prod':
|
|
111
|
+
case 'iadd':
|
|
112
|
+
case 'isub': return { op: x.op, a: rx(x.a), b: rx(x.b) };
|
|
113
|
+
case 'mod':
|
|
114
|
+
case 'div':
|
|
115
|
+
case 'imod':
|
|
116
|
+
case 'idiv':
|
|
117
|
+
case 'imul': return { op: x.op, k: x.k, a: rx(x.a) };
|
|
118
|
+
case 'ineg':
|
|
119
|
+
case 'ofnat': return { op: x.op, a: rx(x.a) };
|
|
120
|
+
case 'int': return x;
|
|
121
|
+
case 'if': return { op: 'if', c: ry(x.c), then: rx(x.then), else: rx(x.else) };
|
|
122
|
+
case 'bool': return { op: 'bool', c: ry(x.c) };
|
|
123
|
+
case 'apply': return { op: 'apply', param: x.param, arg: rx(x.arg) };
|
|
124
|
+
case 'size': return { op: 'size', data: x.data, of: rx(x.of) };
|
|
125
|
+
case 'ctor': return { ...x, args: x.args.map(rx) };
|
|
126
|
+
case 'match': return { ...x, of: rx(x.of), cases: x.cases.map(c => ({ binds: c.binds, body: rx(c.body) })) };
|
|
127
|
+
case 'natmatch': return { ...x, of: rx(x.of), zero: rx(x.zero), succ: rx(x.succ) };
|
|
128
|
+
case 'call': {
|
|
129
|
+
const op = bridges.get(x.fn);
|
|
130
|
+
if (op)
|
|
131
|
+
return op.x(rx(x.args[0]), x.args[1] ? rx(x.args[1]) : nat(0));
|
|
132
|
+
if (unfold && !recursive(fnOf(x.fn))) {
|
|
133
|
+
if (++unfolded > 400)
|
|
134
|
+
throw new RangeError('unfolded past 400 calls');
|
|
135
|
+
return rx(instantiate(fnOf(x.fn), x.args));
|
|
136
|
+
}
|
|
137
|
+
return { op: 'call', fn: copies.get(x.fn) ?? x.fn, args: x.args.map(rx) };
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
const ry = (y) => {
|
|
142
|
+
switch (y.op) {
|
|
143
|
+
case '<':
|
|
144
|
+
case '<=':
|
|
145
|
+
case '=':
|
|
146
|
+
case 'i<':
|
|
147
|
+
case 'i<=': return { op: y.op, a: rx(y.a), b: rx(y.b) };
|
|
148
|
+
case 'and':
|
|
149
|
+
case 'or': return { op: y.op, xs: y.xs.map(ry) };
|
|
150
|
+
case 'not': return { op: 'not', x: ry(y.x) };
|
|
151
|
+
case 'is': return { ...y, of: rx(y.of) };
|
|
152
|
+
case 'holds': {
|
|
153
|
+
const x = rx(y.x);
|
|
154
|
+
return x.op === 'bool' ? x.c : { op: 'holds', x };
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
};
|
|
158
|
+
return { rx, ry };
|
|
159
|
+
}
|
|
160
|
+
const valueOf = (s, x) => (s === 'nat' ? new Nat(x) : s === 'bool' ? new Bool(x.op === 'bool' ? x.c : { op: 'holds', x }) : s === 'int' ? new Integer(x) : new Val(s, x));
|
|
161
|
+
// The copies made, by function and bridges (a law proven again reads the same copies).
|
|
162
|
+
const made = new WeakMap();
|
|
163
|
+
const freshName = (base) => { let n = base; for (let i = 2; fnNamed(n); i++)
|
|
164
|
+
n = `${base}${i}`; return n; };
|
|
165
|
+
// The law through the bridges `bridged`: the copies (made once), the law said with them, the bridge and copy laws, and
|
|
166
|
+
// the program of all of them; none where a copied function is in a ring of several (its copy law would be mutual).
|
|
167
|
+
function build(l, bridged, unfold) {
|
|
168
|
+
// Copied: a function that calls a bridged one, or (unfolding) one that isn't recursive, or a copied one; a function
|
|
169
|
+
// that isn't recursive is unfolded where it is called instead.
|
|
170
|
+
const p0 = programOf([l]), reach = new Set(), unfolded = (n) => unfold && !recursive(fnOf(n));
|
|
171
|
+
for (let changed = true; changed;) {
|
|
172
|
+
changed = false;
|
|
173
|
+
for (const f of p0.fns)
|
|
174
|
+
if (!bridged.has(f.name) && !unfolded(f.name) && !reach.has(f.name) && callsOf(f.body()).some(g => bridged.has(g) || unfolded(g) || reach.has(g))) {
|
|
175
|
+
reach.add(f.name);
|
|
176
|
+
changed = true;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
if ([...reach].some(n => fnOf(n).ring.length > 1))
|
|
180
|
+
return undefined;
|
|
181
|
+
const key = `${unfold}|${[...bridged].map(([f, op]) => `${f}:${op.name}`).sort().join(',')}`, copies = new Map();
|
|
182
|
+
for (const n of reach) {
|
|
183
|
+
const f = fnOf(n), had = made.get(f)?.get(key);
|
|
184
|
+
if (had && fnNamed(had))
|
|
185
|
+
copies.set(n, had);
|
|
186
|
+
}
|
|
187
|
+
for (const n of reach) {
|
|
188
|
+
if (copies.has(n))
|
|
189
|
+
continue;
|
|
190
|
+
const f = fnOf(n), name = freshName(`${n}_bi`);
|
|
191
|
+
copies.set(n, name);
|
|
192
|
+
if (!made.has(f))
|
|
193
|
+
made.set(f, new Map());
|
|
194
|
+
made.get(f).set(key, name);
|
|
195
|
+
}
|
|
196
|
+
for (const n of reach) {
|
|
197
|
+
const f = fnOf(n), name = copies.get(n);
|
|
198
|
+
if (fnNamed(name))
|
|
199
|
+
continue;
|
|
200
|
+
const ren = new Map(f.params.map(q => [`${f.name}.${q.name}`, `${name}.${q.name}`]));
|
|
201
|
+
let body;
|
|
202
|
+
try {
|
|
203
|
+
body = rewriter(bridged, copies, unfold, ren).rx(f.body());
|
|
204
|
+
}
|
|
205
|
+
catch (e) {
|
|
206
|
+
if (e instanceof RangeError)
|
|
207
|
+
return undefined;
|
|
208
|
+
throw e;
|
|
209
|
+
}
|
|
210
|
+
define(name, Object.fromEntries(f.params.map(q => [q.name, q.sort])), f.ret, () => valueOf(f.ret, body));
|
|
211
|
+
}
|
|
212
|
+
let holds;
|
|
213
|
+
try {
|
|
214
|
+
holds = rewriter(bridged, copies, unfold).ry(l.holds);
|
|
215
|
+
}
|
|
216
|
+
catch (e) {
|
|
217
|
+
if (e instanceof RangeError)
|
|
218
|
+
return undefined;
|
|
219
|
+
throw e;
|
|
220
|
+
}
|
|
221
|
+
const lstar = { name: `${l.name}, said through ${[...bridged.keys()].length ? 'the built-ins' : 'its definitions'}`, vars: l.vars, holds, params: l.params };
|
|
222
|
+
// (none the same as the law, and none past eight times its size: definitions unfolded into each other's arguments)
|
|
223
|
+
const size = (y) => JSON.stringify(y).length;
|
|
224
|
+
if (JSON.stringify(lstar.holds) === JSON.stringify(l.holds) || size(lstar.holds) > 8 * size(l.holds) + 4000)
|
|
225
|
+
return undefined;
|
|
226
|
+
const v = (name) => ({ op: 'var', name });
|
|
227
|
+
// Only the copies the law said through them reaches (each with those its copy calls).
|
|
228
|
+
const p1 = programOf([lstar]), back = new Map([...copies].map(([a, b]) => [b, a]));
|
|
229
|
+
const bridges = [...bridged].map(([f, op]) => {
|
|
230
|
+
const fn = fnOf(f), vs = fn.params.map(q => q.name);
|
|
231
|
+
return { fn, op, law: { name: `${f} is ${op.name}`, vars: fn.params.map(q => ({ name: q.name, sort: q.sort })), holds: { op: '=', a: { op: 'call', fn: f, args: vs.map(v) }, b: op.x(v(vs[0]), vs[1] ? v(vs[1]) : nat(0)) }, params: [] } };
|
|
232
|
+
});
|
|
233
|
+
const equivs = p1.fns.filter(f => back.has(f.name)).map(c => {
|
|
234
|
+
const fn = fnOf(back.get(c.name)), vs = fn.params.map(q => q.name), params = [...new Set(fn.paramFns)];
|
|
235
|
+
return { fn, law: { name: `${fn.name} is ${c.name}`, vars: fn.params.map(q => ({ name: q.name, sort: q.sort })), holds: { op: '=', a: { op: 'call', fn: fn.name, args: vs.map(v) }, b: { op: 'call', fn: c.name, args: vs.map(v) } }, params } };
|
|
236
|
+
});
|
|
237
|
+
const p = programOf([l, lstar, ...bridges.map(b => b.law), ...equivs.map(e => e.law)]);
|
|
238
|
+
return { p, lstar, bridges, equivs, copies, unfolded: unfold };
|
|
239
|
+
}
|
|
240
|
+
// --- the proof ---------------------------------------------------------------------------------------------------------
|
|
241
|
+
const k = (value) => ({ op: 'const', value });
|
|
242
|
+
const yes = { op: '<=', a: k(0), b: k(0) };
|
|
243
|
+
const top = (pr) => Math.max(0, ...pr.nodes.map(n => (n.rank[0]?.op === 'const' ? n.rank[0].value : 0)));
|
|
244
|
+
const verify = (fs, specs, until) => {
|
|
245
|
+
const was = pairedKeys.skip;
|
|
246
|
+
pairedKeys.skip = true;
|
|
247
|
+
try {
|
|
248
|
+
return withDeadline(until, () => proveCyclic(fs.facts, specs));
|
|
249
|
+
}
|
|
250
|
+
finally {
|
|
251
|
+
pairedKeys.skip = was;
|
|
252
|
+
}
|
|
253
|
+
};
|
|
254
|
+
// The law through its plan: the bridges proven (those not proven left out, and the rest built again), the copies' laws,
|
|
255
|
+
// the law said through them, and the node from that to the law. Undefined where any of it isn't proven.
|
|
256
|
+
export function proveThrough(l, plan, opts, until) {
|
|
257
|
+
const t0 = performance.now();
|
|
258
|
+
let bridged = new Map(plan.bridges), tried = 0;
|
|
259
|
+
for (;;) {
|
|
260
|
+
if (clock() > until)
|
|
261
|
+
return undefined;
|
|
262
|
+
// (unfolded where that stays within its size, else the bridges alone)
|
|
263
|
+
const b = (plan.unfolds ? build(l, bridged, true) : undefined) ?? (bridged.size ? build(l, bridged, false) : undefined);
|
|
264
|
+
if (!b)
|
|
265
|
+
return undefined;
|
|
266
|
+
// One chain of nodes: each bridge (by the search), each copy's law (its node made, or by the search), in order.
|
|
267
|
+
const fs = factsOf(b.p), chain = [], nodes = [], nodeOf = new Map();
|
|
268
|
+
const add = (pr) => { chain.push(pr); nodes.splice(0, nodes.length, ...pr.nodes); nodeOf.set(pr.law.name, nodes.findIndex(n => n.name === pr.spec.name)); };
|
|
269
|
+
const ctx = () => ({ p: b.p, fs, base: [...chain], level: chain.length ? top(chain.at(-1)) : 0 });
|
|
270
|
+
const lost = [];
|
|
271
|
+
for (const x of b.bridges) {
|
|
272
|
+
const r = searchIn(x.law, opts, Math.min(until, clock() + 6000 * (opts.patience ?? 1)), ctx());
|
|
273
|
+
tried += r.verdict.tried;
|
|
274
|
+
if (r.out)
|
|
275
|
+
add(r.out);
|
|
276
|
+
else
|
|
277
|
+
lost.push(x.fn.name);
|
|
278
|
+
}
|
|
279
|
+
if (lost.length) {
|
|
280
|
+
for (const n of lost)
|
|
281
|
+
bridged.delete(n);
|
|
282
|
+
if (!bridged.size && !plan.unfolds)
|
|
283
|
+
return undefined;
|
|
284
|
+
continue;
|
|
285
|
+
}
|
|
286
|
+
for (const x of b.equivs) {
|
|
287
|
+
const level = (chain.length ? top(chain.at(-1)) : 0) + 1, made = copyNode(x, b, fs, nodeOf, nodes.length, level);
|
|
288
|
+
const checked = made && verify(fs, [...nodes, made], Math.min(until, clock() + 4000));
|
|
289
|
+
if (made && checked && checked.proven && checked.checked) {
|
|
290
|
+
add({ law: x.law, spec: made, nodes: [...nodes, made] });
|
|
291
|
+
continue;
|
|
292
|
+
}
|
|
293
|
+
const r = searchIn(x.law, { ...opts, lemmas: [] }, Math.min(until, clock() + 8000 * (opts.patience ?? 1)), ctx());
|
|
294
|
+
tried += r.verdict.tried;
|
|
295
|
+
if (!r.out)
|
|
296
|
+
return undefined;
|
|
297
|
+
add(r.out);
|
|
298
|
+
}
|
|
299
|
+
// (through the bridges and unfolded: half the time left, the other half through the unfolding alone, the functions
|
|
300
|
+
// as written)
|
|
301
|
+
const again = b.unfolded && bridged.size > 0;
|
|
302
|
+
const r = searchIn(b.lstar, opts, again ? clock() + (until - clock()) / 2 : until, { p: b.p, fs, base: [], level: 0 });
|
|
303
|
+
tried += r.verdict.tried;
|
|
304
|
+
if (!r.out && again && clock() < until) {
|
|
305
|
+
bridged = new Map();
|
|
306
|
+
continue;
|
|
307
|
+
}
|
|
308
|
+
if (!r.out)
|
|
309
|
+
return { ...r.verdict, proven: false, checked: false, law: l.name, counterexample: undefined, tried };
|
|
310
|
+
// The nodes: the chain's, then the law's said through them, then the law's own.
|
|
311
|
+
const specs = [...nodes], names = new Set(specs.map(n => n.name)), off = specs.length;
|
|
312
|
+
for (const n of r.out.nodes) {
|
|
313
|
+
let name = n.name;
|
|
314
|
+
for (let i = 2; names.has(name); i++)
|
|
315
|
+
name = `${n.name} (${i})`;
|
|
316
|
+
names.add(name);
|
|
317
|
+
specs.push({ ...n, name, uses: n.uses.map(u => ({ ...u, node: u.node + off })) });
|
|
318
|
+
}
|
|
319
|
+
const lnode = off + r.out.nodes.indexOf(r.out.spec);
|
|
320
|
+
const node = lawNode(l, b, fs, nodeOf, lnode, Math.max(top(r.out), ...chain.map(top)) + 1);
|
|
321
|
+
if (!node)
|
|
322
|
+
return undefined;
|
|
323
|
+
specs.push(node);
|
|
324
|
+
const v = verify(fs, specs, Math.max(until, clock() + 3000));
|
|
325
|
+
if (!v.proven || !v.checked)
|
|
326
|
+
return undefined;
|
|
327
|
+
const through = [...b.bridges.map(x => `${x.fn.name} is ${x.op.name}`), ...b.equivs.map(x => x.law.name), ...(b.unfolded ? [`unfolded: ${programOf([l]).fns.filter(f => !recursive(f)).map(f => f.name).join(', ')}`] : [])];
|
|
328
|
+
return { law: l.name, proven: true, checked: true, proof: v, lemmas: [...b.bridges.map(x => x.law.name), ...b.equivs.map(x => x.law.name), ...r.verdict.lemmas], scheme: `${r.verdict.scheme ?? 'no induction'}, through the law ${b.lstar.name}`, tried, program: b.p, node: specs.length - 1, ms: Math.round(performance.now() - t0), through };
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
// Two terms, one of the law (or a function's body) and one said through the bridges, walked together: where they read
|
|
332
|
+
// different things, the reason they are equal, recorded (a bridge's or a copy's law at the call's arguments, an unfolded
|
|
333
|
+
// function's fact there, a congruence of a map at two keys). `own`: a function whose read is its copy's by the node's own
|
|
334
|
+
// induction (a copy's law: its recursive calls, given by the uses of its scheme).
|
|
335
|
+
function walker(b, fs, nodeOf, own) {
|
|
336
|
+
const p = b.p, insts = [], uses = [], seen = new Set();
|
|
337
|
+
const bridgeOf = new Map(b.bridges.map(x => [x.fn.name, x])), copyOf = new Map(b.equivs.map(x => [x.fn.name, x]));
|
|
338
|
+
const id = (t) => JSON.stringify(t);
|
|
339
|
+
const inst = (fact, at) => { const i = fs.at.get(fact); if (i === undefined)
|
|
340
|
+
return false; const key = id([i, at]); if (!seen.has(key)) {
|
|
341
|
+
seen.add(key);
|
|
342
|
+
insts.push({ fact: i, at });
|
|
343
|
+
} return true; };
|
|
344
|
+
const use = (law, subst) => { const node = nodeOf.get(law); if (node === undefined)
|
|
345
|
+
return false; const key = id([node, subst]); if (!seen.has(key)) {
|
|
346
|
+
seen.add(key);
|
|
347
|
+
uses.push({ node, subst, guard: yes });
|
|
348
|
+
} return true; };
|
|
349
|
+
const factBody = (f, as) => {
|
|
350
|
+
const holds = fs.facts[fs.at.get(`fn ${f.name}`)].holds, eq = (holds.op === 'and' ? holds.xs[0] : holds);
|
|
351
|
+
return substE(eq.b, { vars: Object.fromEntries(f.params.map((q, i) => [`${f.name}.${q.name}`, as[i]])), maps: {}, flags: {} });
|
|
352
|
+
};
|
|
353
|
+
// A read of the law's side one step closer to the other side (the reason recorded), or undefined.
|
|
354
|
+
const step = (a) => {
|
|
355
|
+
const f = fnNamed(a.map), n = fs.arity.get(a.map) ?? 1, as = argsOf(a.key, n);
|
|
356
|
+
if (!f || !as)
|
|
357
|
+
return undefined;
|
|
358
|
+
const br = bridgeOf.get(f.name);
|
|
359
|
+
if (br) {
|
|
360
|
+
if (!use(br.law.name, Object.fromEntries(f.params.map((q, i) => [q.name, as[i]]))))
|
|
361
|
+
return undefined;
|
|
362
|
+
return substE(encX(p, br.op.x({ op: 'var', name: 'a$' }, { op: 'var', name: 'b$' }), new Map()), { vars: { 'a$': as[0], 'b$': as[1] ?? k(0) }, maps: {}, flags: {} });
|
|
363
|
+
}
|
|
364
|
+
if (!recursive(f) && b.unfolded)
|
|
365
|
+
return inst(`fn ${f.name}`, as) ? factBody(f, as) : undefined;
|
|
366
|
+
const copy = b.copies.get(f.name);
|
|
367
|
+
if (!copy)
|
|
368
|
+
return undefined;
|
|
369
|
+
if (f.name === own)
|
|
370
|
+
return { op: 'get', map: copy, key: a.key };
|
|
371
|
+
const cp = copyOf.get(f.name);
|
|
372
|
+
return cp && use(cp.law.name, Object.fromEntries(f.params.map((q, i) => [q.name, as[i]]))) ? { op: 'get', map: copy, key: a.key } : undefined;
|
|
373
|
+
};
|
|
374
|
+
const walkE = (a, c) => {
|
|
375
|
+
if (id(a) === id(c))
|
|
376
|
+
return true;
|
|
377
|
+
if (a.op === 'get') {
|
|
378
|
+
if (c.op === 'get' && c.map === a.map) {
|
|
379
|
+
const n = fs.arity.get(a.map) ?? 1, as = argsOf(a.key, n), cs = argsOf(c.key, n);
|
|
380
|
+
if (!as || !cs || !as.every((x, i) => walkE(x, cs[i])))
|
|
381
|
+
return false;
|
|
382
|
+
return inst(`cong ${a.map}`, [...as, ...cs]);
|
|
383
|
+
}
|
|
384
|
+
const next = step(a);
|
|
385
|
+
return !!next && walkE(next, c);
|
|
386
|
+
}
|
|
387
|
+
if (a.op !== c.op)
|
|
388
|
+
return false;
|
|
389
|
+
switch (a.op) {
|
|
390
|
+
case 'add':
|
|
391
|
+
case 'sub':
|
|
392
|
+
case 'min':
|
|
393
|
+
case 'max':
|
|
394
|
+
case 'prod': {
|
|
395
|
+
const o = c;
|
|
396
|
+
return walkE(a.a, o.a) && walkE(a.b, o.b);
|
|
397
|
+
}
|
|
398
|
+
case 'mul': {
|
|
399
|
+
const o = c;
|
|
400
|
+
return a.k === o.k && walkE(a.a, o.a);
|
|
401
|
+
}
|
|
402
|
+
case 'if': {
|
|
403
|
+
const o = c;
|
|
404
|
+
return walkC(a.c, o.c) && walkE(a.then, o.then) && walkE(a.else, o.else);
|
|
405
|
+
}
|
|
406
|
+
case 'var':
|
|
407
|
+
case 'const': return false;
|
|
408
|
+
}
|
|
409
|
+
};
|
|
410
|
+
const walkC = (a, c) => {
|
|
411
|
+
if (id(a) === id(c))
|
|
412
|
+
return true;
|
|
413
|
+
// A condition made a value, read as the condition (`holds`): f(..) = 1 where f's read becomes if (cond, 1, 0).
|
|
414
|
+
if (a.op === '=' && a.b.op === 'const' && a.b.value === 1 && (a.a.op === 'get' || a.a.op === 'if') && !(c.op === '=' && c.a.op === a.a.op && (c.a.op !== 'get' || a.a.op !== 'get' || c.a.map === a.a.map))) {
|
|
415
|
+
let x = a.a;
|
|
416
|
+
while (x && x.op === 'get' && !(c.op === '=' && c.a.op === 'get' && c.a.map === x.map))
|
|
417
|
+
x = step(x);
|
|
418
|
+
if (x && x.op === 'if' && x.then.op === 'const' && x.then.value === 1 && x.else.op === 'const' && x.else.value === 0 && walkC(x.c, c))
|
|
419
|
+
return true;
|
|
420
|
+
}
|
|
421
|
+
if (a.op !== c.op)
|
|
422
|
+
return false;
|
|
423
|
+
switch (a.op) {
|
|
424
|
+
case '<':
|
|
425
|
+
case '<=':
|
|
426
|
+
case '=': {
|
|
427
|
+
const o = c;
|
|
428
|
+
return walkE(a.a, o.a) && walkE(a.b, o.b);
|
|
429
|
+
}
|
|
430
|
+
case 'and':
|
|
431
|
+
case 'or': {
|
|
432
|
+
const o = c;
|
|
433
|
+
return a.xs.length === o.xs.length && a.xs.every((x, i) => walkC(x, o.xs[i]));
|
|
434
|
+
}
|
|
435
|
+
case 'not': return walkC(a.x, c.x);
|
|
436
|
+
case 'all':
|
|
437
|
+
case 'flag': return false;
|
|
438
|
+
}
|
|
439
|
+
};
|
|
440
|
+
return { walkC, walkE, insts, uses, inst };
|
|
441
|
+
}
|
|
442
|
+
// The law's node: its conclusion, using the law said through the bridges (at its own variables) and the walk's reasons.
|
|
443
|
+
function lawNode(l, b, fs, nodeOf, lnode, level) {
|
|
444
|
+
const w = walker(b, fs, nodeOf), concl = encY(b.p, l.holds, new Map());
|
|
445
|
+
if (!w.walkC(concl, encY(b.p, b.lstar.holds, new Map())))
|
|
446
|
+
return undefined;
|
|
447
|
+
return { name: `${l.name} (through ${b.lstar.name})`, hyps: [], concl, rank: [k(level), k(0), k(0)], insts: w.insts, uses: [{ node: lnode, subst: {}, guard: yes }, ...w.uses] };
|
|
448
|
+
}
|
|
449
|
+
// A copy's law, f(xs) = f'(xs), as one node at index `self`: both definitions' facts at xs, their bodies walked together
|
|
450
|
+
// (f's own recursive calls its copy's by the node itself, at the uses of f's recursion scheme), the shape of the argument
|
|
451
|
+
// the recursion is on (its rank, its size).
|
|
452
|
+
function copyNode(x, b, fs, nodeOf, self, level) {
|
|
453
|
+
const f = x.fn, copy = b.copies.get(f.name), vs = f.params.map(q => ({ op: 'var', name: q.name }));
|
|
454
|
+
const w = walker(b, fs, nodeOf, f.name);
|
|
455
|
+
if (!w.inst(`fn ${f.name}`, vs) || !w.inst(`fn ${copy}`, vs))
|
|
456
|
+
return undefined;
|
|
457
|
+
const body = (g) => { const h = fs.facts[fs.at.get(`fn ${g}`)].holds, eq = (h.op === 'and' ? h.xs[0] : h); return substE(eq.b, { vars: Object.fromEntries(f.params.map((q, i) => [`${g}.${q.name}`, vs[i]])), maps: {}, flags: {} }); };
|
|
458
|
+
if (!w.walkE(body(f.name), body(copy)))
|
|
459
|
+
return undefined;
|
|
460
|
+
const scheme = recursive(f) ? schemesOf(b.p, x.law).find(sc => sc.name.startsWith(`recursion of ${f.name} `)) : undefined;
|
|
461
|
+
if (recursive(f) && !scheme)
|
|
462
|
+
return undefined;
|
|
463
|
+
const on = scheme?.on, size = !on ? k(0) : on.d === 'nat' ? { op: 'var', name: on.v } : { op: 'get', map: `${on.d.name}.size`, key: { op: 'var', name: on.v } };
|
|
464
|
+
if (on && on.d !== 'nat')
|
|
465
|
+
w.inst(`shape ${on.d.name}`, [{ op: 'var', name: on.v }]);
|
|
466
|
+
return { name: `${x.law.name} (its copy, made)`, hyps: [], concl: encY(b.p, x.law.holds, new Map()), rank: [k(level), size, k(0)], insts: w.insts,
|
|
467
|
+
uses: [...(scheme?.uses ?? []).map(u => ({ node: self, subst: u.subst, guard: u.guard })), ...w.uses] };
|
|
468
|
+
}
|
package/dist/core/watch.js
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
// and flags; `then` with the state before the first step (plain names), the first request and its flags, the j-th
|
|
9
9
|
// request as input@j, and the state and maps after the last step (primed names).
|
|
10
10
|
import { carriedView, evalC, numbersOf } from "./machine.js";
|
|
11
|
+
import { someHolds } from "./exists.js";
|
|
11
12
|
export const isStepLaw = (w) => 'then' in w;
|
|
12
13
|
// A request's environment: the params, its inputs and queries, and the step's flags decided (as ?name).
|
|
13
14
|
export function requestEnv(m, run, params, st, input) {
|
|
@@ -43,7 +44,10 @@ export function watch(m, run, w, params, bound) {
|
|
|
43
44
|
return go(0, {});
|
|
44
45
|
};
|
|
45
46
|
if (!isStepLaw(w)) {
|
|
46
|
-
|
|
47
|
+
// A law with "there exists" (core/exists.ts): broken only where no value can be a witness, as far as that is decided
|
|
48
|
+
// (one natural or integer read linearly); undecided, it is kept.
|
|
49
|
+
const at = (env, st) => (w.some?.length ? someHolds(w.holds, w.some, env, st, m)?.holds ?? true : evalC(w.holds, env, st, m));
|
|
50
|
+
const holds = (st) => atKeys(w.forall, keysOf(bound, st), extra => at({ ...params, ...extra }, st), w.keys);
|
|
47
51
|
return { start: holds, step: (_before, _input, after) => holds(after) };
|
|
48
52
|
}
|
|
49
53
|
const k = w.steps ?? 1, whens = w.when === undefined ? [] : Array.isArray(w.when) ? w.when : [w.when];
|
package/dist/kernel/prove.d.ts
CHANGED
|
@@ -66,6 +66,9 @@ export type Report = {
|
|
|
66
66
|
};
|
|
67
67
|
export declare function proveLaws(m: Machine, laws: Law[]): Report & {
|
|
68
68
|
laws?: Law[];
|
|
69
|
+
machine?: Machine;
|
|
70
|
+
ghosts?: string[];
|
|
71
|
+
witnesses?: Record<string, string>;
|
|
69
72
|
proof?: {
|
|
70
73
|
instances: Record<string, Instances>;
|
|
71
74
|
trees: Record<string, Tree>;
|
|
@@ -101,8 +104,9 @@ type StepProof = {
|
|
|
101
104
|
perStep?: Record<string, Instances>;
|
|
102
105
|
trees: Record<string, Tree>;
|
|
103
106
|
};
|
|
104
|
-
export declare function proveStepLaws(
|
|
107
|
+
export declare function proveStepLaws(given0: Machine, given: Law[], stepLaws: StepLaw[]): Report & {
|
|
105
108
|
laws?: Law[];
|
|
109
|
+
machine?: Machine;
|
|
106
110
|
stepLaws?: StepLaw[];
|
|
107
111
|
proof?: StepProof;
|
|
108
112
|
};
|
package/dist/kernel/prove.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { add, k, refuteFast, scale, solve, sub, v, work } from "../core/solver.js";
|
|
2
2
|
import { checkRefutation } from "../core/check.js";
|
|
3
3
|
import { allowNode, allowTranslated, certCyclic, certOn, certSearch, closingInsts, crossCyclic, cyclicOn, cycLog, hasQuantified, machineNodes, proveCyclic, reqTS, searchedStepNodes, skMachine, timedKind, withWitness, translatedProof, uncovered, uncoveredWhy } from "../core/cyclic.js";
|
|
4
|
+
import { proveSome, someLaws } from "../core/exists.js";
|
|
4
5
|
import { assumed, atomOf, boundOf, canon, canonFlip, columnTerm, decided, factorsOf, isCell, isMonomial, stepZone, check, checkLaws, checkStepLaws, historyMachine, leafConstraints, leafConstraintsReusing, reusing, linear, livenessHelpful, livenessParts, obligations, plainKey, progressObligations, stepLawObligations, trueC, truth, unreachable, witnessesOf, withProducts } from "./kernel.js";
|
|
5
6
|
// The atoms the path leaves open, in evaluation order: deciding any of them makes progress.
|
|
6
7
|
function openE(e, path, out) {
|
|
@@ -1346,6 +1347,9 @@ function chooseWitnesses(items, body, names, m, proves, rebuild) {
|
|
|
1346
1347
|
return { items: resolved(true) };
|
|
1347
1348
|
}
|
|
1348
1349
|
export function proveLaws(m, laws) {
|
|
1350
|
+
// Laws with "there exists" (core/exists.ts): proven through witnesses, as plain laws of the machine with its ghosts.
|
|
1351
|
+
if (someLaws(laws))
|
|
1352
|
+
return proveSome(m, laws);
|
|
1349
1353
|
if (!laws.some(l => someKeys(l.holds).length))
|
|
1350
1354
|
return proveLawsAsGiven(m, laws);
|
|
1351
1355
|
const out = chooseWitnesses(laws, l => l.holds, l => (l.forall ? [l.forall] : []), m, ls => { const r = proveLawsAsGiven(m, ls); return r.proven && r.checked; }, (l, holds) => ({ ...l, holds, stated: l.stated ?? l.holds }));
|
|
@@ -1357,7 +1361,7 @@ export function proveLaws(m, laws) {
|
|
|
1357
1361
|
// yet); not a proof of the set, which proveLaws gives. Laws with some key go through proveLaws.
|
|
1358
1362
|
export function proveLaw(m, laws, i) {
|
|
1359
1363
|
const law = laws[i];
|
|
1360
|
-
if (laws.some(l => someKeys(l.holds).length)) {
|
|
1364
|
+
if (laws.some(l => someKeys(l.holds).length) || someLaws(laws)) {
|
|
1361
1365
|
const r = proveLaws(m, laws);
|
|
1362
1366
|
return r.proven ? { proven: true } : { proven: false, goal: r.failed.goal, stuck: r.failed.stuck, ...(r.failed.witness ? { witness: r.failed.witness } : {}) };
|
|
1363
1367
|
}
|
|
@@ -1497,20 +1501,22 @@ function proveWith(m, laws, instances) {
|
|
|
1497
1501
|
const proof = { instances, trees };
|
|
1498
1502
|
return { proven: true, nodes, leaves, checked: !usualCheck.on || checkLaws(m, laws, proof), proof };
|
|
1499
1503
|
}
|
|
1500
|
-
export function proveStepLaws(
|
|
1501
|
-
const base = proveLaws(
|
|
1504
|
+
export function proveStepLaws(given0, given, stepLaws) {
|
|
1505
|
+
const base = proveLaws(given0, given);
|
|
1502
1506
|
if (!base.proven || !base.proof)
|
|
1503
1507
|
return { proven: false, nodes: base.nodes, leaves: base.leaves, ...(base.failed ? { failed: base.failed } : {}), checked: false };
|
|
1504
1508
|
const laws = base.laws ?? given;
|
|
1509
|
+
// (laws with "there exists": the step laws over the machine with the ghosts their witnesses are)
|
|
1510
|
+
const m = base.machine ?? given0, mx = base.machine ? { machine: base.machine } : {};
|
|
1505
1511
|
if (stepLaws.some(sl => someKeys(sl.then).length)) {
|
|
1506
1512
|
// Each step law's witnesses on its own (a step law assumes no other), the request and the state after among them.
|
|
1507
1513
|
const plain = m.history ? historyMachine(m).machine : m, after = Object.keys(plain.state).map(n => `${n}'`);
|
|
1508
1514
|
const out = chooseWitnesses(stepLaws, sl => sl.then, sl => [...(sl.forall ? [sl.forall] : []), ...plain.inputs, ...after], plain, sls => sls.every(sl => { const r = proveStepLawsAsGiven(m, laws, [sl], base); return r.proven && r.checked; }), (sl, then) => ({ ...sl, then }));
|
|
1509
1515
|
if ('stuck' in out)
|
|
1510
1516
|
return { proven: false, nodes: base.nodes, leaves: base.leaves, failed: { goal: 'witnesses', stuck: out.stuck }, checked: false };
|
|
1511
|
-
return { ...proveStepLawsAsGiven(m, laws, out.items, base), ...(base.laws ? { laws } : {}), stepLaws: out.items };
|
|
1517
|
+
return { ...proveStepLawsAsGiven(m, laws, out.items, base), ...(base.laws ? { laws } : {}), ...mx, stepLaws: out.items };
|
|
1512
1518
|
}
|
|
1513
|
-
return { ...proveStepLawsAsGiven(m, laws, stepLaws, base), ...(base.laws ? { laws } : {}) };
|
|
1519
|
+
return { ...proveStepLawsAsGiven(m, laws, stepLaws, base), ...(base.laws ? { laws } : {}), ...mx };
|
|
1514
1520
|
}
|
|
1515
1521
|
function proveStepLawsAsGiven(m, laws, stepLaws, base) {
|
|
1516
1522
|
if (!base.proof)
|
|
@@ -39,6 +39,8 @@ export declare function readable(id: string, laws: Law[], vars: string[], maps:
|
|
|
39
39
|
hyp: string;
|
|
40
40
|
call: string;
|
|
41
41
|
have?: (j: number) => string;
|
|
42
|
+
lit?: (c: C) => string;
|
|
43
|
+
eLit?: (e: E) => string;
|
|
42
44
|
}): string;
|
|
43
45
|
export type ProgressCase = {
|
|
44
46
|
id: string;
|