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/core/data.js
CHANGED
|
@@ -1,13 +1,22 @@
|
|
|
1
1
|
import { substC, substE } from "../kernel/kernel.js";
|
|
2
2
|
import { proveCyclic } from "./cyclic.js";
|
|
3
|
-
import { pairedKeys, withDeadline } from "../kernel/prove.js";
|
|
3
|
+
import { clock, counted, pairedKeys, withDeadline } from "../kernel/prove.js";
|
|
4
|
+
import { work } from "./solver.js";
|
|
5
|
+
import { ranksOfRuns } from "./cycles.js";
|
|
6
|
+
import { proveThrough, transferPlan } from "./transfer.js";
|
|
4
7
|
// --- sorts and the AST ------------------------------------------------------------------------------------------------
|
|
5
8
|
export const self = { self: true };
|
|
6
9
|
export const natural = 'nat';
|
|
7
10
|
export const boolean = 'bool';
|
|
11
|
+
// A signed integer (Lean's Int): opt-in, beside the naturals, whose truncated subtraction keeps its meaning. Encoded as
|
|
12
|
+
// its zigzag code (z ↦ 2z for z ≥ 0, −2z − 1 below), a bijection with the naturals like a datatype's, so a value is one
|
|
13
|
+
// natural and two values are equal exactly when their codes are; its arithmetic reads a code as two naturals, p − n
|
|
14
|
+
// (from the code's quotient and remainder by 2), and writes the result's code back (`encI`, `codeOfPair`).
|
|
15
|
+
export const integer = 'int';
|
|
16
|
+
export const isDataField = (s) => typeof s === 'object';
|
|
8
17
|
// --- the surface ------------------------------------------------------------------------------------------------------
|
|
9
18
|
let fresh = 0;
|
|
10
|
-
const bindName = (field) => `${field}#${++fresh}`;
|
|
19
|
+
export const bindName = (field) => `${field}#${++fresh}`;
|
|
11
20
|
export class Nat {
|
|
12
21
|
x;
|
|
13
22
|
constructor(x) {
|
|
@@ -16,14 +25,56 @@ export class Nat {
|
|
|
16
25
|
plus(b) { return new Nat({ op: 'add', a: this.x, b: natX(b) }); }
|
|
17
26
|
minus(b) { return new Nat({ op: 'sub', a: this.x, b: natX(b) }); }
|
|
18
27
|
times(k) { return new Nat({ op: 'mul', k, a: this.x }); }
|
|
28
|
+
// A product of two naturals (the kernel multiplies it out; nonlinear: what proves it is products of facts and squares).
|
|
29
|
+
mul(b) { return new Nat({ op: 'prod', a: this.x, b: natX(b) }); }
|
|
30
|
+
modBy(k) { return new Nat({ op: 'mod', k: divisorOf(k), a: this.x }); }
|
|
31
|
+
divBy(k) { return new Nat({ op: 'div', k: divisorOf(k), a: this.x }); }
|
|
32
|
+
toInt() { return new Integer({ op: 'ofnat', a: this.x }); }
|
|
19
33
|
lt(b) { return new Bool({ op: '<', a: this.x, b: natX(b) }); }
|
|
20
34
|
atMost(b) { return new Bool({ op: '<=', a: this.x, b: natX(b) }); }
|
|
21
35
|
gt(b) { return new Bool({ op: '<', a: natX(b), b: this.x }); }
|
|
22
36
|
atLeast(b) { return new Bool({ op: '<=', a: natX(b), b: this.x }); }
|
|
23
37
|
eq(b) { return new Bool({ op: '=', a: this.x, b: natX(b) }); }
|
|
24
38
|
ne(b) { return this.eq(b).not(); }
|
|
39
|
+
// A natural as 0 or the successor of another (`pred`): recursion on a natural passes `pred` where this is a parameter.
|
|
40
|
+
// oxlint-disable-next-line typescript/no-explicit-any
|
|
41
|
+
match(cases) {
|
|
42
|
+
const pred = bindName('pred'), zero = cases.zero(), succ = cases.succ({ pred: new Nat({ op: 'var', name: pred }) });
|
|
43
|
+
const int = zero instanceof Integer || succ instanceof Integer;
|
|
44
|
+
const body = (o) => (typeof o === 'number' ? (int ? intX(o) : natX(o)) : o instanceof Bool ? { op: 'bool', c: o.y } : o.x);
|
|
45
|
+
const x = { op: 'natmatch', of: this.x, zero: body(zero), pred, succ: body(succ) };
|
|
46
|
+
return (zero instanceof Bool ? new Bool({ op: 'holds', x }) : zero instanceof Val ? new Val(zero.data, x) : int ? new Integer(x) : new Nat(x));
|
|
47
|
+
}
|
|
25
48
|
}
|
|
26
49
|
const natX = (t) => (typeof t === 'number' ? { op: 'nat', value: t } : t.x);
|
|
50
|
+
const divisorOf = (k) => { if (!Number.isSafeInteger(k) || k < 1)
|
|
51
|
+
throw new RangeError(`a divisor is a whole number at least 1, not ${k}`); return k; };
|
|
52
|
+
// An integer's term; a number is a literal (any sign).
|
|
53
|
+
export class Integer {
|
|
54
|
+
x;
|
|
55
|
+
constructor(x) {
|
|
56
|
+
this.x = x;
|
|
57
|
+
}
|
|
58
|
+
plus(b) { return new Integer({ op: 'iadd', a: this.x, b: intX(b) }); }
|
|
59
|
+
minus(b) { return new Integer({ op: 'isub', a: this.x, b: intX(b) }); }
|
|
60
|
+
negate() { return new Integer({ op: 'ineg', a: this.x }); }
|
|
61
|
+
times(k) { if (!Number.isSafeInteger(k))
|
|
62
|
+
throw new RangeError(`${k} is not an integer`); return new Integer({ op: 'imul', k, a: this.x }); }
|
|
63
|
+
modBy(k) { return new Integer({ op: 'imod', k: divisorOf(k), a: this.x }); }
|
|
64
|
+
divBy(k) { return new Integer({ op: 'idiv', k: divisorOf(k), a: this.x }); }
|
|
65
|
+
lt(b) { return new Bool({ op: 'i<', a: this.x, b: intX(b) }); }
|
|
66
|
+
atMost(b) { return new Bool({ op: 'i<=', a: this.x, b: intX(b) }); }
|
|
67
|
+
gt(b) { return new Bool({ op: 'i<', a: intX(b), b: this.x }); }
|
|
68
|
+
atLeast(b) { return new Bool({ op: 'i<=', a: intX(b), b: this.x }); }
|
|
69
|
+
eq(b) { return new Bool({ op: '=', a: this.x, b: intX(b) }); }
|
|
70
|
+
ne(b) { return this.eq(b).not(); }
|
|
71
|
+
}
|
|
72
|
+
// An integer function's result: a natural literal in a branch (a match's case, an if's arm) is the integer's.
|
|
73
|
+
const intResult = (x) => (x.op === 'nat' ? { op: 'int', value: x.value } : x.op === 'if' ? { ...x, then: intResult(x.then), else: intResult(x.else) }
|
|
74
|
+
: x.op === 'match' ? { ...x, cases: x.cases.map(c => ({ ...c, body: intResult(c.body) })) } : x.op === 'natmatch' ? { ...x, zero: intResult(x.zero), succ: intResult(x.succ) } : x);
|
|
75
|
+
const intX = (t) => { if (typeof t !== 'number')
|
|
76
|
+
return t.x; if (!Number.isSafeInteger(t))
|
|
77
|
+
throw new RangeError(`${t} is not an integer`); return { op: 'int', value: t }; };
|
|
27
78
|
export class Bool {
|
|
28
79
|
y;
|
|
29
80
|
constructor(y) {
|
|
@@ -53,43 +104,61 @@ export class Val {
|
|
|
53
104
|
if (!f)
|
|
54
105
|
throw new Error(`${d.name}: match has no case ${c.name}`);
|
|
55
106
|
const binds = c.fields.map(fl => bindName(fl.name));
|
|
56
|
-
const fields = Object.fromEntries(c.fields.map((fl, i) => [fl.name, wrap(fl
|
|
107
|
+
const fields = Object.fromEntries(c.fields.map((fl, i) => [fl.name, wrap(fieldSort(fl), { op: 'var', name: binds[i] })]));
|
|
57
108
|
return { binds, out: f(fields) };
|
|
58
109
|
});
|
|
59
110
|
const first = built[0].out;
|
|
60
|
-
|
|
111
|
+
// A case that is an integer makes every case's number an integer's literal.
|
|
112
|
+
const int = built.some(b => b.out instanceof Integer);
|
|
113
|
+
const body = (o) => (typeof o === 'number' ? (int ? intX(o) : natX(o)) : o instanceof Bool ? { op: 'bool', c: o.y } : o.x);
|
|
61
114
|
const x = { op: 'match', data: d.name, of: this.x, cases: built.map(b => ({ binds: b.binds, body: body(b.out) })) };
|
|
62
|
-
return (first instanceof Bool ? new Bool({ op: 'holds', x }) : first instanceof Val ? new Val(first.data, x) : new Nat(x));
|
|
115
|
+
return (first instanceof Bool ? new Bool({ op: 'holds', x }) : first instanceof Val ? new Val(first.data, x) : int ? new Integer(x) : new Nat(x));
|
|
63
116
|
}
|
|
64
117
|
is(ctor) { return new Bool({ op: 'is', data: this.data.name, of: this.x, ctor: this.data.index(ctor) }); }
|
|
118
|
+
// Its size: 1 plus the sizes of its fields of its datatype's group (a list's length plus 1, a tree's nodes and leaves).
|
|
119
|
+
size() { return new Nat({ op: 'size', data: this.data.name, of: this.x }); }
|
|
65
120
|
eq(b) { return new Bool({ op: '=', a: this.x, b: b.x }); }
|
|
66
121
|
ne(b) { return this.eq(b).not(); }
|
|
67
122
|
}
|
|
68
123
|
// A value of a sort from its term.
|
|
69
|
-
export const wrap = (s, x) => (s === 'nat' ? new Nat(x) : s === 'bool' ? new Bool({ op: 'holds', x }) : new Val(s, x));
|
|
70
|
-
const xOf = (s, v) => (s === 'nat' ? natX(v) : s === 'bool' ? { op: 'bool', c: v.y } : v.x);
|
|
124
|
+
export const wrap = (s, x) => (s === 'nat' ? new Nat(x) : s === 'bool' ? new Bool({ op: 'holds', x }) : s === 'int' ? new Integer(x) : new Val(s, x));
|
|
125
|
+
const xOf = (s, v) => (s === 'nat' ? natX(v) : s === 'bool' ? { op: 'bool', c: v.y } : s === 'int' ? intX(v) : v.x);
|
|
71
126
|
export function when(c, a, b) {
|
|
72
127
|
if (a instanceof Bool)
|
|
73
128
|
return new Bool({ op: 'or', xs: [{ op: 'and', xs: [c.y, a.y] }, { op: 'and', xs: [{ op: 'not', x: c.y }, b.y] }] });
|
|
74
|
-
const
|
|
129
|
+
const int = a instanceof Integer || b instanceof Integer, lit = (t) => (int ? intX(t) : natX(t));
|
|
130
|
+
const ax = typeof a === 'number' ? lit(a) : a.x, bx = typeof b === 'number' ? lit(b) : b.x;
|
|
75
131
|
const x = { op: 'if', c: c.y, then: ax, else: bx };
|
|
76
|
-
return (a instanceof Val ? new Val(a.data, x) : new Nat(x));
|
|
132
|
+
return (a instanceof Val ? new Val(a.data, x) : int ? new Integer(x) : new Nat(x));
|
|
77
133
|
}
|
|
78
|
-
// A datatype: its constructors, each with fields that are naturals or values of
|
|
79
|
-
// constructors are ordered as Lean's bijection numbers them: those of no field, then
|
|
80
|
-
//
|
|
134
|
+
// A datatype: its constructors, each with fields that are naturals or values of a datatype (`self`, another of its group,
|
|
135
|
+
// or one declared before it). Its constructors are ordered as Lean's bijection numbers them: those of no field, then
|
|
136
|
+
// those with no field of its group, then the rest; a group with a datatype that has no value is refused.
|
|
137
|
+
const declared = new Map();
|
|
138
|
+
let declaredSeq = 0;
|
|
139
|
+
export const dataNamed = (name) => { const d = declared.get(name); if (!d)
|
|
140
|
+
throw new Error(`no datatype ${name}`); return d; };
|
|
141
|
+
// A field's sort; whether it is of the datatype's group (the fields its size counts, and an induction descends into).
|
|
142
|
+
export const fieldSort = (f) => (f.sort === 'nat' || f.sort === 'int' ? f.sort : dataNamed(f.sort.data));
|
|
143
|
+
export const inGroup = (d, f) => isDataField(f.sort) && d.group.includes(f.sort.data);
|
|
81
144
|
export class Data {
|
|
82
145
|
name;
|
|
146
|
+
group;
|
|
83
147
|
ctors;
|
|
84
|
-
|
|
148
|
+
seq = ++declaredSeq;
|
|
149
|
+
constructor(name, decl, group = [name]) {
|
|
85
150
|
this.name = name;
|
|
86
|
-
|
|
87
|
-
const rank = (c) => (!c.fields.length ? 0 : c.fields.every(f => f.sort === 'nat') ? 1 : 2);
|
|
88
|
-
this.ctors = [...all].sort((a, b) => rank(a) - rank(b));
|
|
89
|
-
if (!this.ctors.some(c => rank(c) < 2))
|
|
90
|
-
throw new Error(`${name}: every constructor has a field of ${name}, so it has no value`);
|
|
151
|
+
this.group = group;
|
|
91
152
|
if (!/^[A-Za-z][A-Za-z0-9_]*$/.test(name))
|
|
92
153
|
throw new Error(`${name}: a datatype's name is letters and digits`);
|
|
154
|
+
const all = Object.entries(decl).map(([c, fs]) => ({ name: c, fields: Object.entries(fs).map(([f, s]) => ({ name: f, sort: s })) }));
|
|
155
|
+
for (const c of all)
|
|
156
|
+
for (const f of c.fields)
|
|
157
|
+
if (isDataField(f.sort) && !group.includes(f.sort.data) && !declared.has(f.sort.data))
|
|
158
|
+
throw new Error(`${name}.${c.name}.${f.name}: no datatype ${f.sort.data}`);
|
|
159
|
+
const rank = (c) => (!c.fields.length ? 0 : c.fields.every(f => !(isDataField(f.sort) && group.includes(f.sort.data))) ? 1 : 2);
|
|
160
|
+
this.ctors = [...all].sort((a, b) => rank(a) - rank(b));
|
|
161
|
+
declared.set(name, this);
|
|
93
162
|
}
|
|
94
163
|
index(ctor) { const i = this.ctors.findIndex(c => c.name === ctor); if (i < 0)
|
|
95
164
|
throw new Error(`${this.name} has no constructor ${ctor}`); return i; }
|
|
@@ -98,32 +167,127 @@ export class Data {
|
|
|
98
167
|
const j = this.index(ctor), c = this.ctors[j];
|
|
99
168
|
if (args.length !== c.fields.length)
|
|
100
169
|
throw new Error(`${this.name}.${ctor} takes ${c.fields.length} fields`);
|
|
101
|
-
return new Val(this, { op: 'ctor', data: this.name, ctor: j, args: c.fields.map((f, i) => xOf(f
|
|
170
|
+
return new Val(this, { op: 'ctor', data: this.name, ctor: j, args: c.fields.map((f, i) => xOf(fieldSort(f), args[i])) });
|
|
171
|
+
}
|
|
172
|
+
// How many constructors deep its smallest value is (a constructor of no field of the group: 0), and each constructor's.
|
|
173
|
+
ctorHeight(c) { return c.fields.reduce((h, f) => (inGroup(this, f) ? Math.max(h, 1 + dataNamed(f.sort.data).height) : h), 0); }
|
|
174
|
+
get height() { return groupHeights(this.group).get(this.name); }
|
|
175
|
+
}
|
|
176
|
+
// The heights of a group's datatypes (least fixpoint; Infinity where a datatype has no value).
|
|
177
|
+
function groupHeights(group) {
|
|
178
|
+
const h = new Map(group.map(n => [n, Infinity]));
|
|
179
|
+
for (let changed = true; changed;) {
|
|
180
|
+
changed = false;
|
|
181
|
+
for (const n of group)
|
|
182
|
+
for (const c of dataNamed(n).ctors) {
|
|
183
|
+
const v = c.fields.reduce((m, f) => (isDataField(f.sort) && group.includes(f.sort.data) ? Math.max(m, 1 + h.get(f.sort.data)) : m), 0);
|
|
184
|
+
if (v < h.get(n)) {
|
|
185
|
+
h.set(n, v);
|
|
186
|
+
changed = true;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
102
189
|
}
|
|
190
|
+
return h;
|
|
191
|
+
}
|
|
192
|
+
// A group's checks: each datatype has a value, and Lean's decoding terminates (a datatype every constructor of which has
|
|
193
|
+
// a field of the group and none of no field decodes n at fields up to n itself: those must not call each other in a ring).
|
|
194
|
+
function checkGroup(ds) {
|
|
195
|
+
const hs = groupHeights(ds.map(d => d.name));
|
|
196
|
+
for (const d of ds)
|
|
197
|
+
if (hs.get(d.name) === Infinity)
|
|
198
|
+
throw new Error(`${d.name}: every constructor has a field of ${ds.length > 1 ? 'its group' : d.name} that has no value, so it has no value`);
|
|
199
|
+
void weakOrder(ds);
|
|
200
|
+
}
|
|
201
|
+
// A datatype is weak when its code n may be decoded at fields up to n (no constructor of no field, the first of the rest
|
|
202
|
+
// with a field of the group); the order Lean's measure numbers them in (each weak one after the group's datatypes its
|
|
203
|
+
// fields name), or a refusal.
|
|
204
|
+
export function weakOrder(ds) {
|
|
205
|
+
const weak = (d) => d.nullary === 0 && d.ctors[0].fields.some(f => inGroup(d, f));
|
|
206
|
+
const out = new Map(), busy = new Set();
|
|
207
|
+
const visit = (d) => {
|
|
208
|
+
const had = out.get(d.name);
|
|
209
|
+
if (had !== undefined)
|
|
210
|
+
return had;
|
|
211
|
+
if (!weak(d)) {
|
|
212
|
+
out.set(d.name, 0);
|
|
213
|
+
return 0;
|
|
214
|
+
}
|
|
215
|
+
if (busy.has(d.name))
|
|
216
|
+
throw new Error(`${d.name}: its group's datatypes with no constructor of no field name each other in a ring, which the bijection with the naturals doesn't take`);
|
|
217
|
+
busy.add(d.name);
|
|
218
|
+
const o = 1 + Math.max(0, ...d.ctors.flatMap(c => c.fields.flatMap(f => (inGroup(d, f) && f.sort.data !== d.name ? [visit(dataNamed(f.sort.data))] : []))));
|
|
219
|
+
busy.delete(d.name);
|
|
220
|
+
if (d.ctors.some(c => c.fields.some(f => isDataField(f.sort) && f.sort.data === d.name)) && weak(d))
|
|
221
|
+
throw new Error(`${d.name}: no constructor of no field and its first constructor has a field of ${d.name}, which the bijection with the naturals doesn't take`);
|
|
222
|
+
out.set(d.name, o);
|
|
223
|
+
return o;
|
|
224
|
+
};
|
|
225
|
+
ds.forEach(visit);
|
|
226
|
+
return out;
|
|
103
227
|
}
|
|
104
|
-
|
|
228
|
+
const normField = (own, group) => (s) => {
|
|
229
|
+
if (s === self)
|
|
230
|
+
return { data: own };
|
|
231
|
+
if (s === 'nat' || (s && typeof s === 'object' && 'nat' in s))
|
|
232
|
+
return 'nat';
|
|
233
|
+
if (s === 'int')
|
|
234
|
+
return 'int';
|
|
235
|
+
if (s instanceof Data)
|
|
236
|
+
return { data: s.name };
|
|
237
|
+
if (typeof s === 'string' && group.includes(s))
|
|
238
|
+
return { data: s };
|
|
239
|
+
throw new Error(`${own}: a field is natural, integer, self, a datatype declared before, or a name of its group`);
|
|
240
|
+
};
|
|
241
|
+
const withCtors = (d) => { for (const c of d.ctors)
|
|
242
|
+
Object.defineProperty(d, c.name, c.fields.length ? { value: (...args) => d.make(c.name, ...args) } : { get: () => d.make(c.name) }); };
|
|
243
|
+
// `data('List', {nil: {}, cons: {head: natural, tail: self}})`: `List.nil`, `List.cons(h, t)`, and `xs.match({...})`. A
|
|
244
|
+
// field may be a datatype declared before (`data('Bag', {none: {}, more: {items: List, rest: self}})`).
|
|
105
245
|
export function data(name, decl) {
|
|
106
|
-
const
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
Object.defineProperty(d, c.name, c.fields.length ? { value: (...args) => d.make(c.name, ...args) } : { get: () => d.make(c.name) });
|
|
246
|
+
const d = new Data(name, Object.fromEntries(Object.entries(decl).map(([c, fs]) => [c, Object.fromEntries(Object.entries(fs).map(([f, s]) => [f, normField(name, [name])(s)]))])));
|
|
247
|
+
checkGroup([d]);
|
|
248
|
+
withCtors(d);
|
|
110
249
|
return d;
|
|
111
250
|
}
|
|
251
|
+
// Datatypes declared together, each field naming one of the group by its name (a mutual pair: a rose tree and its
|
|
252
|
+
// forest, `mutual({Rose: {node: {value: natural, kids: 'Forest'}}, Forest: {nil: {}, cons: {head: 'Rose', tail: 'Forest'}}})`).
|
|
253
|
+
// oxlint-disable-next-line typescript/no-explicit-any
|
|
254
|
+
export function mutual(decls) {
|
|
255
|
+
const group = Object.keys(decls);
|
|
256
|
+
const ds = group.map(n => new Data(n, Object.fromEntries(Object.entries(decls[n]).map(([c, fs]) => [c, Object.fromEntries(Object.entries(fs).map(([f, s]) => [f, normField(n, group)(s)]))])), group));
|
|
257
|
+
checkGroup(ds);
|
|
258
|
+
ds.forEach(withCtors);
|
|
259
|
+
return Object.fromEntries(ds.map(d => [d.name, d]));
|
|
260
|
+
}
|
|
112
261
|
const fns = new Map();
|
|
113
|
-
export function define(name, params, ret, body) {
|
|
262
|
+
export function define(name, params, ret, body, opts = {}) {
|
|
114
263
|
if (!/^[A-Za-z][A-Za-z0-9_]*$/.test(name))
|
|
115
264
|
throw new Error(`${name}: a function's name is letters and digits`);
|
|
116
265
|
const ps = Object.entries(params).map(([n, s]) => ({ name: n, sort: s }));
|
|
117
|
-
|
|
118
|
-
|
|
266
|
+
const stated = opts.measure && measureOf(name, ps, opts.measure);
|
|
267
|
+
let built, rec, failed;
|
|
268
|
+
// The recursion, checked once (a refusal kept and given again).
|
|
269
|
+
const got = () => {
|
|
270
|
+
if (failed)
|
|
271
|
+
throw failed;
|
|
272
|
+
try {
|
|
273
|
+
return (rec ??= recursion(fn));
|
|
274
|
+
}
|
|
275
|
+
catch (e) {
|
|
276
|
+
failed = e;
|
|
277
|
+
throw e;
|
|
278
|
+
}
|
|
279
|
+
};
|
|
280
|
+
const fn = { name, params: ps, ret, paramFns: [], stated, body: () => {
|
|
119
281
|
if (!built) {
|
|
120
282
|
const args = Object.fromEntries(ps.map(p => [p.name, wrap(p.sort, { op: 'var', name: `${name}.${p.name}` })]));
|
|
121
|
-
built = xOf(ret, body(args));
|
|
122
|
-
fn.decreases = decreasing(fn, built);
|
|
283
|
+
built = ret === 'int' ? intResult(xOf(ret, body(args))) : xOf(ret, body(args));
|
|
123
284
|
fn.paramFns = [...new Set(paramsIn(built))];
|
|
285
|
+
walk(built, o => { if (o.op === 'call' && !fns.has(o.fn))
|
|
286
|
+
throw new Error(`${name} calls ${String(o.fn)}, which isn't defined`); });
|
|
124
287
|
}
|
|
125
288
|
return built;
|
|
126
|
-
} };
|
|
289
|
+
}, get decreases() { return got().decreases; }, get ring() { return got().ring; }, get measure() { return got().measure; }, get termination() { return got().termination; },
|
|
290
|
+
set rec(r) { rec = r; } };
|
|
127
291
|
fns.set(name, fn);
|
|
128
292
|
const call = (...args) => {
|
|
129
293
|
if (args.length !== ps.length)
|
|
@@ -148,8 +312,8 @@ function walk(t, f) {
|
|
|
148
312
|
for (const v of Object.values(o))
|
|
149
313
|
walk(v, f);
|
|
150
314
|
}
|
|
151
|
-
// The
|
|
152
|
-
function
|
|
315
|
+
// The calls a body makes, each with what its bound names are fields of (a parameter, through matches).
|
|
316
|
+
function callsIn(body) {
|
|
153
317
|
const calls = [];
|
|
154
318
|
const go = (x, fieldsOf) => {
|
|
155
319
|
if (!x || typeof x !== 'object')
|
|
@@ -166,24 +330,368 @@ function decreasing(fn, body) {
|
|
|
166
330
|
c.binds.forEach(b => m.set(b, of)); go(c.body, m); });
|
|
167
331
|
return;
|
|
168
332
|
}
|
|
169
|
-
if (o.op === '
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
333
|
+
if (o.op === 'natmatch') {
|
|
334
|
+
go(o.of, fieldsOf);
|
|
335
|
+
go(o.zero, fieldsOf);
|
|
336
|
+
const of = o.of.op === 'var' ? (fieldsOf.get(o.of.name) ?? o.of.name) : undefined, m = new Map(fieldsOf);
|
|
337
|
+
if (of)
|
|
338
|
+
m.set(o.pred, of);
|
|
339
|
+
go(o.succ, m);
|
|
340
|
+
return;
|
|
341
|
+
}
|
|
342
|
+
if (o.op === 'call')
|
|
343
|
+
calls.push({ fn: o.fn, args: o.args, fieldsOf });
|
|
173
344
|
for (const v of Object.values(o))
|
|
174
345
|
go(v, fieldsOf);
|
|
175
346
|
};
|
|
176
347
|
go(body, new Map());
|
|
177
|
-
|
|
348
|
+
return calls;
|
|
349
|
+
}
|
|
350
|
+
// A function's ring (the functions it calls that call it back, itself among them) and the parameter of each every call
|
|
351
|
+
// within the ring passes a field of, or a refusal. Each of the ring is given its own.
|
|
352
|
+
function recursion(fn) {
|
|
353
|
+
const callees = (f) => [...new Set(callsIn(f.body()).map(c => c.fn))];
|
|
354
|
+
const reach = (from) => { const seen = new Set(), todo = [from]; while (todo.length) {
|
|
355
|
+
const g = todo.pop();
|
|
356
|
+
for (const h of callees(fnOf(g)))
|
|
357
|
+
if (!seen.has(h)) {
|
|
358
|
+
seen.add(h);
|
|
359
|
+
todo.push(h);
|
|
360
|
+
}
|
|
361
|
+
} return seen; };
|
|
362
|
+
const mine = reach(fn.name);
|
|
363
|
+
const ring = [...mine].filter(g => g === fn.name || reach(g).has(fn.name));
|
|
364
|
+
if (!mine.has(fn.name)) {
|
|
365
|
+
fn.rec = { ring: [fn.name] };
|
|
366
|
+
return { ring: [fn.name] };
|
|
367
|
+
}
|
|
368
|
+
const members = [fn.name, ...ring.filter(g => g !== fn.name)].map(fnOf);
|
|
369
|
+
const options = members.map(f => f.params.flatMap((p, i) => (p.sort instanceof Data || p.sort === 'nat' ? [i] : [])));
|
|
370
|
+
const calls = members.map(f => callsIn(f.body()).filter(c => ring.includes(c.fn)));
|
|
371
|
+
// The first choice (by parameter order) every call within the ring meets.
|
|
372
|
+
const choose = (i, chosen) => {
|
|
373
|
+
if (i === members.length) {
|
|
374
|
+
const ok = members.every((f, a) => calls[a].every(c => { const b = members.findIndex(g => g.name === c.fn), arg = c.args[chosen[b]]; return arg.op === 'var' && c.fieldsOf.get(arg.name) === `${f.name}.${f.params[chosen[a]].name}`; }));
|
|
375
|
+
return ok ? chosen : undefined;
|
|
376
|
+
}
|
|
377
|
+
for (const o of options[i]) {
|
|
378
|
+
const r = choose(i + 1, [...chosen, o]);
|
|
379
|
+
if (r)
|
|
380
|
+
return r;
|
|
381
|
+
}
|
|
178
382
|
return undefined;
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
const
|
|
183
|
-
if (
|
|
184
|
-
|
|
383
|
+
};
|
|
384
|
+
const got = fn.stated ? undefined : choose(0, []);
|
|
385
|
+
if (!got) {
|
|
386
|
+
const why = `${fn.name}: not structural recursion (no parameter whose field every recursive call passes${members.length > 1 ? `, in its ring ${members.map(f => f.name).join(', ')}` : ''}${fn.stated ? '; a measure is given' : ''})`;
|
|
387
|
+
if (members.length > 1)
|
|
388
|
+
throw new Error(`${why}, and a ring of functions is taken by structural recursion only`);
|
|
389
|
+
const t = termination(fn, why);
|
|
390
|
+
return { ring: [fn.name], measure: t.measure, termination: t };
|
|
391
|
+
}
|
|
392
|
+
const names = members.map(f => f.name);
|
|
393
|
+
members.forEach((f, a) => { f.rec = { decreases: got[a], ring: names }; });
|
|
394
|
+
return { decreases: got[0], ring: names };
|
|
395
|
+
}
|
|
396
|
+
export const terminationMs = { ms: 10_000 };
|
|
397
|
+
const measurable = (s) => s === 'nat' || s instanceof Data;
|
|
398
|
+
function measureOf(name, ps, m) {
|
|
399
|
+
if (!m.length)
|
|
400
|
+
throw new Error(`${name}: an empty measure`);
|
|
401
|
+
return { comps: m.map(c => {
|
|
402
|
+
const names = typeof c === 'string' ? [c] : c;
|
|
403
|
+
return { coef: ps.map(p => names.filter(n => n === p.name).length), c: names.every(n => ps.some(p => p.name === n && measurable(p.sort))) ? 0 : (() => { throw new Error(`${name}: a measure names its parameters of a datatype or naturals (${names.join(', ')})`); })() };
|
|
404
|
+
}) };
|
|
405
|
+
}
|
|
406
|
+
export const showMeasure = (f, m) => {
|
|
407
|
+
const comp = (c) => {
|
|
408
|
+
const parts = c.coef.flatMap((k, i) => (k ? [`${k < 0 ? '−' : ''}${Math.abs(k) === 1 ? '' : `${Math.abs(k)}·`}${f.params[i].sort === 'nat' ? f.params[i].name : `size(${f.params[i].name})`}`] : []));
|
|
409
|
+
const t = [...parts, ...(c.c ? [String(c.c)] : [])].join(' + ').replace(/\+ −/g, '− ');
|
|
410
|
+
return t || '0';
|
|
411
|
+
};
|
|
412
|
+
return m.comps.length === 1 ? comp(m.comps[0]) : `(${m.comps.map(comp).join(', ')})`;
|
|
413
|
+
};
|
|
414
|
+
// A component at arguments, as a term (its negative part subtracted, truncated as the encoding reads it).
|
|
415
|
+
const sizeX = (s, a) => (s === 'nat' ? a : { op: 'size', data: s.name, of: a });
|
|
416
|
+
export function measureX(f, c, args) {
|
|
417
|
+
const pos = [], neg = [];
|
|
418
|
+
c.coef.forEach((k, i) => { if (!k)
|
|
419
|
+
return; const t = sizeX(f.params[i].sort, args[i]); (k > 0 ? pos : neg).push(Math.abs(k) === 1 ? t : { op: 'mul', k: Math.abs(k), a: t }); });
|
|
420
|
+
if (c.c > 0)
|
|
421
|
+
pos.push({ op: 'nat', value: c.c });
|
|
422
|
+
if (c.c < 0)
|
|
423
|
+
neg.push({ op: 'nat', value: -c.c });
|
|
424
|
+
const sum = (ts) => (ts.length ? ts.reduce((a, b) => ({ op: 'add', a, b })) : { op: 'nat', value: 0 });
|
|
425
|
+
return neg.length ? { op: 'sub', a: sum(pos), b: sum(neg) } : sum(pos);
|
|
426
|
+
}
|
|
427
|
+
const lexY = (as, bs) => (as.length === 1 ? { op: '<', a: as[0], b: bs[0] } : { op: 'or', xs: [{ op: '<', a: as[0], b: bs[0] }, { op: 'and', xs: [{ op: '=', a: as[0], b: bs[0] }, lexY(as.slice(1), bs.slice(1))] }] });
|
|
428
|
+
function callSites(fn) {
|
|
429
|
+
const out = [], yesX = { op: 'bool', c: yes.y };
|
|
430
|
+
const at = (sh, name) => { let t = sh.get(name); while (t && t.op === 'var' && sh.has(t.name))
|
|
431
|
+
t = sh.get(t.name); return t; };
|
|
432
|
+
const goY = (y, w) => {
|
|
433
|
+
switch (y.op) {
|
|
434
|
+
case '<':
|
|
435
|
+
case '<=':
|
|
436
|
+
case '=':
|
|
437
|
+
case 'i<':
|
|
438
|
+
case 'i<=':
|
|
439
|
+
goX(y.a, w);
|
|
440
|
+
goX(y.b, w);
|
|
441
|
+
return;
|
|
442
|
+
case 'and':
|
|
443
|
+
case 'or':
|
|
444
|
+
y.xs.forEach(z => goY(z, w));
|
|
445
|
+
return;
|
|
446
|
+
case 'not':
|
|
447
|
+
goY(y.x, w);
|
|
448
|
+
return;
|
|
449
|
+
case 'is':
|
|
450
|
+
goX(y.of, w);
|
|
451
|
+
return;
|
|
452
|
+
case 'holds':
|
|
453
|
+
goX(y.x, w);
|
|
454
|
+
return;
|
|
455
|
+
}
|
|
456
|
+
};
|
|
457
|
+
const goX = (x, w) => {
|
|
458
|
+
switch (x.op) {
|
|
459
|
+
case 'var':
|
|
460
|
+
case 'nat': return;
|
|
461
|
+
case 'add':
|
|
462
|
+
case 'sub':
|
|
463
|
+
case 'min':
|
|
464
|
+
case 'max':
|
|
465
|
+
case 'prod':
|
|
466
|
+
case 'iadd':
|
|
467
|
+
case 'isub':
|
|
468
|
+
goX(x.a, w);
|
|
469
|
+
goX(x.b, w);
|
|
470
|
+
return;
|
|
471
|
+
case 'mul':
|
|
472
|
+
case 'mod':
|
|
473
|
+
case 'div':
|
|
474
|
+
case 'imul':
|
|
475
|
+
case 'imod':
|
|
476
|
+
case 'idiv':
|
|
477
|
+
case 'ineg':
|
|
478
|
+
case 'ofnat':
|
|
479
|
+
goX(x.a, w);
|
|
480
|
+
return;
|
|
481
|
+
case 'int': return;
|
|
482
|
+
case 'apply':
|
|
483
|
+
goX(x.arg, w);
|
|
484
|
+
return;
|
|
485
|
+
case 'size':
|
|
486
|
+
goX(x.of, w);
|
|
487
|
+
return;
|
|
488
|
+
case 'bool':
|
|
489
|
+
goY(x.c, w);
|
|
490
|
+
return;
|
|
491
|
+
case 'ctor':
|
|
492
|
+
x.args.forEach(a => goX(a, w));
|
|
493
|
+
return;
|
|
494
|
+
case 'call':
|
|
495
|
+
x.args.forEach(a => goX(a, w));
|
|
496
|
+
if (x.fn === fn.name)
|
|
497
|
+
out.push({ args: x.args, ...w });
|
|
498
|
+
return;
|
|
499
|
+
case 'if':
|
|
500
|
+
goY(x.c, w);
|
|
501
|
+
goX(x.then, { ...w, wrap: y => w.wrap({ op: 'or', xs: [{ op: 'not', x: x.c }, y] }) });
|
|
502
|
+
goX(x.else, { ...w, wrap: y => w.wrap({ op: 'or', xs: [x.c, y] }) });
|
|
503
|
+
return;
|
|
504
|
+
case 'match': {
|
|
505
|
+
goX(x.of, w);
|
|
506
|
+
const d = dataNamed(x.data), v = x.of.op === 'var' ? x.of.name : undefined, had = v ? at(w.shape, v) : undefined;
|
|
507
|
+
x.cases.forEach((c, j) => {
|
|
508
|
+
if (v && had?.op === 'ctor') {
|
|
509
|
+
// matched before on this path: the same case, its fields the ones bound then; another, never reached
|
|
510
|
+
if (had.ctor === j)
|
|
511
|
+
goX(c.body, { ...w, shape: new Map([...w.shape, ...c.binds.map((b, i) => [b, had.args[i]])]) });
|
|
512
|
+
return;
|
|
513
|
+
}
|
|
514
|
+
if (v && !had) {
|
|
515
|
+
const fields = c.binds.map((b, i) => ({ name: b, sort: fieldSort(d.ctors[j].fields[i]) }));
|
|
516
|
+
goX(c.body, { wrap: w.wrap, shape: new Map([...w.shape, [v, { op: 'ctor', data: x.data, ctor: j, args: c.binds.map(b => ({ op: 'var', name: b })) }]]), vars: [...w.vars, ...fields] });
|
|
517
|
+
return;
|
|
518
|
+
}
|
|
519
|
+
goX(c.body, { ...w, wrap: y => w.wrap({ op: 'holds', x: { op: 'match', data: x.data, of: x.of, cases: x.cases.map((c2, i) => ({ binds: c2.binds, body: i === j ? { op: 'bool', c: y } : yesX })) } }) });
|
|
520
|
+
});
|
|
521
|
+
return;
|
|
522
|
+
}
|
|
523
|
+
case 'natmatch': {
|
|
524
|
+
goX(x.of, w);
|
|
525
|
+
const v = x.of.op === 'var' ? x.of.name : undefined;
|
|
526
|
+
if (v && !at(w.shape, v)) {
|
|
527
|
+
goX(x.zero, { ...w, shape: new Map([...w.shape, [v, { op: 'nat', value: 0 }]]) });
|
|
528
|
+
goX(x.succ, { wrap: w.wrap, shape: new Map([...w.shape, [v, { op: 'add', a: { op: 'var', name: x.pred }, b: { op: 'nat', value: 1 } }]]), vars: [...w.vars, { name: x.pred, sort: 'nat' }] });
|
|
529
|
+
return;
|
|
530
|
+
}
|
|
531
|
+
goX(x.zero, { ...w, wrap: y => w.wrap({ op: 'holds', x: { op: 'natmatch', of: x.of, zero: { op: 'bool', c: y }, pred: x.pred, succ: yesX } }) });
|
|
532
|
+
goX(x.succ, { ...w, wrap: y => w.wrap({ op: 'holds', x: { op: 'natmatch', of: x.of, zero: yesX, pred: x.pred, succ: { op: 'bool', c: y } } }) });
|
|
533
|
+
return;
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
};
|
|
537
|
+
goX(fn.body(), { wrap: y => y, shape: new Map(), vars: fn.params.map(q => ({ name: `${fn.name}.${q.name}`, sort: q.sort })) });
|
|
538
|
+
return out;
|
|
539
|
+
}
|
|
540
|
+
// The obligation of a call: under its path, the measure at its arguments below the measure at the parameters, a law over
|
|
541
|
+
// the path's variables (the parameters not matched, the fields of the cases taken).
|
|
542
|
+
function obligation(fn, m, s, i) {
|
|
543
|
+
const own = fn.params.map(q => ({ op: 'var', name: `${fn.name}.${q.name}` }));
|
|
544
|
+
const shaped = (t) => (Array.isArray(t) ? t.map(shaped) : t && typeof t === 'object' ? (t.op === 'var' && s.shape.has(t.name) ? shaped(s.shape.get(t.name)) : Object.fromEntries(Object.entries(t).map(([key, v]) => [key, shaped(v)]))) : t);
|
|
545
|
+
const holds = shaped(s.wrap(lexY(m.comps.map(c => measureX(fn, c, s.args)), m.comps.map(c => measureX(fn, c, own)))));
|
|
546
|
+
return { name: `${fn.name}'s call ${i + 1} lowers ${showMeasure(fn, m)}`, vars: s.vars.filter(v => !s.shape.has(v.name)), holds, params: [...new Set(paramsIn(holds))] };
|
|
547
|
+
}
|
|
548
|
+
// The calls the function's runs make, each from its parameters' sizes to its arguments' (random arguments, up to 2,000
|
|
549
|
+
// calls a run: a run past that is cut, its calls kept).
|
|
550
|
+
let tracing;
|
|
551
|
+
function runEdges(fn) {
|
|
552
|
+
const rand = rng(17), edges = new Map();
|
|
553
|
+
const feat = (vs) => fn.params.map((p, i) => (p.sort === 'nat' ? vs[i] : p.sort instanceof Data ? sizeOf(p.sort, vs[i]) : 0));
|
|
554
|
+
const tables = new Map();
|
|
555
|
+
const params = new Proxy({}, { get: (_, key) => tables.get(key) ?? (tables.set(key, randomFn(rand)), tables.get(key)) });
|
|
556
|
+
for (let i = 0; i < 120 && edges.size < 400; i++) {
|
|
557
|
+
const vals = fn.params.map(p => randomValue(p.sort, rand, 1 + (i % 7)));
|
|
558
|
+
tracing = { fn: fn.name, stack: [], fuel: 2000, edge: (a, b) => { const e = { s: feat(a), t: feat(b) }; edges.set(JSON.stringify(e), e); } };
|
|
559
|
+
try {
|
|
560
|
+
evalX({ op: 'call', fn: fn.name, args: vals.map((_, j) => ({ op: 'var', name: `$a${j}` })) }, new Map(vals.map((v, j) => [`$a${j}`, v])), params);
|
|
561
|
+
}
|
|
562
|
+
catch { /* cut: its calls kept */ }
|
|
563
|
+
finally {
|
|
564
|
+
tracing = undefined;
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
return [...edges.values()];
|
|
568
|
+
}
|
|
569
|
+
const valueOf = (c, xs) => Math.max(0, c.coef.reduce((a, k, i) => a + k * xs[i], c.c));
|
|
570
|
+
const lowers = (m, e) => {
|
|
571
|
+
for (const c of m.comps) {
|
|
572
|
+
const a = valueOf(c, e.t), b = valueOf(c, e.s);
|
|
573
|
+
if (a < b)
|
|
574
|
+
return true;
|
|
575
|
+
if (a > b)
|
|
576
|
+
return false;
|
|
577
|
+
}
|
|
578
|
+
return false;
|
|
579
|
+
};
|
|
580
|
+
function candidates(fn, edges) {
|
|
581
|
+
if (fn.stated)
|
|
582
|
+
return [fn.stated];
|
|
583
|
+
const ix = fn.params.flatMap((p, i) => (measurable(p.sort) ? [i] : [])), n = fn.params.length;
|
|
584
|
+
const one = (i) => ({ coef: fn.params.map((_, j) => (j === i ? 1 : 0)), c: 0 }), sum = (is) => ({ coef: fn.params.map((_, j) => (is.includes(j) ? 1 : 0)), c: 0 });
|
|
585
|
+
const out = ix.map(i => ({ comps: [one(i)] }));
|
|
586
|
+
for (const i of ix)
|
|
587
|
+
for (const j of ix)
|
|
588
|
+
if (i !== j)
|
|
589
|
+
out.push({ comps: [one(i), one(j)] });
|
|
590
|
+
for (const [a, i] of ix.entries())
|
|
591
|
+
for (const j of ix.slice(a + 1))
|
|
592
|
+
out.push({ comps: [sum([i, j])] });
|
|
593
|
+
if (ix.length <= 4)
|
|
594
|
+
for (const i of ix)
|
|
595
|
+
for (const j of ix)
|
|
596
|
+
for (const k of ix)
|
|
597
|
+
if (i !== j && j !== k && i !== k)
|
|
598
|
+
out.push({ comps: [one(i), one(j), one(k)] });
|
|
599
|
+
if (ix.length > 2)
|
|
600
|
+
out.push({ comps: [sum(ix)] });
|
|
601
|
+
// The LP's rank over the runs (coefficients and constants), where the runs give edges.
|
|
602
|
+
if (edges.length) {
|
|
603
|
+
const vars = ix.map(i => `p${i}`), env = (xs) => Object.fromEntries(ix.map(i => [`p${i}`, xs[i]]));
|
|
604
|
+
const r = ranksOfRuns(vars, edges.flatMap(e => [env(e.s), env(e.t)]), edges.map(e => ({ s: env(e.s), t: env(e.t) })));
|
|
605
|
+
if (!r.left && r.comps.length)
|
|
606
|
+
out.push({ comps: r.comps.map(c => ({ coef: Array.from({ length: n }, (_, j) => c.coef[`p${j}`] ?? 0), c: c.c })) });
|
|
607
|
+
}
|
|
608
|
+
return out;
|
|
609
|
+
}
|
|
610
|
+
// An obligation as one node of a cyclic proof (no use, no models): its conclusion, each definition it reads unfolded
|
|
611
|
+
// once, the constructors' facts at the values built, the shape at each size it reads and at its variables of a
|
|
612
|
+
// datatype, the congruences 16 random models' explanations use. The kernel checks it as any cyclic proof; undefined where it isn't proven.
|
|
613
|
+
function directly(law, ms) {
|
|
614
|
+
const p = programOf([law]), fs = factsOf(p), concl = encY(p, law.holds, new Map());
|
|
615
|
+
varSorts = new Map(law.vars.flatMap(v => (v.sort instanceof Data ? [[v.name, v.sort]] : [])));
|
|
616
|
+
const until = clock() + ms;
|
|
617
|
+
const insts = instancesOf(p, fs, { roots: [concl], others: [], depth: 1, shapesAt: [...varSorts.keys()].map(ev), etaAt: [], models: modelsFor(p, law, 16, 3), until });
|
|
618
|
+
if (clock() > until)
|
|
619
|
+
return undefined;
|
|
620
|
+
const was = pairedKeys.skip;
|
|
621
|
+
pairedKeys.skip = true;
|
|
622
|
+
try {
|
|
623
|
+
const v = withDeadline(until, () => proveCyclic(fs.facts, [{ name: law.name, hyps: [], concl, rank: [k(0)], insts, uses: [] }]));
|
|
624
|
+
return v.proven && v.checked ? v : undefined;
|
|
625
|
+
}
|
|
626
|
+
finally {
|
|
627
|
+
pairedKeys.skip = was;
|
|
185
628
|
}
|
|
186
|
-
|
|
629
|
+
}
|
|
630
|
+
function termination(fn, why) {
|
|
631
|
+
return counted(dataRate, () => {
|
|
632
|
+
if (dataTrace)
|
|
633
|
+
console.error(`[data] termination of ${fn.name}`);
|
|
634
|
+
const t0 = performance.now(), sites = callSites(fn);
|
|
635
|
+
if (!fn.params.some(p => measurable(p.sort)))
|
|
636
|
+
throw new Error(`${why}, and it has no parameter a measure reads (a natural or a datatype value)`);
|
|
637
|
+
if (dataTrace)
|
|
638
|
+
console.error(`[data] ${sites.length} calls`);
|
|
639
|
+
const edges = runEdges(fn), until = clock() + terminationMs.ms, tried = [], seen = new Set();
|
|
640
|
+
if (dataTrace)
|
|
641
|
+
console.error(`[data] its runs: ${edges.length} calls at ${Math.round(performance.now() - t0)} ms`);
|
|
642
|
+
for (const m of candidates(fn, edges)) {
|
|
643
|
+
if (dataTrace)
|
|
644
|
+
console.error(`[data] ${showMeasure(fn, m)} at ${Math.round(performance.now() - t0)} ms`);
|
|
645
|
+
if (clock() > until) {
|
|
646
|
+
tried.push('out of time');
|
|
647
|
+
break;
|
|
648
|
+
}
|
|
649
|
+
const text = showMeasure(fn, m), bad = edges.find(e => !lowers(m, e));
|
|
650
|
+
if (seen.has(text))
|
|
651
|
+
continue;
|
|
652
|
+
seen.add(text);
|
|
653
|
+
if (bad) {
|
|
654
|
+
if (fn.stated || !tried.length)
|
|
655
|
+
tried.push(`${text}: a call of its runs doesn't lower it (from ${JSON.stringify(bad.s)} to ${JSON.stringify(bad.t)}, the parameters' sizes)`);
|
|
656
|
+
continue;
|
|
657
|
+
}
|
|
658
|
+
const obligations = [];
|
|
659
|
+
let failed;
|
|
660
|
+
for (const [i, site] of sites.entries()) {
|
|
661
|
+
const law = obligation(fn, m, site, i);
|
|
662
|
+
// A call nested in its own argument or condition: the obligation would read the function being taken.
|
|
663
|
+
if (programOf([law]).fns.some(g => g.name === fn.name))
|
|
664
|
+
throw new Error(`${why}, and a call to itself is nested in the argument or the condition of another (nested recursion isn't taken)`);
|
|
665
|
+
// First one node, the definitions unfolded once (most calls lower the measure by their cases alone); else
|
|
666
|
+
// proveData: by induction where the obligation has a scheme (a call on a filter of the tail: the filter's own
|
|
667
|
+
// recursion), else, or then, the definitions unfolded alone.
|
|
668
|
+
const left = () => Math.max(1, until - clock()), cex = refute(law, 200);
|
|
669
|
+
if (cex) {
|
|
670
|
+
failed = `${text}: its call ${i + 1} doesn't lower it at ${JSON.stringify(cex)}`;
|
|
671
|
+
break;
|
|
672
|
+
}
|
|
673
|
+
const direct = directly(law, Math.min(2000, left()));
|
|
674
|
+
if (direct) {
|
|
675
|
+
obligations.push({ law, proof: direct, lemmas: [] });
|
|
676
|
+
continue;
|
|
677
|
+
}
|
|
678
|
+
const inductive = schemes(programOf([law]), law).length > 0;
|
|
679
|
+
// (no conjectured lemmas: their making isn't counted in the budget, and a call's decrease mostly needs none)
|
|
680
|
+
let v = proveData(law, inductive ? { ms: Math.min(12_000, left()), lemmas: false } : { induction: false, ms: Math.min(4000, left()) });
|
|
681
|
+
if (inductive && !(v.proven && v.checked) && !v.counterexample && clock() < until)
|
|
682
|
+
v = proveData(law, { induction: false, ms: Math.min(4000, left()) });
|
|
683
|
+
if (!(v.proven && v.checked)) {
|
|
684
|
+
failed = `${text}: its call ${i + 1} ${v.counterexample ? `doesn't lower it at ${JSON.stringify(v.counterexample)}` : 'not proven to lower it'}`;
|
|
685
|
+
break;
|
|
686
|
+
}
|
|
687
|
+
obligations.push({ law, proof: v.proof, scheme: v.scheme, lemmas: v.lemmas });
|
|
688
|
+
}
|
|
689
|
+
if (!failed)
|
|
690
|
+
return { measure: m, text, obligations, ms: Math.round(performance.now() - t0) };
|
|
691
|
+
tried.push(failed);
|
|
692
|
+
}
|
|
693
|
+
throw new Error(`${why}, and no measure is proven to fall at every call${tried.length ? ` (${tried.slice(0, 3).join('; ')})` : ''}`);
|
|
694
|
+
});
|
|
187
695
|
}
|
|
188
696
|
export function law(name, vars, holds) {
|
|
189
697
|
const vs = Object.entries(vars).map(([n, s]) => ({ name: n, sort: s }));
|
|
@@ -192,8 +700,20 @@ export function law(name, vars, holds) {
|
|
|
192
700
|
}
|
|
193
701
|
const fnOf = (name) => { const f = fns.get(name); if (!f)
|
|
194
702
|
throw new Error(`no function ${name}`); return f; };
|
|
703
|
+
// A function by its name, if one is defined (core/transfer.ts names its copies apart from every other).
|
|
704
|
+
export const fnNamed = (name) => fns.get(name);
|
|
195
705
|
const asNat = (v) => v;
|
|
196
|
-
|
|
706
|
+
// Evaluation within bounds: a natural past 2^53 (no longer exact) always throws EvalLimit, and within the search
|
|
707
|
+
// (`evalBounds.on`: proveData, refute) so do calls nested past `depth` or past `calls` of one call's evaluation, which
|
|
708
|
+
// recursive powers and factorials reach on small values. The sample that needs one is skipped (refute), the model's
|
|
709
|
+
// read is unknown (the search).
|
|
710
|
+
export class EvalLimit extends RangeError {
|
|
711
|
+
}
|
|
712
|
+
export const evalBounds = { on: false, depth: 1000, calls: 20_000 };
|
|
713
|
+
let evalCalls = 0;
|
|
714
|
+
const exact = (n) => { if (n > Number.MAX_SAFE_INTEGER)
|
|
715
|
+
throw new EvalLimit('past 2^53'); return n; };
|
|
716
|
+
export function evalX(x, env, params, d = 0) {
|
|
197
717
|
switch (x.op) {
|
|
198
718
|
case 'var': {
|
|
199
719
|
const v = env.get(x.name);
|
|
@@ -202,48 +722,95 @@ export function evalX(x, env, params) {
|
|
|
202
722
|
return v;
|
|
203
723
|
}
|
|
204
724
|
case 'nat': return x.value;
|
|
205
|
-
case 'add': return asNat(evalX(x.a, env, params)) + asNat(evalX(x.b, env, params));
|
|
206
|
-
case 'sub': return Math.max(0, asNat(evalX(x.a, env, params)) - asNat(evalX(x.b, env, params)));
|
|
207
|
-
case 'min': return Math.min(asNat(evalX(x.a, env, params)), asNat(evalX(x.b, env, params)));
|
|
208
|
-
case 'max': return Math.max(asNat(evalX(x.a, env, params)), asNat(evalX(x.b, env, params)));
|
|
209
|
-
case 'mul': return x.k * asNat(evalX(x.a, env, params));
|
|
210
|
-
case '
|
|
211
|
-
case '
|
|
212
|
-
case '
|
|
213
|
-
case '
|
|
725
|
+
case 'add': return exact(asNat(evalX(x.a, env, params, d)) + asNat(evalX(x.b, env, params, d)));
|
|
726
|
+
case 'sub': return Math.max(0, asNat(evalX(x.a, env, params, d)) - asNat(evalX(x.b, env, params, d)));
|
|
727
|
+
case 'min': return Math.min(asNat(evalX(x.a, env, params, d)), asNat(evalX(x.b, env, params, d)));
|
|
728
|
+
case 'max': return Math.max(asNat(evalX(x.a, env, params, d)), asNat(evalX(x.b, env, params, d)));
|
|
729
|
+
case 'mul': return exact(x.k * asNat(evalX(x.a, env, params, d)));
|
|
730
|
+
case 'prod': return exact(asNat(evalX(x.a, env, params, d)) * asNat(evalX(x.b, env, params, d)));
|
|
731
|
+
case 'mod': return asNat(evalX(x.a, env, params, d)) % x.k;
|
|
732
|
+
case 'div': return Math.floor(asNat(evalX(x.a, env, params, d)) / x.k);
|
|
733
|
+
case 'int': return x.value;
|
|
734
|
+
case 'iadd': return exact(asNat(evalX(x.a, env, params, d)) + asNat(evalX(x.b, env, params, d)));
|
|
735
|
+
case 'isub': return exact(asNat(evalX(x.a, env, params, d)) - asNat(evalX(x.b, env, params, d)));
|
|
736
|
+
case 'ineg': return -asNat(evalX(x.a, env, params, d));
|
|
737
|
+
case 'imul': return exact(x.k * asNat(evalX(x.a, env, params, d)));
|
|
738
|
+
case 'ofnat': return asNat(evalX(x.a, env, params, d));
|
|
739
|
+
case 'imod': return euclidMod(asNat(evalX(x.a, env, params, d)), x.k);
|
|
740
|
+
case 'idiv': {
|
|
741
|
+
const v = asNat(evalX(x.a, env, params, d));
|
|
742
|
+
return (v - euclidMod(v, x.k)) / x.k;
|
|
743
|
+
}
|
|
744
|
+
case 'if': return evalY(x.c, env, params, d) ? evalX(x.then, env, params, d) : evalX(x.else, env, params, d);
|
|
745
|
+
case 'bool': return evalY(x.c, env, params, d) ? 1 : 0;
|
|
746
|
+
case 'apply': return params[x.param](asNat(evalX(x.arg, env, params, d)));
|
|
747
|
+
case 'size': return sizeOf(dataNamed(x.data), evalX(x.of, env, params, d));
|
|
748
|
+
case 'ctor': return { c: x.ctor, f: x.args.map(a => evalX(a, env, params, d)) };
|
|
749
|
+
case 'natmatch': {
|
|
750
|
+
const n = asNat(evalX(x.of, env, params, d));
|
|
751
|
+
if (n === 0)
|
|
752
|
+
return evalX(x.zero, env, params, d);
|
|
753
|
+
const e2 = new Map(env);
|
|
754
|
+
e2.set(x.pred, n - 1);
|
|
755
|
+
return evalX(x.succ, e2, params, d);
|
|
756
|
+
}
|
|
214
757
|
case 'match': {
|
|
215
|
-
const v = evalX(x.of, env, params), cs = x.cases[v.c];
|
|
758
|
+
const v = evalX(x.of, env, params, d), cs = x.cases[v.c];
|
|
216
759
|
const e2 = new Map(env);
|
|
217
760
|
cs.binds.forEach((b, i) => e2.set(b, v.f[i]));
|
|
218
|
-
return evalX(cs.body, e2, params);
|
|
761
|
+
return evalX(cs.body, e2, params, d);
|
|
219
762
|
}
|
|
220
763
|
case 'call': {
|
|
221
|
-
const f = fnOf(x.fn), body = f.body();
|
|
222
|
-
const e2 = new Map(f.params.map((p, i) => [`${f.name}.${p.name}`,
|
|
223
|
-
|
|
764
|
+
const f = fnOf(x.fn), body = f.body(), vals = x.args.map(a => evalX(a, env, params, d));
|
|
765
|
+
const e2 = new Map(f.params.map((p, i) => [`${f.name}.${p.name}`, vals[i]]));
|
|
766
|
+
if (evalBounds.on) {
|
|
767
|
+
if (d === 0)
|
|
768
|
+
evalCalls = 0;
|
|
769
|
+
if (d >= evalBounds.depth || ++evalCalls > evalBounds.calls)
|
|
770
|
+
throw new EvalLimit(`${f.name}: past the evaluation's bounds`);
|
|
771
|
+
}
|
|
772
|
+
if (tracing?.fn !== f.name)
|
|
773
|
+
return evalX(body, e2, params, d + 1);
|
|
774
|
+
// A run of a function whose termination is being proven: each call recorded, the run cut past its fuel.
|
|
775
|
+
const t = tracing;
|
|
776
|
+
if (--t.fuel < 0)
|
|
777
|
+
throw new RangeError('out of fuel');
|
|
778
|
+
const top = t.stack.at(-1);
|
|
779
|
+
if (top)
|
|
780
|
+
t.edge(top, vals);
|
|
781
|
+
t.stack.push(vals);
|
|
782
|
+
try {
|
|
783
|
+
return evalX(body, e2, params, d + 1);
|
|
784
|
+
}
|
|
785
|
+
finally {
|
|
786
|
+
t.stack.pop();
|
|
787
|
+
}
|
|
224
788
|
}
|
|
225
789
|
}
|
|
226
790
|
}
|
|
227
|
-
export function evalY(y, env, params) {
|
|
791
|
+
export function evalY(y, env, params, d = 0) {
|
|
228
792
|
switch (y.op) {
|
|
229
|
-
case '<': return asNat(evalX(y.a, env, params)) < asNat(evalX(y.b, env, params));
|
|
230
|
-
case '<=': return asNat(evalX(y.a, env, params)) <= asNat(evalX(y.b, env, params));
|
|
231
|
-
case '
|
|
232
|
-
case '
|
|
233
|
-
case '
|
|
234
|
-
case '
|
|
235
|
-
case '
|
|
236
|
-
case '
|
|
793
|
+
case '<': return asNat(evalX(y.a, env, params, d)) < asNat(evalX(y.b, env, params, d));
|
|
794
|
+
case '<=': return asNat(evalX(y.a, env, params, d)) <= asNat(evalX(y.b, env, params, d));
|
|
795
|
+
case 'i<': return asNat(evalX(y.a, env, params, d)) < asNat(evalX(y.b, env, params, d));
|
|
796
|
+
case 'i<=': return asNat(evalX(y.a, env, params, d)) <= asNat(evalX(y.b, env, params, d));
|
|
797
|
+
case '=': return same(evalX(y.a, env, params, d), evalX(y.b, env, params, d));
|
|
798
|
+
case 'and': return y.xs.every(z => evalY(z, env, params, d));
|
|
799
|
+
case 'or': return y.xs.some(z => evalY(z, env, params, d));
|
|
800
|
+
case 'not': return !evalY(y.x, env, params, d);
|
|
801
|
+
case 'is': return evalX(y.of, env, params, d).c === y.ctor;
|
|
802
|
+
case 'holds': return evalX(y.x, env, params, d) === 1;
|
|
237
803
|
}
|
|
238
804
|
}
|
|
805
|
+
const euclidMod = (v, k) => ((v % k) + k) % k;
|
|
239
806
|
const same = (a, b) => (typeof a === 'number' || typeof b === 'number' ? a === b : a.c === b.c && a.f.every((x, i) => same(x, b.f[i])));
|
|
240
807
|
export const showValue = (d, v) => {
|
|
241
808
|
if (typeof v === 'number')
|
|
242
809
|
return String(v);
|
|
243
|
-
if (d === 'nat' || d === 'bool')
|
|
810
|
+
if (d === 'nat' || d === 'bool' || d === 'int')
|
|
244
811
|
return JSON.stringify(v);
|
|
245
812
|
const c = d.ctors[v.c];
|
|
246
|
-
return c.fields.length ? `${c.name}(${c.fields.map((f, i) => showValue(f
|
|
813
|
+
return c.fields.length ? `${c.name}(${c.fields.map((f, i) => showValue(fieldSort(f), v.f[i])).join(', ')})` : c.name;
|
|
247
814
|
};
|
|
248
815
|
export const rng = (seed) => () => { seed = (seed * 1103515245 + 12345) % 2147483648; return seed / 2147483648; };
|
|
249
816
|
export function randomValue(s, rand, size, natMax = 5) {
|
|
@@ -251,11 +818,15 @@ export function randomValue(s, rand, size, natMax = 5) {
|
|
|
251
818
|
return Math.floor(rand() * (natMax + 1));
|
|
252
819
|
if (s === 'bool')
|
|
253
820
|
return rand() < 0.5 ? 0 : 1;
|
|
254
|
-
|
|
821
|
+
if (s === 'int')
|
|
822
|
+
return Math.floor(rand() * (2 * natMax + 1)) - natMax;
|
|
823
|
+
// The leaves: constructors of no field of the group, or (a datatype with none, a rose tree) those of least height.
|
|
824
|
+
const base = s.ctors.filter(c => c.fields.every(f => !inGroup(s, f)));
|
|
825
|
+
const leaves = base.length ? base : s.ctors.filter(c => s.ctorHeight(c) === s.height);
|
|
255
826
|
const pick = size <= 0 || rand() < 0.25 ? leaves : s.ctors;
|
|
256
827
|
const c = pick[Math.floor(rand() * pick.length)], j = s.ctors.indexOf(c);
|
|
257
|
-
const rec = c.fields.filter(f => f
|
|
258
|
-
return { c: j, f: c.fields.map(f => randomValue(f
|
|
828
|
+
const rec = c.fields.filter(f => inGroup(s, f)).length;
|
|
829
|
+
return { c: j, f: c.fields.map(f => randomValue(fieldSort(f), rand, inGroup(s, f) ? Math.floor((size - 1) / Math.max(1, rec)) : Math.min(3, size), natMax)) };
|
|
259
830
|
}
|
|
260
831
|
// A random function of a natural: a table of small values.
|
|
261
832
|
export const randomFn = (rand, natMax = 5) => { const t = new Map(); return n => { if (!t.has(n))
|
|
@@ -278,7 +849,7 @@ const yesC = leC(k(0), k(0));
|
|
|
278
849
|
const pairE = (a, b) => { const s = { op: 'add', a, b }; return { op: 'add', a: { op: 'prod', a: s, b: s }, b }; };
|
|
279
850
|
export const keyOf = (args) => args.slice(1).reduce(pairE, args[0]);
|
|
280
851
|
// The arguments of a key built by keyOf (n of them), read back from its shape.
|
|
281
|
-
function argsOf(key, n) {
|
|
852
|
+
export function argsOf(key, n) {
|
|
282
853
|
if (n === 1)
|
|
283
854
|
return [key];
|
|
284
855
|
if (key.op !== 'add' || key.a.op !== 'prod' || key.a.a.op !== 'add')
|
|
@@ -292,8 +863,13 @@ export const ctorMap = (d, j) => `${d.name}.${d.ctors[j].name}`;
|
|
|
292
863
|
// Everything a law reads: the datatypes, the functions (with those they call), the function parameters.
|
|
293
864
|
export function programOf(laws, extra = []) {
|
|
294
865
|
const fs = new Map(), ds = new Map(), ps = new Set();
|
|
295
|
-
|
|
296
|
-
|
|
866
|
+
// A datatype with those its fields name (its group, those declared before it).
|
|
867
|
+
const addSort = (s) => { if (s instanceof Data && !ds.has(s.name)) {
|
|
868
|
+
ds.set(s.name, s);
|
|
869
|
+
for (const c of s.ctors)
|
|
870
|
+
for (const f of c.fields)
|
|
871
|
+
addSort(fieldSort(f));
|
|
872
|
+
} };
|
|
297
873
|
const visit = (t) => walk(t, o => {
|
|
298
874
|
if (o.op === 'call' && !fs.has(o.fn)) {
|
|
299
875
|
const f = fnOf(o.fn);
|
|
@@ -304,6 +880,11 @@ export function programOf(laws, extra = []) {
|
|
|
304
880
|
}
|
|
305
881
|
if (o.op === 'apply')
|
|
306
882
|
ps.add(o.param);
|
|
883
|
+
if (o.op === 'size')
|
|
884
|
+
addSort(dataNamed(o.data));
|
|
885
|
+
// (a datatype read only through a constructor, a match or a test of one: a law with a definition unfolded)
|
|
886
|
+
if ((o.op === 'ctor' || o.op === 'match' || o.op === 'is') && typeof o.data === 'string')
|
|
887
|
+
addSort(dataNamed(o.data));
|
|
307
888
|
});
|
|
308
889
|
for (const l of laws) {
|
|
309
890
|
l.vars.forEach(v => addSort(v.sort));
|
|
@@ -311,7 +892,8 @@ export function programOf(laws, extra = []) {
|
|
|
311
892
|
}
|
|
312
893
|
for (const f of extra)
|
|
313
894
|
visit({ op: 'call', fn: f.name, args: [] });
|
|
314
|
-
|
|
895
|
+
// In order of declaration (each after those its fields name; a group together).
|
|
896
|
+
const datas = [...ds.values()].sort((a, b) => a.seq - b.seq);
|
|
315
897
|
// Functions in definition order (each after those it calls).
|
|
316
898
|
const order = [], seen = new Set();
|
|
317
899
|
const place = (f) => { if (seen.has(f.name))
|
|
@@ -319,11 +901,95 @@ export function programOf(laws, extra = []) {
|
|
|
319
901
|
place(fnOf(o.fn)); }); order.push(f); };
|
|
320
902
|
for (const f of fs.values())
|
|
321
903
|
place(f);
|
|
322
|
-
|
|
904
|
+
// (the recursion of each checked)
|
|
905
|
+
for (const f of order)
|
|
906
|
+
void f.decreases;
|
|
907
|
+
const divs = new Set();
|
|
908
|
+
let ints = false;
|
|
909
|
+
const scan = (t) => walk(t, o => { if (o.op === 'mod' || o.op === 'div' || o.op === 'imod' || o.op === 'idiv')
|
|
910
|
+
divs.add(o.k); if (typeof o.op === 'string' && (intOps.has(o.op) || o.op === 'i<' || o.op === 'i<='))
|
|
911
|
+
ints = true; });
|
|
912
|
+
for (const l of laws) {
|
|
913
|
+
scan(l.holds);
|
|
914
|
+
if (l.vars.some(v => v.sort === 'int'))
|
|
915
|
+
ints = true;
|
|
916
|
+
}
|
|
917
|
+
for (const f of order) {
|
|
918
|
+
scan(f.body());
|
|
919
|
+
if (f.ret === 'int' || f.params.some(q => q.sort === 'int'))
|
|
920
|
+
ints = true;
|
|
921
|
+
}
|
|
922
|
+
for (const d of datas)
|
|
923
|
+
if (d.ctors.some(c => c.fields.some(f => f.sort === 'int')))
|
|
924
|
+
ints = true;
|
|
925
|
+
if (ints)
|
|
926
|
+
divs.add(2);
|
|
927
|
+
return { datas, fns: order, params: [...ps], divisors: [...divs].sort((a, b) => a - b), ints };
|
|
323
928
|
}
|
|
324
929
|
const dataOf = (p, name) => p.datas.find(d => d.name === name);
|
|
930
|
+
// Division and remainder by a constant: the maps div$k and mod$k (core/machine.ts names them so), known by Euclid's fact
|
|
931
|
+
// at each key read (`arith k`).
|
|
932
|
+
export const divMap = (k) => `div$${k}`, modMap = (k) => `mod$${k}`;
|
|
933
|
+
const arithMap = /^(?:div|mod)\$(\d+)$/;
|
|
934
|
+
// Integers by their zigzag code: a code c is the integer p − n, p and n its value's two parts as maps of the code
|
|
935
|
+
// (int$pos, int$neg), known by one fact (`zcode`): c + c mod 2 = 2p + 2n, p = 0 where c is odd, n = 0 where it is even;
|
|
936
|
+
// a pair (p, n) has the code 2(p ∸ n) + (2(n ∸ p) ∸ 1). Neither direction needs an if, so the kernel splits only where a
|
|
937
|
+
// proof needs the sign or the parity.
|
|
938
|
+
export const zig = (z) => (z >= 0 ? 2 * z : -2 * z - 1);
|
|
939
|
+
export const unzig = (c) => (c % 2 === 0 ? c / 2 : -(c + 1) / 2);
|
|
940
|
+
const sumE = (a, b) => (a.op === 'const' && a.value === 0 ? b : b.op === 'const' && b.value === 0 ? a : { op: 'add', a, b });
|
|
941
|
+
export const posMap = 'int$pos', negMap = 'int$neg';
|
|
942
|
+
const pairOfCode = (c) => {
|
|
943
|
+
if (c.op === 'const') {
|
|
944
|
+
const z = unzig(c.value);
|
|
945
|
+
return [k(Math.max(z, 0)), k(Math.max(-z, 0))];
|
|
946
|
+
}
|
|
947
|
+
return [get(posMap, c), get(negMap, c)];
|
|
948
|
+
};
|
|
949
|
+
const codeOfPair = ([pos, neg]) => {
|
|
950
|
+
if (neg.op === 'const' && neg.value === 0)
|
|
951
|
+
return { op: 'mul', k: 2, a: pos };
|
|
952
|
+
return { op: 'add', a: { op: 'mul', k: 2, a: { op: 'sub', a: pos, b: neg } }, b: { op: 'sub', a: { op: 'mul', k: 2, a: { op: 'sub', a: neg, b: pos } }, b: k(1) } };
|
|
953
|
+
};
|
|
954
|
+
const intOps = new Set(['int', 'iadd', 'isub', 'ineg', 'imul', 'ofnat', 'imod', 'idiv']);
|
|
955
|
+
// An integer term as the two naturals p − n of its value.
|
|
956
|
+
function encI(p, x, env) {
|
|
957
|
+
if (x.op === 'int')
|
|
958
|
+
return [k(Math.max(x.value, 0)), k(Math.max(-x.value, 0))];
|
|
959
|
+
if (x.op === 'iadd' || x.op === 'isub') {
|
|
960
|
+
const [a, b] = [encI(p, x.a, env), encI(p, x.b, env)];
|
|
961
|
+
return x.op === 'iadd' ? [sumE(a[0], b[0]), sumE(a[1], b[1])] : [sumE(a[0], b[1]), sumE(a[1], b[0])];
|
|
962
|
+
}
|
|
963
|
+
if (x.op === 'ineg') {
|
|
964
|
+
const [pos, neg] = encI(p, x.a, env);
|
|
965
|
+
return [neg, pos];
|
|
966
|
+
}
|
|
967
|
+
if (x.op === 'imul') {
|
|
968
|
+
const [pos, neg] = encI(p, x.a, env), m = Math.abs(x.k), sc = (e) => (m === 1 ? e : { op: 'mul', k: m, a: e });
|
|
969
|
+
return x.k >= 0 ? [sc(pos), sc(neg)] : [sc(neg), sc(pos)];
|
|
970
|
+
}
|
|
971
|
+
if (x.op === 'ofnat')
|
|
972
|
+
return [encX(p, x.a, env), k(0)];
|
|
973
|
+
// pos + (k − 1)·neg is the value plus k·neg: the same remainder, and the quotient plus neg.
|
|
974
|
+
if (x.op === 'imod' || x.op === 'idiv') {
|
|
975
|
+
const [pos, neg] = encI(p, x.a, env), t = x.k === 1 ? pos : sumE(pos, { op: 'mul', k: x.k - 1, a: neg });
|
|
976
|
+
return x.op === 'imod' ? [get(modMap(x.k), t), k(0)] : [get(divMap(x.k), t), neg];
|
|
977
|
+
}
|
|
978
|
+
return pairOfCode(encX(p, x, env));
|
|
979
|
+
}
|
|
325
980
|
export function encX(p, x, env) {
|
|
326
981
|
switch (x.op) {
|
|
982
|
+
case 'prod': return { op: 'prod', a: encX(p, x.a, env), b: encX(p, x.b, env) };
|
|
983
|
+
case 'mod': return get(modMap(x.k), encX(p, x.a, env));
|
|
984
|
+
case 'div': return get(divMap(x.k), encX(p, x.a, env));
|
|
985
|
+
case 'int': return k(zig(x.value));
|
|
986
|
+
case 'iadd':
|
|
987
|
+
case 'isub':
|
|
988
|
+
case 'ineg':
|
|
989
|
+
case 'imul':
|
|
990
|
+
case 'ofnat':
|
|
991
|
+
case 'imod':
|
|
992
|
+
case 'idiv': return codeOfPair(encI(p, x, env));
|
|
327
993
|
case 'var': return env.get(x.name) ?? ev(x.name);
|
|
328
994
|
case 'nat': return k(x.value);
|
|
329
995
|
case 'add':
|
|
@@ -334,11 +1000,17 @@ export function encX(p, x, env) {
|
|
|
334
1000
|
case 'if': return { op: 'if', c: encY(p, x.c, env), then: encX(p, x.then, env), else: encX(p, x.else, env) };
|
|
335
1001
|
case 'bool': return { op: 'if', c: encY(p, x.c, env), then: k(1), else: k(0) };
|
|
336
1002
|
case 'apply': return get(x.param, encX(p, x.arg, env));
|
|
1003
|
+
case 'size': return get(sizeMap(x.data), encX(p, x.of, env));
|
|
337
1004
|
case 'call': return get(x.fn, keyOf(x.args.map(a => encX(p, a, env))));
|
|
338
1005
|
case 'ctor': {
|
|
339
1006
|
const d = dataOf(p, x.data);
|
|
340
1007
|
return x.args.length ? get(ctorMap(d, x.ctor), keyOf(x.args.map(a => encX(p, a, env)))) : k(x.ctor);
|
|
341
1008
|
}
|
|
1009
|
+
case 'natmatch': {
|
|
1010
|
+
const of = encX(p, x.of, env), e2 = new Map(env);
|
|
1011
|
+
e2.set(x.pred, { op: 'sub', a: of, b: k(1) });
|
|
1012
|
+
return { op: 'if', c: eqC(of, k(0)), then: encX(p, x.zero, env), else: encX(p, x.succ, e2) };
|
|
1013
|
+
}
|
|
342
1014
|
case 'match': {
|
|
343
1015
|
const d = dataOf(p, x.data), of = encX(p, x.of, env);
|
|
344
1016
|
const bodies = x.cases.map((c, j) => { const e2 = new Map(env); c.binds.forEach((b, i) => e2.set(b, get(fieldMap(d, j, i), of))); return encX(p, c.body, e2); });
|
|
@@ -348,9 +1020,20 @@ export function encX(p, x, env) {
|
|
|
348
1020
|
}
|
|
349
1021
|
export function encY(p, y, env) {
|
|
350
1022
|
switch (y.op) {
|
|
1023
|
+
case 'i<':
|
|
1024
|
+
case 'i<=': {
|
|
1025
|
+
const [a, b] = [encI(p, y.a, env), encI(p, y.b, env)];
|
|
1026
|
+
return { op: y.op === 'i<' ? '<' : '<=', a: sumE(a[0], b[1]), b: sumE(b[0], a[1]) };
|
|
1027
|
+
}
|
|
1028
|
+
// Two integers where one is arithmetic: equal values (the same as equal codes, without writing the sum's code).
|
|
1029
|
+
case '=':
|
|
1030
|
+
if (intOps.has(y.a.op) || intOps.has(y.b.op)) {
|
|
1031
|
+
const [a, b] = [encI(p, y.a, env), encI(p, y.b, env)];
|
|
1032
|
+
return { op: '=', a: sumE(a[0], b[1]), b: sumE(b[0], a[1]) };
|
|
1033
|
+
}
|
|
1034
|
+
return { op: '=', a: encX(p, y.a, env), b: encX(p, y.b, env) };
|
|
351
1035
|
case '<':
|
|
352
|
-
case '<=':
|
|
353
|
-
case '=': return { op: y.op, a: encX(p, y.a, env), b: encX(p, y.b, env) };
|
|
1036
|
+
case '<=': return { op: y.op, a: encX(p, y.a, env), b: encX(p, y.b, env) };
|
|
354
1037
|
case 'and':
|
|
355
1038
|
case 'or': return { op: y.op, xs: y.xs.map(z => encY(p, z, env)) };
|
|
356
1039
|
case 'not': return { op: 'not', x: encY(p, y.x, env) };
|
|
@@ -366,7 +1049,7 @@ export function factsOf(p) {
|
|
|
366
1049
|
const tag = get(tagMap(d.name), x), size = (t) => get(sizeMap(d.name), t);
|
|
367
1050
|
// Under tag j: the size, and every field of another constructor 0 (what Lean's model reads there).
|
|
368
1051
|
const under = (c, j) => {
|
|
369
|
-
const sz = c.fields.flatMap((f, i) => (f
|
|
1052
|
+
const sz = c.fields.flatMap((f, i) => (inGroup(d, f) ? [get(sizeMap(f.sort.data), get(fieldMap(d, j, i), x))] : [])).reduce((a, b) => ({ op: 'add', a, b }), k(1));
|
|
370
1053
|
const others = d.ctors.flatMap((c2, j2) => (j2 === j ? [] : c2.fields.map((_, i2) => eqC(get(fieldMap(d, j2, i2), x), k(0)))));
|
|
371
1054
|
return andC(eqC(size(x), sz), ...others);
|
|
372
1055
|
};
|
|
@@ -388,6 +1071,18 @@ export function factsOf(p) {
|
|
|
388
1071
|
const body = encX(p, f.body(), new Map());
|
|
389
1072
|
put(`fn ${f.name}`, { name: '', keys, holds: andC(eqC(get(f.name, keyOf(keys.map(ev))), body), ...(f.ret === 'bool' ? [leC(get(f.name, keyOf(keys.map(ev))), k(1))] : [])) });
|
|
390
1073
|
}
|
|
1074
|
+
// Euclid's division by each divisor: x = k·(x div k) + x mod k, x mod k < k.
|
|
1075
|
+
for (const k2 of p.divisors) {
|
|
1076
|
+
put(`arith ${k2}`, { name: '', keys: ['x$'], holds: andC(eqC(x, { op: 'add', a: { op: 'mul', k: k2, a: get(divMap(k2), x) }, b: get(modMap(k2), x) }), { op: '<', a: get(modMap(k2), x), b: k(k2) }) });
|
|
1077
|
+
arity.set(divMap(k2), 1);
|
|
1078
|
+
arity.set(modMap(k2), 1);
|
|
1079
|
+
}
|
|
1080
|
+
if (p.ints) {
|
|
1081
|
+
const pos = get(posMap, x), neg = get(negMap, x), par = get(modMap(2), x);
|
|
1082
|
+
put('zcode', { name: '', keys: ['x$'], holds: andC(eqC({ op: 'add', a: x, b: par }, { op: 'add', a: { op: 'mul', k: 2, a: pos }, b: { op: 'mul', k: 2, a: neg } }), { op: 'or', xs: [eqC(par, k(0)), eqC(pos, k(0))] }, { op: 'or', xs: [eqC(par, k(1)), eqC(neg, k(0))] }) });
|
|
1083
|
+
arity.set(posMap, 1);
|
|
1084
|
+
arity.set(negMap, 1);
|
|
1085
|
+
}
|
|
391
1086
|
// Every map the encoding reads: the datatypes' tag, size and fields too (each of one argument).
|
|
392
1087
|
for (const d of p.datas) {
|
|
393
1088
|
arity.set(tagMap(d.name), 1);
|
|
@@ -420,14 +1115,14 @@ export function decode(d, n) {
|
|
|
420
1115
|
if (n < z)
|
|
421
1116
|
return { c: n, f: [] };
|
|
422
1117
|
const q = Math.floor((n - z) / m), j = z + ((n - z) % m), c = d.ctors[j];
|
|
423
|
-
return { c: j, f: c.fields.map((f, i) => { const x = tup(q, c.fields.length, i); return f.sort === 'nat' ? x : decode(
|
|
1118
|
+
return { c: j, f: c.fields.map((f, i) => { const x = tup(q, c.fields.length, i); return f.sort === 'nat' ? x : f.sort === 'int' ? unzig(x) : decode(dataNamed(f.sort.data), x); }) };
|
|
424
1119
|
}
|
|
425
1120
|
export function encode(d, v) {
|
|
426
1121
|
const o = v, z = d.nullary, m = d.ctors.length - z;
|
|
427
1122
|
if (o.c < z)
|
|
428
1123
|
return o.c;
|
|
429
1124
|
const c = d.ctors[o.c];
|
|
430
|
-
return safe(z + m * tupk(c.fields.map((f, i) => (f.sort === 'nat' ? o.f[i] : encode(
|
|
1125
|
+
return safe(z + m * tupk(c.fields.map((f, i) => (f.sort === 'nat' ? o.f[i] : f.sort === 'int' ? zig(o.f[i]) : encode(dataNamed(f.sort.data), o.f[i])))) + (o.c - z));
|
|
431
1126
|
}
|
|
432
1127
|
// Each map of the encoding as a function of its arguments, on the codes (for core/cycles.ts's runs).
|
|
433
1128
|
export function codeValues(p) {
|
|
@@ -436,20 +1131,28 @@ export function codeValues(p) {
|
|
|
436
1131
|
out[tagMap(d.name)] = n => decode(d, n).c;
|
|
437
1132
|
out[sizeMap(d.name)] = n => sizeOf(d, decode(d, n));
|
|
438
1133
|
d.ctors.forEach((c, j) => {
|
|
439
|
-
c.fields.forEach((f, i) => { out[fieldMap(d, j, i)] = n => { const v = decode(d, n); return v.c === j ? (f.sort === 'nat' ? v.f[i] : encode(
|
|
1134
|
+
c.fields.forEach((f, i) => { out[fieldMap(d, j, i)] = n => { const v = decode(d, n); return v.c === j ? (f.sort === 'nat' ? v.f[i] : f.sort === 'int' ? zig(v.f[i]) : encode(dataNamed(f.sort.data), v.f[i])) : 0; }; });
|
|
440
1135
|
if (c.fields.length)
|
|
441
|
-
out[ctorMap(d, j)] = (...xs) => encode(d, { c: j, f: c.fields.map((f, i) => (f.sort === 'nat' ? xs[i] : decode(
|
|
1136
|
+
out[ctorMap(d, j)] = (...xs) => encode(d, { c: j, f: c.fields.map((f, i) => (f.sort === 'nat' ? xs[i] : f.sort === 'int' ? unzig(xs[i]) : decode(dataNamed(f.sort.data), xs[i]))) });
|
|
442
1137
|
});
|
|
443
1138
|
}
|
|
444
1139
|
for (const f of p.fns)
|
|
445
1140
|
out[f.name] = (...xs) => {
|
|
446
|
-
const v = evalX(f.body(), new Map(f.params.map((q, i) => [`${f.name}.${q.name}`, q.sort instanceof Data ? decode(q.sort, xs[i]) : xs[i]])), {});
|
|
447
|
-
return f.ret instanceof Data ? encode(f.ret, v) : v;
|
|
1141
|
+
const v = evalX(f.body(), new Map(f.params.map((q, i) => [`${f.name}.${q.name}`, q.sort instanceof Data ? decode(q.sort, xs[i]) : q.sort === 'int' ? unzig(xs[i]) : xs[i]])), {});
|
|
1142
|
+
return f.ret instanceof Data ? encode(f.ret, v) : f.ret === 'int' ? zig(v) : v;
|
|
448
1143
|
};
|
|
1144
|
+
for (const k2 of p.divisors) {
|
|
1145
|
+
out[divMap(k2)] = n => Math.floor(n / k2);
|
|
1146
|
+
out[modMap(k2)] = n => n % k2;
|
|
1147
|
+
}
|
|
1148
|
+
if (p.ints) {
|
|
1149
|
+
out[posMap] = n => Math.max(unzig(n), 0);
|
|
1150
|
+
out[negMap] = n => Math.max(-unzig(n), 0);
|
|
1151
|
+
}
|
|
449
1152
|
return out;
|
|
450
1153
|
}
|
|
451
1154
|
// That a value's fields of its datatype have smaller codes (true of the codes above: each field at most q).
|
|
452
|
-
export const orderFacts = (p) => p.datas.map(d => ({ name: `order ${d.name}`, keys: ['x$'], holds: andC(yesC, ...d.ctors.flatMap((c, j) => c.fields.flatMap((f, i) => (f.sort ===
|
|
1155
|
+
export const orderFacts = (p) => p.datas.map(d => ({ name: `order ${d.name}`, keys: ['x$'], holds: andC(yesC, ...d.ctors.flatMap((c, j) => c.fields.flatMap((f, i) => (isDataField(f.sort) && f.sort.data === d.name ? [implies(eqC(get(tagMap(d.name), ev('x$')), k(j)), { op: '<', a: get(fieldMap(d, j, i), ev('x$')), b: ev('x$') })] : [])))) }));
|
|
453
1156
|
// --- concrete models: a value of the encoding at random values (which terms are equal, for congruences and matches) ----
|
|
454
1157
|
// Values interned per datatype: a constructor of no field is its tag; the rest numbered from there. A key is a
|
|
455
1158
|
// pairing's natural, read back through its square root.
|
|
@@ -490,8 +1193,8 @@ export class Model {
|
|
|
490
1193
|
return v;
|
|
491
1194
|
}
|
|
492
1195
|
// A value of a sort as the encoding's natural, and back.
|
|
493
|
-
code(s, v) { return s instanceof Data ? this.id(s, v) : v; }
|
|
494
|
-
decode(s, n) { return s instanceof Data ? this.value(s, n) : n; }
|
|
1196
|
+
code(s, v) { return s instanceof Data ? this.id(s, v) : s === 'int' ? zig(v) : v; }
|
|
1197
|
+
decode(s, n) { return s instanceof Data ? this.value(s, n) : s === 'int' ? unzig(n) : n; }
|
|
495
1198
|
memo = new Map();
|
|
496
1199
|
read(map, key) {
|
|
497
1200
|
const at = `${map}|${key}`, had = this.memo.get(at);
|
|
@@ -513,11 +1216,11 @@ export class Model {
|
|
|
513
1216
|
for (const [i, f] of c.fields.entries())
|
|
514
1217
|
if (map === fieldMap(d, j, i)) {
|
|
515
1218
|
const v = this.value(d, key);
|
|
516
|
-
return v.c === j ? this.code(f
|
|
1219
|
+
return v.c === j ? this.code(fieldSort(f), v.f[i]) : 0;
|
|
517
1220
|
}
|
|
518
1221
|
if (c.fields.length && map === ctorMap(d, j)) {
|
|
519
1222
|
const as = unkey(key, c.fields.length);
|
|
520
|
-
return this.id(d, { c: j, f: c.fields.map((f, i) => this.decode(f
|
|
1223
|
+
return this.id(d, { c: j, f: c.fields.map((f, i) => this.decode(fieldSort(f), as[i])) });
|
|
521
1224
|
}
|
|
522
1225
|
}
|
|
523
1226
|
}
|
|
@@ -526,8 +1229,17 @@ export class Model {
|
|
|
526
1229
|
const as = unkey(key, f.params.length);
|
|
527
1230
|
const env = new Map(f.params.map((q, i) => [`${f.name}.${q.name}`, this.decode(q.sort, as[i])]));
|
|
528
1231
|
const out = evalX(f.body(), env, this.params);
|
|
529
|
-
return f.ret === 'nat' || f.ret === 'bool' ? out : this.id(f.ret, out);
|
|
1232
|
+
return f.ret === 'nat' || f.ret === 'bool' ? out : f.ret === 'int' ? zig(out) : this.id(f.ret, out);
|
|
1233
|
+
}
|
|
1234
|
+
const ar = arithMap.exec(map);
|
|
1235
|
+
if (ar) {
|
|
1236
|
+
const k2 = Number(ar[1]);
|
|
1237
|
+
return map.startsWith('mod') ? key % k2 : Math.floor(key / k2);
|
|
530
1238
|
}
|
|
1239
|
+
if (map === posMap)
|
|
1240
|
+
return Math.max(unzig(key), 0);
|
|
1241
|
+
if (map === negMap)
|
|
1242
|
+
return Math.max(-unzig(key), 0);
|
|
531
1243
|
const prm = this.params[map];
|
|
532
1244
|
if (prm)
|
|
533
1245
|
return prm(key);
|
|
@@ -576,7 +1288,7 @@ export class Model {
|
|
|
576
1288
|
}
|
|
577
1289
|
}
|
|
578
1290
|
}
|
|
579
|
-
export const sizeOf = (d, v) => { const o = v; return 1 + d.ctors[o.c].fields.reduce((s, f, i) => s + (f
|
|
1291
|
+
export const sizeOf = (d, v) => { const o = v; return 1 + d.ctors[o.c].fields.reduce((s, f, i) => s + (inGroup(d, f) ? sizeOf(fieldSort(f), o.f[i]) : 0), 0); };
|
|
580
1292
|
const isqrt = (n) => { let s = Math.floor(Math.sqrt(n)); while (s * s > n)
|
|
581
1293
|
s--; while ((s + 1) * (s + 1) <= n)
|
|
582
1294
|
s++; return s; };
|
|
@@ -587,20 +1299,176 @@ export function unkey(key, n) {
|
|
|
587
1299
|
return [...unkey(a, n - 1), b];
|
|
588
1300
|
}
|
|
589
1301
|
// Random values of the law's variables: a law false at one is refuted with it.
|
|
1302
|
+
// A sample whose evaluation passes the bounds (EvalLimit, or the stack) is skipped.
|
|
590
1303
|
export function refute(l, n = 400, seed = 7) {
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
1304
|
+
return bounded(() => {
|
|
1305
|
+
for (const s of samples(l.vars, programOf([l]).params, n, seed)) {
|
|
1306
|
+
let ok;
|
|
1307
|
+
try {
|
|
1308
|
+
ok = evalY(l.holds, s.env, s.params);
|
|
1309
|
+
}
|
|
1310
|
+
catch (e) {
|
|
1311
|
+
if (e instanceof RangeError)
|
|
1312
|
+
continue;
|
|
1313
|
+
throw new Error(`${l.name}: ${e.message}`);
|
|
1314
|
+
}
|
|
1315
|
+
if (!ok)
|
|
1316
|
+
return Object.fromEntries(l.vars.map(v => [v.name, showValue(v.sort, s.env.get(v.name))]));
|
|
595
1317
|
}
|
|
596
|
-
|
|
597
|
-
|
|
1318
|
+
return undefined;
|
|
1319
|
+
});
|
|
1320
|
+
}
|
|
1321
|
+
const bounded = (f) => { const was = evalBounds.on; evalBounds.on = true; try {
|
|
1322
|
+
return f();
|
|
1323
|
+
}
|
|
1324
|
+
finally {
|
|
1325
|
+
evalBounds.on = was;
|
|
1326
|
+
} };
|
|
1327
|
+
// The function a function f calls at its decreasing argument's field of datatype `to` (a rose tree's sum, at its forest:
|
|
1328
|
+
// the forest's sum): the call, the same for every such field, its other arguments f's own parameters or closed terms.
|
|
1329
|
+
function partner(f, to) {
|
|
1330
|
+
const di = f.decreases;
|
|
1331
|
+
if (di === undefined)
|
|
1332
|
+
return undefined;
|
|
1333
|
+
const own = `${f.name}.${f.params[di].name}`, found = new Map();
|
|
1334
|
+
let bad = false;
|
|
1335
|
+
const go = (x, fields) => {
|
|
1336
|
+
if (!x || typeof x !== 'object')
|
|
1337
|
+
return;
|
|
1338
|
+
if (Array.isArray(x)) {
|
|
1339
|
+
x.forEach(y => go(y, fields));
|
|
1340
|
+
return;
|
|
1341
|
+
}
|
|
1342
|
+
const t = x;
|
|
1343
|
+
if (t.op === 'match' && t.of.op === 'var' && t.of.name === own) {
|
|
1344
|
+
const d = dataNamed(t.data);
|
|
1345
|
+
t.cases.forEach((c, j) => go(c.body, new Set([...fields, ...c.binds.filter((_, i) => { const fl = d.ctors[j].fields[i]; return isDataField(fl.sort) && fl.sort.data === to.name; })])));
|
|
1346
|
+
return;
|
|
598
1347
|
}
|
|
1348
|
+
if (t.op === 'call') {
|
|
1349
|
+
const g = fnOf(t.fn), gd = g.decreases, at = gd === undefined ? undefined : t.args[gd];
|
|
1350
|
+
if (at?.op === 'var' && fields.has(at.name)) {
|
|
1351
|
+
const args = t.args.map((a, i) => (i === gd ? -1 : a.op === 'var' && a.name.startsWith(`${f.name}.`) ? f.params.findIndex(q => `${f.name}.${q.name}` === a.name) : !JSON.stringify(a).includes('"op":"var"') ? a : undefined));
|
|
1352
|
+
if (args.some(a => a === undefined))
|
|
1353
|
+
bad = true;
|
|
1354
|
+
else
|
|
1355
|
+
found.set(JSON.stringify([g.name, args]), { fn: g, args: args });
|
|
1356
|
+
}
|
|
1357
|
+
}
|
|
1358
|
+
for (const v of Object.values(t))
|
|
1359
|
+
go(v, fields);
|
|
1360
|
+
};
|
|
1361
|
+
go(f.body(), new Set());
|
|
1362
|
+
return !bad && found.size === 1 ? [...found.values()][0] : undefined;
|
|
1363
|
+
}
|
|
1364
|
+
// The law said of a value of datatype `to` in place of v: v a variable of `to`, and each call whose decreasing argument
|
|
1365
|
+
// is then of `to` (no longer of its own datatype) its function's partner at `to`; none where a sort no longer fits (an
|
|
1366
|
+
// argument, a comparison).
|
|
1367
|
+
function companionOf(l, v, to, name) {
|
|
1368
|
+
// (a sort unknown, an if's or a match's, fits any)
|
|
1369
|
+
const sorts = new Map(l.vars.map(x => [x.name, x.sort])), same = (a, b) => a === undefined || b === undefined || (a instanceof Data ? a.name : a) === (b instanceof Data ? b.name : b);
|
|
1370
|
+
const sortOf = (x, renamed) => (x.op === 'var' ? (renamed && x.name === name ? to : sorts.get(x.name)) : x.op === 'call' ? fnOf(x.fn).ret : x.op === 'ctor' ? dataNamed(x.data) : x.op === 'match' || x.op === 'if' ? undefined : 'nat');
|
|
1371
|
+
let ok = true;
|
|
1372
|
+
const lift = (t) => {
|
|
599
1373
|
if (!ok)
|
|
600
|
-
return
|
|
1374
|
+
return t;
|
|
1375
|
+
if (Array.isArray(t))
|
|
1376
|
+
return t.map(lift);
|
|
1377
|
+
if (!t || typeof t !== 'object')
|
|
1378
|
+
return t;
|
|
1379
|
+
const o = t;
|
|
1380
|
+
if (o.op === 'var' && o.name === v)
|
|
1381
|
+
return { op: 'var', name };
|
|
1382
|
+
if (o.op === 'call') {
|
|
1383
|
+
const f = fnOf(o.fn), di = f.decreases, as = o.args.map(a => lift(a));
|
|
1384
|
+
if (di !== undefined && !same(sortOf(as[di], true), sortOf(o.args[di], false))) {
|
|
1385
|
+
const pt = partner(f, to);
|
|
1386
|
+
if (!pt || !same(sortOf(as[di], true), to)) {
|
|
1387
|
+
ok = false;
|
|
1388
|
+
return o;
|
|
1389
|
+
}
|
|
1390
|
+
const args = pt.args.map(a => (a === -1 ? as[di] : typeof a === 'number' ? as[a] : a));
|
|
1391
|
+
if (args.some((a, i) => i !== pt.fn.decreases && !same(sortOf(a, true), pt.fn.params[i].sort))) {
|
|
1392
|
+
ok = false;
|
|
1393
|
+
return o;
|
|
1394
|
+
}
|
|
1395
|
+
return { op: 'call', fn: pt.fn.name, args };
|
|
1396
|
+
}
|
|
1397
|
+
if (as.some((a, i) => !same(sortOf(a, true), f.params[i].sort))) {
|
|
1398
|
+
ok = false;
|
|
1399
|
+
return o;
|
|
1400
|
+
}
|
|
1401
|
+
return { ...o, args: as };
|
|
1402
|
+
}
|
|
1403
|
+
if (o.op === 'ctor') {
|
|
1404
|
+
const d = dataNamed(o.data), as = o.args.map(a => lift(a));
|
|
1405
|
+
if (as.some((a, i) => !same(sortOf(a, true), fieldSort(d.ctors[o.ctor].fields[i]))))
|
|
1406
|
+
ok = false;
|
|
1407
|
+
return { ...o, args: as };
|
|
1408
|
+
}
|
|
1409
|
+
const out = Object.fromEntries(Object.entries(o).map(([key, x]) => [key, lift(x)]));
|
|
1410
|
+
if (out.op === '=' && !same(sortOf(out.a, true), sortOf(out.b, true)))
|
|
1411
|
+
ok = false;
|
|
1412
|
+
const om = o;
|
|
1413
|
+
if (om.op === 'match' && !same(sortOf(out.of, true), dataNamed(om.data)))
|
|
1414
|
+
ok = false;
|
|
1415
|
+
return out;
|
|
1416
|
+
};
|
|
1417
|
+
const holds = lift(l.holds);
|
|
1418
|
+
if (!ok || !JSON.stringify(holds).includes(JSON.stringify({ op: 'var', name })))
|
|
1419
|
+
return undefined;
|
|
1420
|
+
return { name: `${l.name}, at ${to.name}`, vars: l.vars.map(x => (x.name === v ? { name, sort: to } : x)), holds, params: l.params };
|
|
1421
|
+
}
|
|
1422
|
+
// The mutual scheme on v: the companions of the datatypes v's fields reach (through the group), or none.
|
|
1423
|
+
function mutualScheme(l, v, d) {
|
|
1424
|
+
const at = new Map([[d.name, { law: l, v }]]), todo = [d];
|
|
1425
|
+
while (todo.length) {
|
|
1426
|
+
const x = todo.shift(), from = at.get(x.name);
|
|
1427
|
+
for (const c of x.ctors)
|
|
1428
|
+
for (const f of c.fields) {
|
|
1429
|
+
if (!inGroup(x, f) || at.has(f.sort.data))
|
|
1430
|
+
continue;
|
|
1431
|
+
const to = dataNamed(f.sort.data), name = `${v}${to.name}`, law = companionOf(from.law, from.v, to, name);
|
|
1432
|
+
if (!law)
|
|
1433
|
+
return undefined;
|
|
1434
|
+
at.set(to.name, { law, v: name });
|
|
1435
|
+
todo.push(to);
|
|
1436
|
+
}
|
|
601
1437
|
}
|
|
602
|
-
|
|
1438
|
+
if (at.size < 2)
|
|
1439
|
+
return undefined;
|
|
1440
|
+
const order = [...at.keys()];
|
|
1441
|
+
const usesOf = (x, xv) => x.ctors.flatMap((c, j) => c.fields.flatMap((f, i) => (inGroup(x, f) && at.has(f.sort.data)
|
|
1442
|
+
? [{ subst: { [at.get(f.sort.data).v]: get(fieldMap(x, j, i), ev(xv)) }, guard: eqC(get(tagMap(x.name), ev(xv)), k(j)), member: order.indexOf(f.sort.data) }] : [])));
|
|
1443
|
+
return { name: `mutual structure of ${v}`, on: { v, d }, uses: usesOf(d, v), companions: order.slice(1).map(n => { const x = dataNamed(n), a = at.get(n); return { law: a.law, on: { v: a.v, d: x }, uses: usesOf(x, a.v) }; }) };
|
|
603
1444
|
}
|
|
1445
|
+
// The width of a program's ranks' middle: its widest measure (1 when every recursion is structural).
|
|
1446
|
+
const rankWidth = (p) => Math.max(1, ...p.fns.map(f => (f.decreases === undefined ? f.measure?.comps.length ?? 1 : 1)));
|
|
1447
|
+
// Where a well-founded scheme's node wants a datatype's shape: at the values its measure reads, and at those its measure
|
|
1448
|
+
// reads at each use (a constructor's value a call passes: its size from its fields).
|
|
1449
|
+
function rankShapes(shapes, rank, uses) {
|
|
1450
|
+
const out = new Map(shapes.map(e => [tid(e), e]));
|
|
1451
|
+
walk(uses.map(u => rank.map(e => substE(e, { vars: u.subst, maps: {}, flags: {} }))), o => { if (o.op === 'get' && o.map.endsWith('.size'))
|
|
1452
|
+
out.set(tid(o.key), o.key); });
|
|
1453
|
+
return [...out.values()];
|
|
1454
|
+
}
|
|
1455
|
+
// The parameters a function's definition is decided at when they are a constructor's value: a structural one's
|
|
1456
|
+
// decreasing parameter (a datatype's), a well-founded one's parameters of a datatype its body matches on.
|
|
1457
|
+
function unfoldAt(f) {
|
|
1458
|
+
if (f.decreases !== undefined)
|
|
1459
|
+
return f.params[f.decreases].sort instanceof Data ? [f.decreases] : [];
|
|
1460
|
+
if (!f.measure)
|
|
1461
|
+
return [];
|
|
1462
|
+
const out = new Set();
|
|
1463
|
+
walk(f.body(), o => { if (o.op === 'match' && o.of.op === 'var') {
|
|
1464
|
+
const i = f.params.findIndex(q => `${f.name}.${q.name}` === o.of.name);
|
|
1465
|
+
if (i >= 0 && f.params[i].sort instanceof Data)
|
|
1466
|
+
out.add(i);
|
|
1467
|
+
} });
|
|
1468
|
+
return [...out];
|
|
1469
|
+
}
|
|
1470
|
+
// The inductions the search tries for a law (its schemes), for tests.
|
|
1471
|
+
export const schemesOf = (p, l) => schemes(p, l);
|
|
604
1472
|
function schemes(p, l) {
|
|
605
1473
|
const out = [], seen = new Set();
|
|
606
1474
|
const add = (s) => { const id = JSON.stringify(s.uses); if (!seen.has(id)) {
|
|
@@ -614,12 +1482,13 @@ function schemes(p, l) {
|
|
|
614
1482
|
return;
|
|
615
1483
|
const f = fnOf(o.fn), args = o.args;
|
|
616
1484
|
f.body();
|
|
617
|
-
|
|
1485
|
+
const m = f.decreases === undefined ? f.measure : undefined;
|
|
1486
|
+
if (f.decreases === undefined && !m)
|
|
618
1487
|
return;
|
|
619
|
-
const at = args[f.decreases];
|
|
620
|
-
if (at.op !== 'var' || !l.vars.some(v => v.name === at.name))
|
|
1488
|
+
const at = args[f.decreases ?? 0];
|
|
1489
|
+
if (!m && (at.op !== 'var' || !l.vars.some(v => v.name === at.name)))
|
|
621
1490
|
return;
|
|
622
|
-
const d = f.params[f.decreases].sort;
|
|
1491
|
+
const d = f.params[f.decreases ?? 0].sort;
|
|
623
1492
|
const formal = new Map(f.params.map((q, i) => [`${f.name}.${q.name}`, args[i].op === 'var' && l.vars.some(v => v.name === args[i].name) ? ev(args[i].name) : undefined]));
|
|
624
1493
|
const uses = [];
|
|
625
1494
|
const go = (x, env, guard) => {
|
|
@@ -636,6 +1505,22 @@ function schemes(p, l) {
|
|
|
636
1505
|
t.cases.forEach((c, j) => { const e2 = new Map(env); c.binds.forEach((b, i) => e2.set(b, get(fieldMap(dd, j, i), of))); go(c.body, e2, [...guard, eqC(get(tagMap(dd.name), of), k(j))]); });
|
|
637
1506
|
return;
|
|
638
1507
|
}
|
|
1508
|
+
if (t.op === 'natmatch') {
|
|
1509
|
+
go(t.of, env, guard);
|
|
1510
|
+
const of = encX(p, t.of, env), e2 = new Map(env);
|
|
1511
|
+
go(t.zero, env, [...guard, eqC(of, k(0))]);
|
|
1512
|
+
e2.set(t.pred, { op: 'sub', a: of, b: k(1) });
|
|
1513
|
+
go(t.succ, e2, [...guard, { op: 'not', x: eqC(of, k(0)) }]);
|
|
1514
|
+
return;
|
|
1515
|
+
}
|
|
1516
|
+
// (a well-founded call's descent may need its if's condition: x − y below x where y ≤ x)
|
|
1517
|
+
if (m && t.op === 'if') {
|
|
1518
|
+
const c = encY(p, t.c, env);
|
|
1519
|
+
go(t.c, env, guard);
|
|
1520
|
+
go(t.then, env, [...guard, c]);
|
|
1521
|
+
go(t.else, env, [...guard, { op: 'not', x: c }]);
|
|
1522
|
+
return;
|
|
1523
|
+
}
|
|
639
1524
|
if (t.op === 'call' && t.fn === f.name) {
|
|
640
1525
|
const subst = {};
|
|
641
1526
|
let ok = true;
|
|
@@ -657,21 +1542,34 @@ function schemes(p, l) {
|
|
|
657
1542
|
};
|
|
658
1543
|
const env = new Map([...formal].flatMap(([key, e]) => (e ? [[key, e]] : [])));
|
|
659
1544
|
go(f.body(), env, []);
|
|
660
|
-
if (uses.length)
|
|
1545
|
+
if (!uses.length)
|
|
1546
|
+
return;
|
|
1547
|
+
if (!m) {
|
|
661
1548
|
add({ name: `recursion of ${f.name} on ${at.name}`, on: { v: at.name, d }, uses });
|
|
1549
|
+
return;
|
|
1550
|
+
}
|
|
1551
|
+
// Well-founded: ranked by the measure at the law's arguments; the law's variables it reads named.
|
|
1552
|
+
const read = f.params.flatMap((q, i) => (m.comps.some(c => c.coef[i]) && args[i].op === 'var' && l.vars.some(v => v.name === args[i].name) ? [{ i, v: args[i].name }] : []));
|
|
1553
|
+
add({ name: `recursion of ${f.name} on ${read.map(r => r.v).join(', ') || 'its arguments'}`, uses, rank: m.comps.map(c => encX(p, measureX(f, c, args), new Map())),
|
|
1554
|
+
shapes: read.filter(r => f.params[r.i].sort instanceof Data).map(r => ev(r.v)) });
|
|
662
1555
|
});
|
|
663
|
-
// Structural induction on each variable of a datatype, the others fixed
|
|
1556
|
+
// Structural induction on each variable of a datatype, the others fixed; mutual where its fields reach another of its
|
|
1557
|
+
// group (the law and its companions), plain where they reach none or no companion is found.
|
|
664
1558
|
for (const v of l.vars) {
|
|
665
1559
|
if (!(v.sort instanceof Data))
|
|
666
1560
|
continue;
|
|
667
|
-
const d = v.sort, x = ev(v.name);
|
|
668
|
-
|
|
1561
|
+
const d = v.sort, x = ev(v.name), mut = mutualScheme(l, v.name, d);
|
|
1562
|
+
if (mut) {
|
|
1563
|
+
out.push(mut);
|
|
1564
|
+
continue;
|
|
1565
|
+
}
|
|
1566
|
+
add({ name: `structure of ${v.name}`, on: { v: v.name, d }, uses: d.ctors.flatMap((c, j) => c.fields.flatMap((f, i) => (isDataField(f.sort) && f.sort.data === d.name ? [{ subst: { [v.name]: get(fieldMap(d, j, i), x) }, guard: eqC(get(tagMap(d.name), x), k(j)) }] : []))) });
|
|
669
1567
|
}
|
|
670
1568
|
return out;
|
|
671
1569
|
}
|
|
672
1570
|
function instancesOf(p, fs, w) {
|
|
673
1571
|
// Past the attempt's time, what is found so far (the attempt then fails or proves with it).
|
|
674
|
-
const late = () => w.until !== undefined &&
|
|
1572
|
+
const late = () => w.until !== undefined && clock() > w.until;
|
|
675
1573
|
const tt = performance.now(), lap = (what) => { if (dataTrace)
|
|
676
1574
|
console.error(`[data] instances: ${what} at ${Math.round(performance.now() - tt)} ms`); };
|
|
677
1575
|
const insts = [], seen = new Set();
|
|
@@ -727,10 +1625,27 @@ function instancesOf(p, fs, w) {
|
|
|
727
1625
|
inst(`fn ${f.name}`, as);
|
|
728
1626
|
const body = reads(holdsAt(`fn ${f.name}`, as));
|
|
729
1627
|
ctors(note(body));
|
|
730
|
-
|
|
1628
|
+
for (const b of body)
|
|
1629
|
+
next.push(b);
|
|
731
1630
|
}
|
|
732
1631
|
frontier = next;
|
|
733
1632
|
}
|
|
1633
|
+
// Euclid's fact at every key a remainder or quotient is read at (a read the facts bring is read too).
|
|
1634
|
+
const ariths = () => {
|
|
1635
|
+
for (let pass = 0; pass < 2; pass++)
|
|
1636
|
+
for (const r of [...all]) {
|
|
1637
|
+
if (r.map === posMap || r.map === negMap) {
|
|
1638
|
+
inst('zcode', [r.key]);
|
|
1639
|
+
note(reads(holdsAt('zcode', [r.key])));
|
|
1640
|
+
}
|
|
1641
|
+
const m = arithMap.exec(r.map);
|
|
1642
|
+
if (m) {
|
|
1643
|
+
inst(`arith ${m[1]}`, [r.key]);
|
|
1644
|
+
note(reads(holdsAt(`arith ${m[1]}`, [r.key])));
|
|
1645
|
+
}
|
|
1646
|
+
}
|
|
1647
|
+
};
|
|
1648
|
+
ariths();
|
|
734
1649
|
lap(`unfolded, ${insts.length} instances, ${all.length} reads`);
|
|
735
1650
|
const closure = (model) => {
|
|
736
1651
|
const parent = new Map(), terms = new Map(), adj = new Map();
|
|
@@ -836,29 +1751,31 @@ function instancesOf(p, fs, w) {
|
|
|
836
1751
|
}
|
|
837
1752
|
for (const r of [...all]) {
|
|
838
1753
|
const f = fnOfMap(r.map);
|
|
839
|
-
if (!f
|
|
1754
|
+
if (!f)
|
|
840
1755
|
continue;
|
|
841
1756
|
const as = argsOf(r.key, f.params.length);
|
|
842
1757
|
if (!as)
|
|
843
1758
|
continue;
|
|
844
|
-
const
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
1759
|
+
for (const di of unfoldAt(f)) {
|
|
1760
|
+
const own = as[di], d = f.params[di].sort, root = cl.find(tid(own));
|
|
1761
|
+
const built = (t) => (t.op === 'get' && d.ctors.some((c, j) => c.fields.length && t.map === ctorMap(d, j))) || (t.op === 'const' && t.value < d.nullary);
|
|
1762
|
+
const cands = built(own) ? [own] : [...cl.terms.values()].filter(t => built(t) && cl.find(tid(t)) === root);
|
|
1763
|
+
for (const t of cands) {
|
|
1764
|
+
const as2 = as.map((a, i) => (i === di ? t : a)), rid = JSON.stringify({ map: f.name, key: keyOf(as2) }), key2 = JSON.stringify([fs.at.get(`fn ${f.name}`), as2]);
|
|
1765
|
+
const anchor = { read: tid(get(f.name, r.key)), arg: own, value: t }, had = anchoredAt.get(key2) ?? [];
|
|
1766
|
+
if (!had.some(x => x.read === anchor.read))
|
|
1767
|
+
anchoredAt.set(key2, [...had, anchor]);
|
|
1768
|
+
if (unfolded.has(rid) || extra >= 12 || done.has(sigOf(f.name, as2)))
|
|
1769
|
+
continue;
|
|
1770
|
+
unfolded.add(rid);
|
|
1771
|
+
extra++;
|
|
1772
|
+
done.add(sigOf(f.name, as2));
|
|
1773
|
+
if (dataTrace)
|
|
1774
|
+
console.error(`[data] unfold ${f.name} at ${JSON.stringify(as2).slice(0, 160)} for ${JSON.stringify(r.key).slice(0, 80)}`);
|
|
1775
|
+
inst(`fn ${f.name}`, as2);
|
|
1776
|
+
note([{ map: f.name, key: keyOf(as2) }]);
|
|
1777
|
+
ctors(note(reads(holdsAt(`fn ${f.name}`, as2))));
|
|
1778
|
+
}
|
|
862
1779
|
}
|
|
863
1780
|
}
|
|
864
1781
|
}
|
|
@@ -877,8 +1794,8 @@ function instancesOf(p, fs, w) {
|
|
|
877
1794
|
if (c.fields.length && t.map === ctorMap(d, j))
|
|
878
1795
|
return d;
|
|
879
1796
|
for (const [i, fl] of c.fields.entries())
|
|
880
|
-
if (fl.sort
|
|
881
|
-
return
|
|
1797
|
+
if (isDataField(fl.sort) && t.map === fieldMap(d, j, i))
|
|
1798
|
+
return dataNamed(fl.sort.data);
|
|
882
1799
|
}
|
|
883
1800
|
}
|
|
884
1801
|
return undefined;
|
|
@@ -889,6 +1806,12 @@ function instancesOf(p, fs, w) {
|
|
|
889
1806
|
} };
|
|
890
1807
|
for (const t of w.shapesAt)
|
|
891
1808
|
shape(t, typeOfVar(p, t));
|
|
1809
|
+
// A size the goal reads (a measure's, in a well-founded call's obligation): the shape of its value, which gives the
|
|
1810
|
+
// size from the fields.
|
|
1811
|
+
for (const r of reads([w.roots, w.others]))
|
|
1812
|
+
for (const d of p.datas)
|
|
1813
|
+
if (r.map === sizeMap(d.name))
|
|
1814
|
+
shape(r.key, d);
|
|
892
1815
|
if (w.depth >= 2)
|
|
893
1816
|
for (const r of [...all])
|
|
894
1817
|
for (const d of p.datas)
|
|
@@ -1045,6 +1968,9 @@ function instancesOf(p, fs, w) {
|
|
|
1045
1968
|
for (const d of used.values())
|
|
1046
1969
|
if (!w.every || d.n >= w.models.length)
|
|
1047
1970
|
inst(`cong ${d.map}`, d.at);
|
|
1971
|
+
for (const q of [...insts])
|
|
1972
|
+
note(reads(holdsAt(fs.facts[q.fact].name, q.at)));
|
|
1973
|
+
ariths();
|
|
1048
1974
|
lap(`congruences, ${insts.length} instances, ${w.models.length} models`);
|
|
1049
1975
|
return insts;
|
|
1050
1976
|
}
|
|
@@ -1163,11 +2089,10 @@ const dataTrace = !!globalThis.process?.env?.DATA_TRACE;
|
|
|
1163
2089
|
// Debugging: every congruence a model's closure makes, not only those explanations use.
|
|
1164
2090
|
const allCong = !!globalThis.process?.env?.DATA_ALLCONG;
|
|
1165
2091
|
function attempt(p, fs, l, scheme, depth, lemmas, level, pool) {
|
|
1166
|
-
const t0 =
|
|
2092
|
+
const t0 = clock();
|
|
1167
2093
|
anchoredAt.clear();
|
|
1168
2094
|
// A few models for the instances; the whole pool for the cases (a rare case gets its own models from it).
|
|
1169
|
-
const
|
|
1170
|
-
const concl = encY(p, l.holds, new Map());
|
|
2095
|
+
const few = (ms) => ms.slice(0, 16);
|
|
1171
2096
|
const specs = [], where = new Map();
|
|
1172
2097
|
for (const lm of lemmas) {
|
|
1173
2098
|
const local = new Map();
|
|
@@ -1181,31 +2106,39 @@ function attempt(p, fs, l, scheme, depth, lemmas, level, pool) {
|
|
|
1181
2106
|
specs.push({ ...n, uses: n.uses.map(u => ({ ...u, node: local.get(u.node) ?? u.node })) });
|
|
1182
2107
|
});
|
|
1183
2108
|
}
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
const
|
|
1187
|
-
const
|
|
1188
|
-
const
|
|
1189
|
-
|
|
1190
|
-
const
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
2109
|
+
// The scheme's nodes: the law's, then each companion's (a mutual induction), each its own models. A use of member i
|
|
2110
|
+
// reads node -1 - i until the nodes are placed.
|
|
2111
|
+
const members = [{ law: l, on: scheme?.on, uses: scheme?.uses ?? [], pool, rank: scheme?.rank, shapes: scheme?.shapes }, ...(scheme?.companions ?? []).map(c => ({ ...c, pool: modelsFor(p, c.law, pool.length, 5) }))];
|
|
2112
|
+
const concls = members.map(mb => encY(p, mb.law.holds, new Map()));
|
|
2113
|
+
const base = specs.length, title = `${l.name}${scheme ? ` (${scheme.name})` : ''}`;
|
|
2114
|
+
varSorts = new Map(members.flatMap(mb => mb.law.vars.flatMap(v => (v.sort instanceof Data ? [[v.name, v.sort]] : []))));
|
|
2115
|
+
const lemmaNodes = lemmas.map(lm => ({ law: lm.law, node: where.get(lm.spec.name), concl: specs[where.get(lm.spec.name)].concl }));
|
|
2116
|
+
const useConcl = (u) => substC(u.node < 0 ? concls[-1 - u.node] : specs[u.node].concl, { vars: u.subst, maps: {}, flags: {} });
|
|
2117
|
+
const plans = [];
|
|
2118
|
+
for (const [mi, mb] of members.entries()) {
|
|
2119
|
+
const models = mb.pool.slice(0, 24), concl = concls[mi], name = mi === 0 ? title : `${title}, at ${mb.on.d.name}`;
|
|
2120
|
+
const ih = mb.uses.map(u => ({ node: -1 - (u.member ?? mi), subst: u.subst, guard: u.guard }));
|
|
2121
|
+
const shapesAt = mb.rank ? rankShapes(mb.shapes ?? [], mb.rank, mb.uses) : mb.on && mb.on.d !== 'nat' ? [ev(mb.on.v)] : [];
|
|
2122
|
+
const ihConcls = ih.map(useConcl);
|
|
2123
|
+
// Uses of the lemmas, where a lemma's largest read matches a read of this goal (unfolded), its key up to the
|
|
2124
|
+
// equalities the unfolded definitions give (any branch); twice over, the uses' own conclusions read the second time.
|
|
2125
|
+
const pre = instancesOf(p, fs, { roots: [concl], others: ihConcls, depth, shapesAt, etaAt: [], models: [], until: t0 + attemptMs.ms });
|
|
2126
|
+
const lemmaUses = lemmaUsesFor(p, fs, lemmaNodes, [concl, ihConcls], pre, models);
|
|
2127
|
+
const uses = [...ih, ...lemmaUses];
|
|
2128
|
+
if (dataTrace)
|
|
2129
|
+
for (const u of lemmaUses)
|
|
2130
|
+
console.error(`[data] lemma use ${specs[u.node].name.slice(0, 40)} at ${Object.entries(u.subst).map(([x, t]) => `${x} := ${JSON.stringify(t).slice(0, 80)}`).join('; ')}`);
|
|
2131
|
+
// The lemmas' conclusions are unfolded like the law's (they are what the goal reads, said another way); the cycle's
|
|
2132
|
+
// own uses only from depth 3.
|
|
2133
|
+
const instsFor = (ms, us, every = false) => instancesOf(p, fs, { roots: [concl], others: us.map(useConcl), depth, shapesAt, etaAt: [...shapesAt, ...valuesCompared(p, mb.law)], models: ms, every, until: t0 + attemptMs.ms });
|
|
2134
|
+
const cases = scheme ? casesOf(p, pre.filter(q => fs.facts[q.fact].name.startsWith('fn ')).map(q => holdsAt(fs, q)), new Set(mb.law.vars.map(v => v.name)), mb.pool) : [];
|
|
2135
|
+
if (cases.length <= 1) {
|
|
2136
|
+
// One node: four uses of a lemma, twelve in all, the best first (as lemmaUsesFor ranks them).
|
|
2137
|
+
const one = capUses(uses, 12);
|
|
2138
|
+
plans.push([{ name, hyps: [], concl, sub: 0, insts: instsFor(models, one), uses: one }]);
|
|
2139
|
+
continue;
|
|
2140
|
+
}
|
|
2141
|
+
const plan = [{ name, hyps: [], concl, sub: 1, insts: [], uses: [], cases: cases.map(c => andC(yesC, ...c.lits)) }];
|
|
1209
2142
|
for (const [i, c] of cases.entries()) {
|
|
1210
2143
|
// The uses whose guard some model of the case meets (all, for a case no model reaches).
|
|
1211
2144
|
const holdsIn = (g) => !c.models || c.models.some(({ m, env }) => { try {
|
|
@@ -1221,26 +2154,42 @@ function attempt(p, fs, l, scheme, depth, lemmas, level, pool) {
|
|
|
1221
2154
|
catch {
|
|
1222
2155
|
return false;
|
|
1223
2156
|
} }); };
|
|
1224
|
-
const us = uses.filter(u => holdsIn(u.guard) && applies(u));
|
|
2157
|
+
const us = capUses(uses.filter(u => holdsIn(u.guard) && applies(u)));
|
|
1225
2158
|
const ms = few(c.models ?? c.near ?? models);
|
|
1226
|
-
|
|
2159
|
+
plan.push({ name: `${name}, case ${i + 1}`, hyps: c.lits, concl, sub: 0, insts: reached(p, fs, instsFor(ms, us), [c.lits, concl, us.map(u => [u.guard, useConcl(u)]), shapesAt], ms), uses: us });
|
|
1227
2160
|
}
|
|
2161
|
+
plans.push(plan);
|
|
2162
|
+
}
|
|
2163
|
+
// Placed: each member's node, then its cases; a main node with cases uses them, at rank (level, size, 1) above theirs.
|
|
2164
|
+
const offsets = plans.map((_, i) => base + plans.slice(0, i).reduce((n, q) => n + q.length, 0));
|
|
2165
|
+
for (const [mi, plan] of plans.entries()) {
|
|
2166
|
+
const mb = members[mi], size = !mb.on ? k(0) : mb.on.d === 'nat' ? ev(mb.on.v) : get(sizeMap(mb.on.d.name), ev(mb.on.v));
|
|
2167
|
+
// (level, the measure or the size, 0 or 1), the measure's place padded to the program's widest measure
|
|
2168
|
+
const comps = mb.rank ?? [size], mid = [...comps, ...Array.from({ length: rankWidth(p) - comps.length }, () => k(0))];
|
|
2169
|
+
for (const { sub, cases, ...n } of plan)
|
|
2170
|
+
specs.push({ ...n, rank: [k(level), ...mid, k(sub)], uses: cases ? cases.map((g, i) => ({ node: offsets[mi] + 1 + i, subst: {}, guard: g })) : n.uses.map(u => (u.node < 0 ? { ...u, node: offsets[-1 - u.node] } : u)) });
|
|
1228
2171
|
}
|
|
2172
|
+
const name = title;
|
|
1229
2173
|
// Each attempt within its own time (a goal past it is left unknown, and the attempt fails).
|
|
1230
2174
|
if (dataTrace)
|
|
1231
|
-
console.error(`[data] @${Math.round(performance.now())} ${l.name} (${scheme?.name}) depth ${depth}, ${lemmas.length} lemmas: built in ${Math.round(
|
|
1232
|
-
if (
|
|
1233
|
-
return { verdict: { proven: false, checked: false, node: name, goal: name, stuck: 'unknown' }, specs };
|
|
2175
|
+
console.error(`[data] @${Math.round(performance.now())} ${l.name} (${scheme?.name}) depth ${depth}, ${lemmas.length} lemmas: built in ${Math.round(clock() - t0)} ms (counted), ${specs.length} nodes, ${specs.reduce((n, x) => n + x.insts.length, 0)} instances`);
|
|
2176
|
+
if (clock() > t0 + attemptMs.ms)
|
|
2177
|
+
return { verdict: { proven: false, checked: false, node: name, goal: name, stuck: 'unknown' }, specs, main: base };
|
|
1234
2178
|
const was = pairedKeys.skip;
|
|
1235
2179
|
pairedKeys.skip = true;
|
|
1236
2180
|
try {
|
|
1237
|
-
return { verdict: withDeadline(t0 + attemptMs.ms, () => proveCyclic(fs.facts, specs)), specs };
|
|
2181
|
+
return { verdict: withDeadline(t0 + attemptMs.ms, () => proveCyclic(fs.facts, specs)), specs, main: base };
|
|
1238
2182
|
}
|
|
1239
2183
|
finally {
|
|
1240
2184
|
pairedKeys.skip = was;
|
|
1241
2185
|
}
|
|
1242
2186
|
}
|
|
1243
2187
|
export const attemptMs = { ms: 8000 };
|
|
2188
|
+
// A datatype search's budgets count work, not wall time (kernel/prove.ts's `counted`: the solver's rows and each node's),
|
|
2189
|
+
// 300 units a millisecond: a search finds the same proof on any machine, at any load. On Modal (2 vCPU) the corpus's
|
|
2190
|
+
// searches do 50 to 263 units a millisecond (`runs/modal/cover/data3`, data-cal), so a budget lets each do at least the
|
|
2191
|
+
// work it did there in wall time.
|
|
2192
|
+
const dataRate = 300;
|
|
1244
2193
|
// A case node's instances, those its models reach: from every read of its conclusion and its uses' (and their guards),
|
|
1245
2194
|
// through each instance at a read reached (a function's at its call, a constructor's at its cell, a shape or eta at a
|
|
1246
2195
|
// value whose tag is read), the reads of what it gives where the models take it (an if's taken branch, an implication's
|
|
@@ -1292,6 +2241,12 @@ function reached(p, fs, insts, from, ms) {
|
|
|
1292
2241
|
return [get(name.slice(3), keyOf(q.at))];
|
|
1293
2242
|
if (name.startsWith('ctor '))
|
|
1294
2243
|
return [get(name.slice(5), keyOf(q.at))];
|
|
2244
|
+
if (name.startsWith('arith ')) {
|
|
2245
|
+
const k2 = Number(name.slice(6));
|
|
2246
|
+
return [get(divMap(k2), q.at[0]), get(modMap(k2), q.at[0])];
|
|
2247
|
+
}
|
|
2248
|
+
if (name === 'zcode')
|
|
2249
|
+
return [get(posMap, q.at[0]), get(negMap, q.at[0])];
|
|
1295
2250
|
if (name.startsWith('shape ') || name.startsWith('eta ')) {
|
|
1296
2251
|
const d = name.split(' ')[1];
|
|
1297
2252
|
return [get(tagMap(d), q.at[0]), get(sizeMap(d), q.at[0]), ...p.datas.filter(x => x.name === d).flatMap(x => x.ctors.flatMap((c, j) => c.fields.map((_, i) => get(fieldMap(x, j, i), q.at[0]))))];
|
|
@@ -1386,6 +2341,7 @@ function lemmaUsesFor(p, fs, lemmas, from, insts, models = []) {
|
|
|
1386
2341
|
reads.push(o); });
|
|
1387
2342
|
for (let round = 0; round < 2; round++) {
|
|
1388
2343
|
const fresh = [];
|
|
2344
|
+
const cands = [];
|
|
1389
2345
|
// The newest lemma first (the one the law is tried again for).
|
|
1390
2346
|
for (const lm of [...lemmas].reverse()) {
|
|
1391
2347
|
if (round > 0 && out.some(u => u.node === lm.node))
|
|
@@ -1410,28 +2366,70 @@ function lemmaUsesFor(p, fs, lemmas, from, insts, models = []) {
|
|
|
1410
2366
|
continue;
|
|
1411
2367
|
// Where a model gives the lemma's arguments the goal read's (a match through every branch's classes may be none).
|
|
1412
2368
|
const there = pats.map(a => substE(a, { vars: sub, maps: {}, flags: {} }));
|
|
1413
|
-
|
|
2369
|
+
const score = models.length ? models.filter(({ m, env }) => { try {
|
|
1414
2370
|
return there.every((a, q) => m.evalE(a, env) === m.evalE(args[q], env));
|
|
1415
2371
|
}
|
|
1416
2372
|
catch {
|
|
1417
2373
|
return false;
|
|
1418
|
-
} })
|
|
2374
|
+
} }).length : 1;
|
|
2375
|
+
if (!score)
|
|
1419
2376
|
continue;
|
|
1420
2377
|
const k2 = JSON.stringify([lm.node, sub]);
|
|
1421
|
-
if (seen.has(k2)
|
|
2378
|
+
if (seen.has(k2))
|
|
1422
2379
|
continue;
|
|
1423
2380
|
seen.add(k2);
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
2381
|
+
const names = Object.keys(sub).sort();
|
|
2382
|
+
const fp = models.length ? JSON.stringify([lm.node, models.map(({ m, env }) => names.map(x => { try {
|
|
2383
|
+
return m.evalE(sub[x], env);
|
|
2384
|
+
}
|
|
2385
|
+
catch {
|
|
2386
|
+
return -1;
|
|
2387
|
+
} }))]) : k2;
|
|
2388
|
+
cands.push({ use: { node: lm.node, subst: sub, guard: yesC }, at: { goal: t, lemma: substE(top, { vars: sub, maps: {}, flags: {} }) }, score, fp, size: k2.length });
|
|
1428
2389
|
}
|
|
1429
2390
|
}
|
|
1430
2391
|
}
|
|
2392
|
+
// First, as found: four of a lemma, twelve in all (the next round reads their conclusions). Then the rest, best first
|
|
2393
|
+
// (the most models, then the smallest terms), one of each set of values, up to `perLemma` of a lemma and `total` in
|
|
2394
|
+
// all: a case node keeps those its models meet, `perCase` of a lemma (attempt's `applies`), so a use that holds in
|
|
2395
|
+
// one case only (the insertion's other side) isn't crowded out by those found first.
|
|
2396
|
+
const fps = new Set(), first = new Set();
|
|
2397
|
+
const take = (c) => { fps.add(c.fp); out.push(c.use); matchedAt.set(c.use, c.at); };
|
|
2398
|
+
for (const c of cands) {
|
|
2399
|
+
if (out.length >= 12 || out.filter(u => u.node === c.use.node).length >= 4)
|
|
2400
|
+
continue;
|
|
2401
|
+
take(c);
|
|
2402
|
+
first.add(c);
|
|
2403
|
+
walk(substC(lemmas.find(lm => lm.node === c.use.node).concl, { vars: c.use.subst, maps: {}, flags: {} }), o => { if (o.op === 'get')
|
|
2404
|
+
fresh.push(o); });
|
|
2405
|
+
}
|
|
2406
|
+
const rest = cands.filter(c => !first.has(c)).sort((a, b) => b.score - a.score || a.size - b.size);
|
|
2407
|
+
for (const c of rest)
|
|
2408
|
+
if (!fps.has(c.fp) && out.length < lemmaCaps.total && out.filter(u => u.node === c.use.node).length < lemmaCaps.perLemma)
|
|
2409
|
+
take(c);
|
|
1431
2410
|
reads = fresh;
|
|
1432
2411
|
}
|
|
1433
2412
|
return out;
|
|
1434
2413
|
}
|
|
2414
|
+
// How many uses of lemmas a goal gets: the candidates kept (`perLemma` of each lemma, `total`), and of those a case node
|
|
2415
|
+
// keeps (`perCase` of each lemma, those its models meet).
|
|
2416
|
+
export const lemmaCaps = { perLemma: 8, total: 24, perCase: 4 };
|
|
2417
|
+
// A node's uses: the scheme's (the cycle's, read at nodes not yet placed) all, `perCase` of each lemma, `total` lemma
|
|
2418
|
+
// uses in all, in the order given.
|
|
2419
|
+
function capUses(uses, total = Infinity) {
|
|
2420
|
+
const per = new Map();
|
|
2421
|
+
let all = 0;
|
|
2422
|
+
return uses.filter(u => {
|
|
2423
|
+
if (u.node < 0)
|
|
2424
|
+
return true;
|
|
2425
|
+
const n = per.get(u.node) ?? 0;
|
|
2426
|
+
if (n >= lemmaCaps.perCase || all >= total)
|
|
2427
|
+
return false;
|
|
2428
|
+
per.set(u.node, n + 1);
|
|
2429
|
+
all++;
|
|
2430
|
+
return true;
|
|
2431
|
+
});
|
|
2432
|
+
}
|
|
1435
2433
|
// The equalities a condition may give: those in a positive place (a conjunction's parts, a disjunction's disjuncts that
|
|
1436
2434
|
// aren't negations, so an implication's conclusion and never its premise).
|
|
1437
2435
|
function equalities(c) {
|
|
@@ -1453,19 +2451,23 @@ function modelsFor(p, l, n, seed) {
|
|
|
1453
2451
|
});
|
|
1454
2452
|
}
|
|
1455
2453
|
// Prove a law with the lemmas given (each proven, with its nodes), trying each scheme at each depth.
|
|
1456
|
-
|
|
2454
|
+
// `without`: 'plain', the law with no induction where no scheme applies; 'cases', no induction, then by the cases of
|
|
2455
|
+
// each variable (a natural's zero and successor, a datatype's constructors), none of them using the law again.
|
|
2456
|
+
function proveWith(p, fs, l, lemmas, level, opts, until, per, depths, without) {
|
|
1457
2457
|
const models = modelsFor(p, l, 240, opts.seed ?? 3);
|
|
1458
2458
|
let tried = 0, last;
|
|
1459
|
-
const
|
|
2459
|
+
const cases = l.vars.flatMap((v) => (v.sort === 'nat' || v.sort instanceof Data ? [{ name: `cases of ${v.name}`, on: { v: v.name, d: v.sort }, uses: [] }] : []));
|
|
2460
|
+
const found = opts.induction === false || without === 'cases' ? [] : schemes(p, l);
|
|
2461
|
+
const ss = opts.induction === false ? [undefined] : without === 'cases' ? [undefined, ...cases] : without === 'plain' && !found.length ? [undefined] : found;
|
|
1460
2462
|
for (const depth of depths.filter(d => d <= (opts.depth ?? 3)))
|
|
1461
2463
|
for (const s of ss) {
|
|
1462
|
-
if (
|
|
2464
|
+
if (clock() > until)
|
|
1463
2465
|
return { tried, last };
|
|
1464
2466
|
tried++;
|
|
1465
|
-
const { verdict, specs } = (attemptMs.ms = Math.min(per * (opts.patience ?? 1), until -
|
|
2467
|
+
const { verdict, specs, main } = (attemptMs.ms = Math.min(per * (opts.patience ?? 1), until - clock()), attempt(p, fs, l, s, depth, lemmas, level, models));
|
|
1466
2468
|
last = verdict;
|
|
1467
2469
|
if (verdict.proven && verdict.checked)
|
|
1468
|
-
return { out: { law: l, spec: specs
|
|
2470
|
+
return { out: { law: l, spec: specs[main], nodes: specs }, verdict, scheme: s?.name ?? (without ? 'no induction' : undefined), tried };
|
|
1469
2471
|
}
|
|
1470
2472
|
return { tried, last };
|
|
1471
2473
|
}
|
|
@@ -1527,7 +2529,7 @@ export function conjectures(p, l, max = 24, maxSize = 6) {
|
|
|
1527
2529
|
addT({ x: { op: 'call', fn: f.name, args: args.map(a => a.x) }, sort: f.ret, size });
|
|
1528
2530
|
for (const d of p.datas)
|
|
1529
2531
|
d.ctors.forEach((c, j) => { if (c.fields.length)
|
|
1530
|
-
for (const args of build(c.fields.map(f => (f
|
|
2532
|
+
for (const args of build(c.fields.map(f => fieldSort(f)), size - 1))
|
|
1531
2533
|
addT({ x: { op: 'ctor', data: d.name, ctor: j, args: args.map(a => a.x) }, sort: d, size }); });
|
|
1532
2534
|
for (const [a2, b2] of build(['nat', 'nat'], size - 1))
|
|
1533
2535
|
if (JSON.stringify(a2.x) <= JSON.stringify(b2.x))
|
|
@@ -1641,8 +2643,10 @@ export function conjectures(p, l, max = 24, maxSize = 6) {
|
|
|
1641
2643
|
const kept = [];
|
|
1642
2644
|
// The definitions, unfolded at a constructor: f(.., C(x, y), ..) → its case's body, where that is a plain term.
|
|
1643
2645
|
for (const f of fnsUsed) {
|
|
1644
|
-
|
|
1645
|
-
|
|
2646
|
+
// (a structural function's decreasing parameter, a well-founded one's the body matches on first)
|
|
2647
|
+
const body = f.body(), top = body.op === 'match' && body.of.op === 'var' ? f.params.findIndex(q => `${f.name}.${q.name}` === body.of.name) : -1;
|
|
2648
|
+
const di = f.decreases ?? (f.measure ? top : -1);
|
|
2649
|
+
if (body.op !== 'match' || di < 0 || di !== top)
|
|
1646
2650
|
continue;
|
|
1647
2651
|
const d = f.params[di].sort;
|
|
1648
2652
|
body.cases.forEach((c, j) => {
|
|
@@ -1745,15 +2749,28 @@ export const showX = (x) => {
|
|
|
1745
2749
|
case 'min':
|
|
1746
2750
|
case 'max': return `${x.op}(${showX(x.a)}, ${showX(x.b)})`;
|
|
1747
2751
|
case 'mul': return `${x.k}·${showX(x.a)}`;
|
|
2752
|
+
case 'prod': return `${showX(x.a)}·${showX(x.b)}`;
|
|
2753
|
+
case 'mod':
|
|
2754
|
+
case 'imod': return `${showX(x.a)} mod ${x.k}`;
|
|
2755
|
+
case 'div':
|
|
2756
|
+
case 'idiv': return `${showX(x.a)} div ${x.k}`;
|
|
2757
|
+
case 'int': return String(x.value);
|
|
2758
|
+
case 'iadd': return `${showX(x.a)} + ${showX(x.b)}`;
|
|
2759
|
+
case 'isub': return `${showX(x.a)} − ${showX(x.b)}`;
|
|
2760
|
+
case 'ineg': return `−${showX(x.a)}`;
|
|
2761
|
+
case 'imul': return `${x.k}·${showX(x.a)}`;
|
|
2762
|
+
case 'ofnat': return showX(x.a);
|
|
1748
2763
|
case 'if': return `if ${showY(x.c)} then ${showX(x.then)} else ${showX(x.else)}`;
|
|
1749
2764
|
case 'bool': return showY(x.c);
|
|
1750
2765
|
case 'apply': return `${x.param}(${showX(x.arg)})`;
|
|
2766
|
+
case 'size': return `size(${showX(x.of)})`;
|
|
1751
2767
|
case 'call': return `${x.fn}(${x.args.map(showX).join(', ')})`;
|
|
1752
2768
|
case 'ctor': {
|
|
1753
2769
|
const d = [...fns.values()].length >= 0 ? x.data : x.data;
|
|
1754
2770
|
return `${d}.${x.ctor}${x.args.length ? `(${x.args.map(showX).join(', ')})` : ''}`;
|
|
1755
2771
|
}
|
|
1756
2772
|
case 'match': return `match ${showX(x.of)}`;
|
|
2773
|
+
case 'natmatch': return `match ${showX(x.of)}`;
|
|
1757
2774
|
}
|
|
1758
2775
|
};
|
|
1759
2776
|
export const showY = (y) => {
|
|
@@ -1761,6 +2778,8 @@ export const showY = (y) => {
|
|
|
1761
2778
|
case '<':
|
|
1762
2779
|
case '<=':
|
|
1763
2780
|
case '=': return `${showX(y.a)} ${y.op} ${showX(y.b)}`;
|
|
2781
|
+
case 'i<':
|
|
2782
|
+
case 'i<=': return `${showX(y.a)} ${y.op.slice(1)} ${showX(y.b)}`;
|
|
1764
2783
|
case 'and': return y.xs.map(showY).join(' and ');
|
|
1765
2784
|
case 'or': return y.xs.map(showY).join(' or ');
|
|
1766
2785
|
case 'not': return `not (${showY(y.x)})`;
|
|
@@ -1771,16 +2790,49 @@ export const showY = (y) => {
|
|
|
1771
2790
|
// Prove a law over datatypes: refuted by a random value where one breaks it; else proven by a cycle the search finds,
|
|
1772
2791
|
// with lemmas found from the values when the law alone isn't (`lemmas: false` turns that off; an array gives lemmas to
|
|
1773
2792
|
// prove and use instead, which the corpus never does).
|
|
1774
|
-
export function proveData(l, opts = {}) {
|
|
1775
|
-
|
|
1776
|
-
const
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
2793
|
+
export function proveData(l, opts = {}) { return counted(dataRate, () => bounded(() => proveDataCounted(l, opts))); }
|
|
2794
|
+
function proveDataCounted(l, opts) {
|
|
2795
|
+
const t0 = performance.now(), until = clock() + (opts.ms ?? 120_000);
|
|
2796
|
+
// Where the law calls a non-recursive function, or reaches a recursive one that agrees with a built-in (core/transfer.ts):
|
|
2797
|
+
// the law alone at one unfolding first, then the law said through them, then the rest of the search on the law.
|
|
2798
|
+
const plan = opts.transfer === false || opts.induction === false || Array.isArray(opts.lemmas) ? undefined : transferPlan(l);
|
|
2799
|
+
if (!plan)
|
|
2800
|
+
return searchIn(l, opts, until).verdict;
|
|
2801
|
+
const quick = searchIn(l, opts, until, undefined, 'first').verdict;
|
|
2802
|
+
if (quick.proven || quick.counterexample)
|
|
2803
|
+
return quick;
|
|
2804
|
+
// (a law said through its definitions past what the encoding's terms take is left as stated)
|
|
2805
|
+
let through;
|
|
2806
|
+
try {
|
|
2807
|
+
through = proveThrough(l, plan, opts, until);
|
|
2808
|
+
}
|
|
2809
|
+
catch (e) {
|
|
2810
|
+
if (!(e instanceof RangeError))
|
|
2811
|
+
throw e;
|
|
2812
|
+
}
|
|
2813
|
+
if (through?.proven)
|
|
2814
|
+
return { ...through, ms: Math.round(performance.now() - t0) };
|
|
2815
|
+
const rest = searchIn(l, opts, until, undefined, 'rest').verdict;
|
|
2816
|
+
return { ...rest, tried: rest.tried + quick.tried + (through?.tried ?? 0), ms: Math.round(performance.now() - t0), ...(rest.goal || rest.proven ? {} : { goal: quick.goal, stuck: quick.stuck }) };
|
|
2817
|
+
}
|
|
2818
|
+
export function searchIn(l, opts, until, ctx, phase = 'all') {
|
|
2819
|
+
const t0 = performance.now(), w0 = work.n;
|
|
2820
|
+
const p = ctx?.p ?? programOf([l, ...(Array.isArray(opts.lemmas) ? opts.lemmas : [])]), fs = ctx?.fs ?? factsOf(p);
|
|
2821
|
+
const base = ctx?.base ?? [], lv = ctx?.level ?? 0;
|
|
2822
|
+
let out;
|
|
2823
|
+
const done = (v) => {
|
|
2824
|
+
if (dataTrace)
|
|
2825
|
+
console.error(`[data] ${l.name}: ${work.n - w0} units of work in ${Math.round(performance.now() - t0)} ms`);
|
|
2826
|
+
return { verdict: { law: l.name, ...v, ms: Math.round(performance.now() - t0) }, out };
|
|
2827
|
+
};
|
|
2828
|
+
if (phase !== 'rest') {
|
|
2829
|
+
const cex = refute(l, opts.samples ?? 400);
|
|
2830
|
+
if (cex)
|
|
2831
|
+
return done({ proven: false, checked: false, counterexample: cex, lemmas: [], tried: 0 });
|
|
2832
|
+
}
|
|
1781
2833
|
// The law alone at one unfolding; then lemmas at one unfolding each (the law tried again after each its goal
|
|
1782
|
-
// matches); then the law at two; then lemmas at two; last the law at three
|
|
1783
|
-
// proof uses it.
|
|
2834
|
+
// matches); then the law at two; then lemmas at two; last the law at three, and without induction (by cases). A lemma
|
|
2835
|
+
// is named in the verdict when the proof uses it.
|
|
1784
2836
|
let tried = 0;
|
|
1785
2837
|
const used = (r, lemmas) => {
|
|
1786
2838
|
const nodes = r.nodes, seen = new Set(), todo = [nodes.indexOf(r.spec)];
|
|
@@ -1794,34 +2846,49 @@ export function proveData(l, opts = {}) {
|
|
|
1794
2846
|
}
|
|
1795
2847
|
return lemmas.filter(q => seen.has(nodes.indexOf(nodes.find(n => n.name === q.spec.name)))).map(q => q.law.name);
|
|
1796
2848
|
};
|
|
1797
|
-
const ok = (r, lemmas, prog = p) => (tried += r.tried, r.out ? done({ proven: true, checked: true, proof: r.verdict, lemmas: used(r.out, lemmas), scheme: r.scheme, tried, program: prog, node: r.out.nodes.indexOf(r.out.spec) }) : undefined);
|
|
1798
|
-
|
|
1799
|
-
|
|
2849
|
+
const ok = (r, lemmas, prog = p) => (tried += r.tried, r.out ? (out = r.out, done({ proven: true, checked: true, proof: r.verdict, lemmas: used(r.out, lemmas), scheme: r.scheme, tried, program: prog, node: r.out.nodes.indexOf(r.out.spec) })) : undefined);
|
|
2850
|
+
// A law that calls no function (arithmetic alone: an integer's, a remainder's) has nothing to induct on: its cases.
|
|
2851
|
+
if (phase !== 'rest' && opts.induction !== false && !JSON.stringify(l.holds).includes('"op":"call"')) {
|
|
2852
|
+
const c = ok(proveWith(p, fs, l, base, lv + 1, opts, until, 3000, [1, 2], 'cases'), base);
|
|
2853
|
+
if (c)
|
|
2854
|
+
return c;
|
|
2855
|
+
}
|
|
2856
|
+
const first = phase === 'rest' ? undefined : proveWith(p, fs, l, base, lv + 1, opts, until, 6000, [1], 'plain');
|
|
2857
|
+
const one = first && ok(first, base);
|
|
1800
2858
|
if (one)
|
|
1801
2859
|
return one;
|
|
2860
|
+
if (phase === 'first')
|
|
2861
|
+
return done({ proven: false, checked: false, lemmas: [], tried, ...stuckOf(first.last) });
|
|
1802
2862
|
const without = opts.lemmas === false || opts.induction === false;
|
|
1803
2863
|
if (without)
|
|
1804
|
-
return ok(proveWith(p, fs, l,
|
|
2864
|
+
return ok(proveWith(p, fs, l, base, lv + 1, opts, until, 20000, [2, 3]), base) ?? (opts.induction === false ? undefined : ok(proveWith(p, fs, l, base, lv + 1, opts, until, 3000, [1, 2], 'cases'), base)) ?? done({ proven: false, checked: false, lemmas: [], tried, ...stuckOf(first?.last) });
|
|
1805
2865
|
const cands = Array.isArray(opts.lemmas) ? opts.lemmas : conjectures(p, l);
|
|
1806
|
-
const p2 = programOf([l, ...cands]), fs2 = factsOf(p2);
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
2866
|
+
const p2 = ctx ? p : programOf([l, ...cands]), fs2 = ctx ? fs : factsOf(p2);
|
|
2867
|
+
// Each scheme's goals: the law's, and each companion's (a lemma may be what only a companion's goal reads).
|
|
2868
|
+
const goalOf = (law, uses, on, concls, shapes) => {
|
|
2869
|
+
const concl = encY(p2, law.holds, new Map()), ihc = uses.map(u => substC(concls[u.member ?? 0] ?? concl, { vars: u.subst, maps: {}, flags: {} }));
|
|
2870
|
+
return { from: [concl, ihc], insts: instancesOf(p2, fs2, { roots: [concl], others: ihc, depth: 2, shapesAt: shapes ?? (on && on.d !== 'nat' ? [ev(on.v)] : []), etaAt: [], models: [] }) };
|
|
1810
2871
|
};
|
|
1811
|
-
const goals = schemes(p2, l).
|
|
2872
|
+
const goals = schemes(p2, l).flatMap(s => {
|
|
2873
|
+
const all = [{ law: l, uses: s.uses, on: s.on, shapes: s.shapes }, ...(s.companions ?? [])], concls = all.map(m => encY(p2, m.law.holds, new Map()));
|
|
2874
|
+
return all.map((m, i) => goalOf(m.law, m.uses.map(u => ({ ...u, member: u.member ?? i })), m.on, concls, 'shapes' in m ? m.shapes : undefined));
|
|
2875
|
+
});
|
|
1812
2876
|
const matchesGoal = (c) => goals.some(g => lemmaUsesFor(p2, fs2, [{ law: c, node: 0, concl: encY(p2, c.holds, new Map()) }], g.from, g.insts).length > 0);
|
|
1813
2877
|
// Those the goal matches first, of those the ones of the law's shape (an implication for an implication) first.
|
|
1814
2878
|
const implication = (y) => y.op === 'or' && y.xs.length === 2 && y.xs[0].op === 'not';
|
|
1815
2879
|
const score = (c, hit) => (hit ? 2 : 0) + (implication(c.holds) === implication(l.holds) ? 1 : 0);
|
|
2880
|
+
if (dataTrace)
|
|
2881
|
+
console.error(`[data] ${l.name}: ${cands.length} conjectures at ${Math.round(clock())} of ${Math.round(until)}`);
|
|
1816
2882
|
const ranked = cands.map(c => ({ c, hit: matchesGoal(c) })).sort((a, b) => score(b.c, b.hit) - score(a.c, a.hit));
|
|
1817
|
-
|
|
2883
|
+
// The lemmas proven: those given (`base`) and those found, each found one a level above the last.
|
|
2884
|
+
const proven = [...base], failed = new Set(), level = () => lv + 1 + proven.length - base.length;
|
|
1818
2885
|
const lemmasAt = (depth) => {
|
|
1819
2886
|
for (const { c, hit } of ranked) {
|
|
1820
|
-
if (
|
|
2887
|
+
if (clock() > until)
|
|
1821
2888
|
return undefined;
|
|
1822
2889
|
if (proven.some(q => q.law === c) || (depth === 2 && !failed.has(c)) || refute(c, 200))
|
|
1823
2890
|
continue;
|
|
1824
|
-
const r = proveWith(p2, fs2, c, proven,
|
|
2891
|
+
const r = proveWith(p2, fs2, c, proven, level(), opts, Math.min(until, clock() + (depth === 1 ? 8000 : 12000) * (opts.patience ?? 1)), 5000, [depth]);
|
|
1825
2892
|
tried += r.tried;
|
|
1826
2893
|
if (!r.out) {
|
|
1827
2894
|
failed.add(c);
|
|
@@ -1831,7 +2898,7 @@ export function proveData(l, opts = {}) {
|
|
|
1831
2898
|
proven.push(r.out);
|
|
1832
2899
|
if (!hit && implication(c.holds) !== implication(l.holds))
|
|
1833
2900
|
continue;
|
|
1834
|
-
const again = ok(proveWith(p2, fs2, l, proven,
|
|
2901
|
+
const again = ok(proveWith(p2, fs2, l, proven, level(), opts, until, 6000, depth === 1 ? [1] : [1, 2]), proven, p2);
|
|
1835
2902
|
if (again)
|
|
1836
2903
|
return again;
|
|
1837
2904
|
}
|
|
@@ -1839,19 +2906,23 @@ export function proveData(l, opts = {}) {
|
|
|
1839
2906
|
};
|
|
1840
2907
|
const steps = [
|
|
1841
2908
|
() => lemmasAt(1),
|
|
1842
|
-
() => ok(proveWith(p, fs, l,
|
|
1843
|
-
() => (proven.length ? ok(proveWith(p2, fs2, l, proven,
|
|
2909
|
+
() => ok(proveWith(p, fs, l, base, lv + 1, opts, until, 30000, [2]), base),
|
|
2910
|
+
() => (proven.length > base.length ? ok(proveWith(p2, fs2, l, proven, level(), opts, until, 30000, [2]), proven, p2) : undefined),
|
|
1844
2911
|
() => lemmasAt(2),
|
|
1845
|
-
() => ok(proveWith(p2, fs2, l, proven,
|
|
2912
|
+
() => ok(proveWith(p2, fs2, l, proven, level(), opts, until, 12000, [3]), proven, p2),
|
|
2913
|
+
// No induction: the law by the cases of a variable (or none), its definitions unfolded.
|
|
2914
|
+
() => ok(proveWith(p, fs, l, base, lv + 1, opts, until, 3000, [1, 2], 'cases'), base),
|
|
1846
2915
|
];
|
|
1847
|
-
for (const step of steps) {
|
|
1848
|
-
if (
|
|
2916
|
+
for (const [i, step] of steps.entries()) {
|
|
2917
|
+
if (clock() > until)
|
|
1849
2918
|
break;
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
2919
|
+
if (dataTrace)
|
|
2920
|
+
console.error(`[data] ${l.name}: step ${i} at ${Math.round(clock())} of ${Math.round(until)}`);
|
|
2921
|
+
const r = step();
|
|
2922
|
+
if (r)
|
|
2923
|
+
return r;
|
|
1853
2924
|
}
|
|
1854
|
-
return done({ proven: false, checked: false, lemmas: [], tried, ...stuckOf(first
|
|
2925
|
+
return done({ proven: false, checked: false, lemmas: [], tried, ...stuckOf(first?.last) });
|
|
1855
2926
|
}
|
|
1856
2927
|
const stuckOf = (v) => (v && !v.proven ? { goal: v.goal, stuck: v.stuck } : v && !v.checked ? { goal: 'check', stuck: v.why } : {});
|
|
1857
2928
|
// The law with no cycle: its definitions unfolded, the kernel's prover alone (what the library proves without the search).
|