gdp-wired 0.18.0 → 0.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/core/cycles.d.ts +23 -2
- package/dist/core/cycles.js +451 -51
- package/dist/core/cyclic.d.ts +105 -7
- package/dist/core/cyclic.js +800 -103
- package/dist/core/data.d.ts +61 -5
- package/dist/core/data.js +487 -113
- package/dist/core/discover.js +54 -33
- package/dist/core/dsl.d.ts +4 -2
- package/dist/core/dsl.js +47 -12
- package/dist/core/solver.d.ts +3 -0
- package/dist/core/solver.js +5 -0
- package/dist/core/translate.d.ts +30 -0
- package/dist/core/translate.js +578 -0
- package/dist/kernel/prove.d.ts +16 -0
- package/dist/kernel/prove.js +160 -13
- package/dist/lean-kernel/emit.d.ts +36 -1
- package/dist/lean-kernel/emit.js +527 -85
- package/dist/lean-kernel/library.js +2 -1
- package/package.json +1 -1
package/dist/core/data.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
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";
|
|
4
5
|
// --- sorts and the AST ------------------------------------------------------------------------------------------------
|
|
5
6
|
export const self = { self: true };
|
|
6
7
|
export const natural = 'nat';
|
|
@@ -22,6 +23,14 @@ export class Nat {
|
|
|
22
23
|
atLeast(b) { return new Bool({ op: '<=', a: natX(b), b: this.x }); }
|
|
23
24
|
eq(b) { return new Bool({ op: '=', a: this.x, b: natX(b) }); }
|
|
24
25
|
ne(b) { return this.eq(b).not(); }
|
|
26
|
+
// A natural as 0 or the successor of another (`pred`): recursion on a natural passes `pred` where this is a parameter.
|
|
27
|
+
// oxlint-disable-next-line typescript/no-explicit-any
|
|
28
|
+
match(cases) {
|
|
29
|
+
const pred = bindName('pred'), zero = cases.zero(), succ = cases.succ({ pred: new Nat({ op: 'var', name: pred }) });
|
|
30
|
+
const body = (o) => (typeof o === 'number' ? natX(o) : o instanceof Bool ? { op: 'bool', c: o.y } : o.x);
|
|
31
|
+
const x = { op: 'natmatch', of: this.x, zero: body(zero), pred, succ: body(succ) };
|
|
32
|
+
return (zero instanceof Bool ? new Bool({ op: 'holds', x }) : zero instanceof Val ? new Val(zero.data, x) : new Nat(x));
|
|
33
|
+
}
|
|
25
34
|
}
|
|
26
35
|
const natX = (t) => (typeof t === 'number' ? { op: 'nat', value: t } : t.x);
|
|
27
36
|
export class Bool {
|
|
@@ -53,7 +62,7 @@ export class Val {
|
|
|
53
62
|
if (!f)
|
|
54
63
|
throw new Error(`${d.name}: match has no case ${c.name}`);
|
|
55
64
|
const binds = c.fields.map(fl => bindName(fl.name));
|
|
56
|
-
const fields = Object.fromEntries(c.fields.map((fl, i) => [fl.name, wrap(fl
|
|
65
|
+
const fields = Object.fromEntries(c.fields.map((fl, i) => [fl.name, wrap(fieldSort(fl), { op: 'var', name: binds[i] })]));
|
|
57
66
|
return { binds, out: f(fields) };
|
|
58
67
|
});
|
|
59
68
|
const first = built[0].out;
|
|
@@ -75,21 +84,34 @@ export function when(c, a, b) {
|
|
|
75
84
|
const x = { op: 'if', c: c.y, then: ax, else: bx };
|
|
76
85
|
return (a instanceof Val ? new Val(a.data, x) : new Nat(x));
|
|
77
86
|
}
|
|
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
|
-
//
|
|
87
|
+
// A datatype: its constructors, each with fields that are naturals or values of a datatype (`self`, another of its group,
|
|
88
|
+
// or one declared before it). Its constructors are ordered as Lean's bijection numbers them: those of no field, then
|
|
89
|
+
// those with no field of its group, then the rest; a group with a datatype that has no value is refused.
|
|
90
|
+
const declared = new Map();
|
|
91
|
+
let declaredSeq = 0;
|
|
92
|
+
export const dataNamed = (name) => { const d = declared.get(name); if (!d)
|
|
93
|
+
throw new Error(`no datatype ${name}`); return d; };
|
|
94
|
+
// A field's sort; whether it is of the datatype's group (the fields its size counts, and an induction descends into).
|
|
95
|
+
export const fieldSort = (f) => (f.sort === 'nat' ? 'nat' : dataNamed(f.sort.data));
|
|
96
|
+
export const inGroup = (d, f) => f.sort !== 'nat' && d.group.includes(f.sort.data);
|
|
81
97
|
export class Data {
|
|
82
98
|
name;
|
|
99
|
+
group;
|
|
83
100
|
ctors;
|
|
84
|
-
|
|
101
|
+
seq = ++declaredSeq;
|
|
102
|
+
constructor(name, decl, group = [name]) {
|
|
85
103
|
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`);
|
|
104
|
+
this.group = group;
|
|
91
105
|
if (!/^[A-Za-z][A-Za-z0-9_]*$/.test(name))
|
|
92
106
|
throw new Error(`${name}: a datatype's name is letters and digits`);
|
|
107
|
+
const all = Object.entries(decl).map(([c, fs]) => ({ name: c, fields: Object.entries(fs).map(([f, s]) => ({ name: f, sort: s })) }));
|
|
108
|
+
for (const c of all)
|
|
109
|
+
for (const f of c.fields)
|
|
110
|
+
if (f.sort !== 'nat' && !group.includes(f.sort.data) && !declared.has(f.sort.data))
|
|
111
|
+
throw new Error(`${name}.${c.name}.${f.name}: no datatype ${f.sort.data}`);
|
|
112
|
+
const rank = (c) => (!c.fields.length ? 0 : c.fields.every(f => !(f.sort !== 'nat' && group.includes(f.sort.data))) ? 1 : 2);
|
|
113
|
+
this.ctors = [...all].sort((a, b) => rank(a) - rank(b));
|
|
114
|
+
declared.set(name, this);
|
|
93
115
|
}
|
|
94
116
|
index(ctor) { const i = this.ctors.findIndex(c => c.name === ctor); if (i < 0)
|
|
95
117
|
throw new Error(`${this.name} has no constructor ${ctor}`); return i; }
|
|
@@ -98,32 +120,111 @@ export class Data {
|
|
|
98
120
|
const j = this.index(ctor), c = this.ctors[j];
|
|
99
121
|
if (args.length !== c.fields.length)
|
|
100
122
|
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
|
|
123
|
+
return new Val(this, { op: 'ctor', data: this.name, ctor: j, args: c.fields.map((f, i) => xOf(fieldSort(f), args[i])) });
|
|
102
124
|
}
|
|
125
|
+
// How many constructors deep its smallest value is (a constructor of no field of the group: 0), and each constructor's.
|
|
126
|
+
ctorHeight(c) { return c.fields.reduce((h, f) => (inGroup(this, f) ? Math.max(h, 1 + dataNamed(f.sort.data).height) : h), 0); }
|
|
127
|
+
get height() { return groupHeights(this.group).get(this.name); }
|
|
103
128
|
}
|
|
104
|
-
//
|
|
129
|
+
// The heights of a group's datatypes (least fixpoint; Infinity where a datatype has no value).
|
|
130
|
+
function groupHeights(group) {
|
|
131
|
+
const h = new Map(group.map(n => [n, Infinity]));
|
|
132
|
+
for (let changed = true; changed;) {
|
|
133
|
+
changed = false;
|
|
134
|
+
for (const n of group)
|
|
135
|
+
for (const c of dataNamed(n).ctors) {
|
|
136
|
+
const v = c.fields.reduce((m, f) => (f.sort !== 'nat' && group.includes(f.sort.data) ? Math.max(m, 1 + h.get(f.sort.data)) : m), 0);
|
|
137
|
+
if (v < h.get(n)) {
|
|
138
|
+
h.set(n, v);
|
|
139
|
+
changed = true;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return h;
|
|
144
|
+
}
|
|
145
|
+
// A group's checks: each datatype has a value, and Lean's decoding terminates (a datatype every constructor of which has
|
|
146
|
+
// 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).
|
|
147
|
+
function checkGroup(ds) {
|
|
148
|
+
const hs = groupHeights(ds.map(d => d.name));
|
|
149
|
+
for (const d of ds)
|
|
150
|
+
if (hs.get(d.name) === Infinity)
|
|
151
|
+
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`);
|
|
152
|
+
void weakOrder(ds);
|
|
153
|
+
}
|
|
154
|
+
// 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
|
|
155
|
+
// with a field of the group); the order Lean's measure numbers them in (each weak one after the group's datatypes its
|
|
156
|
+
// fields name), or a refusal.
|
|
157
|
+
export function weakOrder(ds) {
|
|
158
|
+
const weak = (d) => d.nullary === 0 && d.ctors[0].fields.some(f => inGroup(d, f));
|
|
159
|
+
const out = new Map(), busy = new Set();
|
|
160
|
+
const visit = (d) => {
|
|
161
|
+
const had = out.get(d.name);
|
|
162
|
+
if (had !== undefined)
|
|
163
|
+
return had;
|
|
164
|
+
if (!weak(d)) {
|
|
165
|
+
out.set(d.name, 0);
|
|
166
|
+
return 0;
|
|
167
|
+
}
|
|
168
|
+
if (busy.has(d.name))
|
|
169
|
+
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`);
|
|
170
|
+
busy.add(d.name);
|
|
171
|
+
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))] : []))));
|
|
172
|
+
busy.delete(d.name);
|
|
173
|
+
if (d.ctors.some(c => c.fields.some(f => f.sort !== 'nat' && f.sort.data === d.name)) && weak(d))
|
|
174
|
+
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`);
|
|
175
|
+
out.set(d.name, o);
|
|
176
|
+
return o;
|
|
177
|
+
};
|
|
178
|
+
ds.forEach(visit);
|
|
179
|
+
return out;
|
|
180
|
+
}
|
|
181
|
+
const normField = (own, group) => (s) => {
|
|
182
|
+
if (s === self)
|
|
183
|
+
return { data: own };
|
|
184
|
+
if (s === 'nat' || (s && typeof s === 'object' && 'nat' in s))
|
|
185
|
+
return 'nat';
|
|
186
|
+
if (s instanceof Data)
|
|
187
|
+
return { data: s.name };
|
|
188
|
+
if (typeof s === 'string' && group.includes(s))
|
|
189
|
+
return { data: s };
|
|
190
|
+
throw new Error(`${own}: a field is natural, self, a datatype declared before, or a name of its group`);
|
|
191
|
+
};
|
|
192
|
+
const withCtors = (d) => { for (const c of d.ctors)
|
|
193
|
+
Object.defineProperty(d, c.name, c.fields.length ? { value: (...args) => d.make(c.name, ...args) } : { get: () => d.make(c.name) }); };
|
|
194
|
+
// `data('List', {nil: {}, cons: {head: natural, tail: self}})`: `List.nil`, `List.cons(h, t)`, and `xs.match({...})`. A
|
|
195
|
+
// field may be a datatype declared before (`data('Bag', {none: {}, more: {items: List, rest: self}})`).
|
|
105
196
|
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) });
|
|
197
|
+
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)]))])));
|
|
198
|
+
checkGroup([d]);
|
|
199
|
+
withCtors(d);
|
|
110
200
|
return d;
|
|
111
201
|
}
|
|
202
|
+
// Datatypes declared together, each field naming one of the group by its name (a mutual pair: a rose tree and its
|
|
203
|
+
// forest, `mutual({Rose: {node: {value: natural, kids: 'Forest'}}, Forest: {nil: {}, cons: {head: 'Rose', tail: 'Forest'}}})`).
|
|
204
|
+
// oxlint-disable-next-line typescript/no-explicit-any
|
|
205
|
+
export function mutual(decls) {
|
|
206
|
+
const group = Object.keys(decls);
|
|
207
|
+
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));
|
|
208
|
+
checkGroup(ds);
|
|
209
|
+
ds.forEach(withCtors);
|
|
210
|
+
return Object.fromEntries(ds.map(d => [d.name, d]));
|
|
211
|
+
}
|
|
112
212
|
const fns = new Map();
|
|
113
213
|
export function define(name, params, ret, body) {
|
|
114
214
|
if (!/^[A-Za-z][A-Za-z0-9_]*$/.test(name))
|
|
115
215
|
throw new Error(`${name}: a function's name is letters and digits`);
|
|
116
216
|
const ps = Object.entries(params).map(([n, s]) => ({ name: n, sort: s }));
|
|
117
|
-
let built;
|
|
217
|
+
let built, rec;
|
|
118
218
|
const fn = { name, params: ps, ret, paramFns: [], body: () => {
|
|
119
219
|
if (!built) {
|
|
120
220
|
const args = Object.fromEntries(ps.map(p => [p.name, wrap(p.sort, { op: 'var', name: `${name}.${p.name}` })]));
|
|
121
221
|
built = xOf(ret, body(args));
|
|
122
|
-
fn.decreases = decreasing(fn, built);
|
|
123
222
|
fn.paramFns = [...new Set(paramsIn(built))];
|
|
223
|
+
walk(built, o => { if (o.op === 'call' && !fns.has(o.fn))
|
|
224
|
+
throw new Error(`${name} calls ${String(o.fn)}, which isn't defined`); });
|
|
124
225
|
}
|
|
125
226
|
return built;
|
|
126
|
-
} };
|
|
227
|
+
}, get decreases() { return (rec ??= recursion(fn)).decreases; }, get ring() { return (rec ??= recursion(fn)).ring; }, set rec(r) { rec = r; } };
|
|
127
228
|
fns.set(name, fn);
|
|
128
229
|
const call = (...args) => {
|
|
129
230
|
if (args.length !== ps.length)
|
|
@@ -148,8 +249,8 @@ function walk(t, f) {
|
|
|
148
249
|
for (const v of Object.values(o))
|
|
149
250
|
walk(v, f);
|
|
150
251
|
}
|
|
151
|
-
// The
|
|
152
|
-
function
|
|
252
|
+
// The calls a body makes, each with what its bound names are fields of (a parameter, through matches).
|
|
253
|
+
function callsIn(body) {
|
|
153
254
|
const calls = [];
|
|
154
255
|
const go = (x, fieldsOf) => {
|
|
155
256
|
if (!x || typeof x !== 'object')
|
|
@@ -166,24 +267,63 @@ function decreasing(fn, body) {
|
|
|
166
267
|
c.binds.forEach(b => m.set(b, of)); go(c.body, m); });
|
|
167
268
|
return;
|
|
168
269
|
}
|
|
169
|
-
if (o.op === '
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
270
|
+
if (o.op === 'natmatch') {
|
|
271
|
+
go(o.of, fieldsOf);
|
|
272
|
+
go(o.zero, fieldsOf);
|
|
273
|
+
const of = o.of.op === 'var' ? (fieldsOf.get(o.of.name) ?? o.of.name) : undefined, m = new Map(fieldsOf);
|
|
274
|
+
if (of)
|
|
275
|
+
m.set(o.pred, of);
|
|
276
|
+
go(o.succ, m);
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
if (o.op === 'call')
|
|
280
|
+
calls.push({ fn: o.fn, args: o.args, fieldsOf });
|
|
173
281
|
for (const v of Object.values(o))
|
|
174
282
|
go(v, fieldsOf);
|
|
175
283
|
};
|
|
176
284
|
go(body, new Map());
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
285
|
+
return calls;
|
|
286
|
+
}
|
|
287
|
+
// A function's ring (the functions it calls that call it back, itself among them) and the parameter of each every call
|
|
288
|
+
// within the ring passes a field of, or a refusal. Each of the ring is given its own.
|
|
289
|
+
function recursion(fn) {
|
|
290
|
+
const callees = (f) => [...new Set(callsIn(f.body()).map(c => c.fn))];
|
|
291
|
+
const reach = (from) => { const seen = new Set(), todo = [from]; while (todo.length) {
|
|
292
|
+
const g = todo.pop();
|
|
293
|
+
for (const h of callees(fnOf(g)))
|
|
294
|
+
if (!seen.has(h)) {
|
|
295
|
+
seen.add(h);
|
|
296
|
+
todo.push(h);
|
|
297
|
+
}
|
|
298
|
+
} return seen; };
|
|
299
|
+
const mine = reach(fn.name);
|
|
300
|
+
const ring = [...mine].filter(g => g === fn.name || reach(g).has(fn.name));
|
|
301
|
+
if (!mine.has(fn.name)) {
|
|
302
|
+
fn.rec = { ring: [fn.name] };
|
|
303
|
+
return { ring: [fn.name] };
|
|
185
304
|
}
|
|
186
|
-
|
|
305
|
+
const members = [fn.name, ...ring.filter(g => g !== fn.name)].map(fnOf);
|
|
306
|
+
const options = members.map(f => f.params.flatMap((p, i) => (p.sort instanceof Data || p.sort === 'nat' ? [i] : [])));
|
|
307
|
+
const calls = members.map(f => callsIn(f.body()).filter(c => ring.includes(c.fn)));
|
|
308
|
+
// The first choice (by parameter order) every call within the ring meets.
|
|
309
|
+
const choose = (i, chosen) => {
|
|
310
|
+
if (i === members.length) {
|
|
311
|
+
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}`; }));
|
|
312
|
+
return ok ? chosen : undefined;
|
|
313
|
+
}
|
|
314
|
+
for (const o of options[i]) {
|
|
315
|
+
const r = choose(i + 1, [...chosen, o]);
|
|
316
|
+
if (r)
|
|
317
|
+
return r;
|
|
318
|
+
}
|
|
319
|
+
return undefined;
|
|
320
|
+
};
|
|
321
|
+
const got = choose(0, []);
|
|
322
|
+
if (!got)
|
|
323
|
+
throw new Error(`${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(', ')}` : ''})`);
|
|
324
|
+
const names = members.map(f => f.name);
|
|
325
|
+
members.forEach((f, a) => { f.rec = { decreases: got[a], ring: names }; });
|
|
326
|
+
return { decreases: got[0], ring: names };
|
|
187
327
|
}
|
|
188
328
|
export function law(name, vars, holds) {
|
|
189
329
|
const vs = Object.entries(vars).map(([n, s]) => ({ name: n, sort: s }));
|
|
@@ -211,6 +351,14 @@ export function evalX(x, env, params) {
|
|
|
211
351
|
case 'bool': return evalY(x.c, env, params) ? 1 : 0;
|
|
212
352
|
case 'apply': return params[x.param](asNat(evalX(x.arg, env, params)));
|
|
213
353
|
case 'ctor': return { c: x.ctor, f: x.args.map(a => evalX(a, env, params)) };
|
|
354
|
+
case 'natmatch': {
|
|
355
|
+
const n = asNat(evalX(x.of, env, params));
|
|
356
|
+
if (n === 0)
|
|
357
|
+
return evalX(x.zero, env, params);
|
|
358
|
+
const e2 = new Map(env);
|
|
359
|
+
e2.set(x.pred, n - 1);
|
|
360
|
+
return evalX(x.succ, e2, params);
|
|
361
|
+
}
|
|
214
362
|
case 'match': {
|
|
215
363
|
const v = evalX(x.of, env, params), cs = x.cases[v.c];
|
|
216
364
|
const e2 = new Map(env);
|
|
@@ -243,7 +391,7 @@ export const showValue = (d, v) => {
|
|
|
243
391
|
if (d === 'nat' || d === 'bool')
|
|
244
392
|
return JSON.stringify(v);
|
|
245
393
|
const c = d.ctors[v.c];
|
|
246
|
-
return c.fields.length ? `${c.name}(${c.fields.map((f, i) => showValue(f
|
|
394
|
+
return c.fields.length ? `${c.name}(${c.fields.map((f, i) => showValue(fieldSort(f), v.f[i])).join(', ')})` : c.name;
|
|
247
395
|
};
|
|
248
396
|
export const rng = (seed) => () => { seed = (seed * 1103515245 + 12345) % 2147483648; return seed / 2147483648; };
|
|
249
397
|
export function randomValue(s, rand, size, natMax = 5) {
|
|
@@ -251,11 +399,13 @@ export function randomValue(s, rand, size, natMax = 5) {
|
|
|
251
399
|
return Math.floor(rand() * (natMax + 1));
|
|
252
400
|
if (s === 'bool')
|
|
253
401
|
return rand() < 0.5 ? 0 : 1;
|
|
254
|
-
|
|
402
|
+
// The leaves: constructors of no field of the group, or (a datatype with none, a rose tree) those of least height.
|
|
403
|
+
const base = s.ctors.filter(c => c.fields.every(f => !inGroup(s, f)));
|
|
404
|
+
const leaves = base.length ? base : s.ctors.filter(c => s.ctorHeight(c) === s.height);
|
|
255
405
|
const pick = size <= 0 || rand() < 0.25 ? leaves : s.ctors;
|
|
256
406
|
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
|
|
407
|
+
const rec = c.fields.filter(f => inGroup(s, f)).length;
|
|
408
|
+
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
409
|
}
|
|
260
410
|
// A random function of a natural: a table of small values.
|
|
261
411
|
export const randomFn = (rand, natMax = 5) => { const t = new Map(); return n => { if (!t.has(n))
|
|
@@ -292,8 +442,13 @@ export const ctorMap = (d, j) => `${d.name}.${d.ctors[j].name}`;
|
|
|
292
442
|
// Everything a law reads: the datatypes, the functions (with those they call), the function parameters.
|
|
293
443
|
export function programOf(laws, extra = []) {
|
|
294
444
|
const fs = new Map(), ds = new Map(), ps = new Set();
|
|
295
|
-
|
|
296
|
-
|
|
445
|
+
// A datatype with those its fields name (its group, those declared before it).
|
|
446
|
+
const addSort = (s) => { if (s instanceof Data && !ds.has(s.name)) {
|
|
447
|
+
ds.set(s.name, s);
|
|
448
|
+
for (const c of s.ctors)
|
|
449
|
+
for (const f of c.fields)
|
|
450
|
+
addSort(fieldSort(f));
|
|
451
|
+
} };
|
|
297
452
|
const visit = (t) => walk(t, o => {
|
|
298
453
|
if (o.op === 'call' && !fs.has(o.fn)) {
|
|
299
454
|
const f = fnOf(o.fn);
|
|
@@ -311,7 +466,8 @@ export function programOf(laws, extra = []) {
|
|
|
311
466
|
}
|
|
312
467
|
for (const f of extra)
|
|
313
468
|
visit({ op: 'call', fn: f.name, args: [] });
|
|
314
|
-
|
|
469
|
+
// In order of declaration (each after those its fields name; a group together).
|
|
470
|
+
const datas = [...ds.values()].sort((a, b) => a.seq - b.seq);
|
|
315
471
|
// Functions in definition order (each after those it calls).
|
|
316
472
|
const order = [], seen = new Set();
|
|
317
473
|
const place = (f) => { if (seen.has(f.name))
|
|
@@ -319,6 +475,9 @@ export function programOf(laws, extra = []) {
|
|
|
319
475
|
place(fnOf(o.fn)); }); order.push(f); };
|
|
320
476
|
for (const f of fs.values())
|
|
321
477
|
place(f);
|
|
478
|
+
// (the recursion of each checked)
|
|
479
|
+
for (const f of order)
|
|
480
|
+
void f.decreases;
|
|
322
481
|
return { datas, fns: order, params: [...ps] };
|
|
323
482
|
}
|
|
324
483
|
const dataOf = (p, name) => p.datas.find(d => d.name === name);
|
|
@@ -339,6 +498,11 @@ export function encX(p, x, env) {
|
|
|
339
498
|
const d = dataOf(p, x.data);
|
|
340
499
|
return x.args.length ? get(ctorMap(d, x.ctor), keyOf(x.args.map(a => encX(p, a, env)))) : k(x.ctor);
|
|
341
500
|
}
|
|
501
|
+
case 'natmatch': {
|
|
502
|
+
const of = encX(p, x.of, env), e2 = new Map(env);
|
|
503
|
+
e2.set(x.pred, { op: 'sub', a: of, b: k(1) });
|
|
504
|
+
return { op: 'if', c: eqC(of, k(0)), then: encX(p, x.zero, env), else: encX(p, x.succ, e2) };
|
|
505
|
+
}
|
|
342
506
|
case 'match': {
|
|
343
507
|
const d = dataOf(p, x.data), of = encX(p, x.of, env);
|
|
344
508
|
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); });
|
|
@@ -366,7 +530,7 @@ export function factsOf(p) {
|
|
|
366
530
|
const tag = get(tagMap(d.name), x), size = (t) => get(sizeMap(d.name), t);
|
|
367
531
|
// Under tag j: the size, and every field of another constructor 0 (what Lean's model reads there).
|
|
368
532
|
const under = (c, j) => {
|
|
369
|
-
const sz = c.fields.flatMap((f, i) => (f
|
|
533
|
+
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
534
|
const others = d.ctors.flatMap((c2, j2) => (j2 === j ? [] : c2.fields.map((_, i2) => eqC(get(fieldMap(d, j2, i2), x), k(0)))));
|
|
371
535
|
return andC(eqC(size(x), sz), ...others);
|
|
372
536
|
};
|
|
@@ -420,14 +584,14 @@ export function decode(d, n) {
|
|
|
420
584
|
if (n < z)
|
|
421
585
|
return { c: n, f: [] };
|
|
422
586
|
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(
|
|
587
|
+
return { c: j, f: c.fields.map((f, i) => { const x = tup(q, c.fields.length, i); return f.sort === 'nat' ? x : decode(dataNamed(f.sort.data), x); }) };
|
|
424
588
|
}
|
|
425
589
|
export function encode(d, v) {
|
|
426
590
|
const o = v, z = d.nullary, m = d.ctors.length - z;
|
|
427
591
|
if (o.c < z)
|
|
428
592
|
return o.c;
|
|
429
593
|
const c = d.ctors[o.c];
|
|
430
|
-
return safe(z + m * tupk(c.fields.map((f, i) => (f.sort === 'nat' ? o.f[i] : encode(
|
|
594
|
+
return safe(z + m * tupk(c.fields.map((f, i) => (f.sort === 'nat' ? o.f[i] : encode(dataNamed(f.sort.data), o.f[i])))) + (o.c - z));
|
|
431
595
|
}
|
|
432
596
|
// Each map of the encoding as a function of its arguments, on the codes (for core/cycles.ts's runs).
|
|
433
597
|
export function codeValues(p) {
|
|
@@ -436,9 +600,9 @@ export function codeValues(p) {
|
|
|
436
600
|
out[tagMap(d.name)] = n => decode(d, n).c;
|
|
437
601
|
out[sizeMap(d.name)] = n => sizeOf(d, decode(d, n));
|
|
438
602
|
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(
|
|
603
|
+
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(dataNamed(f.sort.data), v.f[i])) : 0; }; });
|
|
440
604
|
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(
|
|
605
|
+
out[ctorMap(d, j)] = (...xs) => encode(d, { c: j, f: c.fields.map((f, i) => (f.sort === 'nat' ? xs[i] : decode(dataNamed(f.sort.data), xs[i]))) });
|
|
442
606
|
});
|
|
443
607
|
}
|
|
444
608
|
for (const f of p.fns)
|
|
@@ -449,7 +613,7 @@ export function codeValues(p) {
|
|
|
449
613
|
return out;
|
|
450
614
|
}
|
|
451
615
|
// 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
|
|
616
|
+
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 !== 'nat' && 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
617
|
// --- concrete models: a value of the encoding at random values (which terms are equal, for congruences and matches) ----
|
|
454
618
|
// Values interned per datatype: a constructor of no field is its tag; the rest numbered from there. A key is a
|
|
455
619
|
// pairing's natural, read back through its square root.
|
|
@@ -513,11 +677,11 @@ export class Model {
|
|
|
513
677
|
for (const [i, f] of c.fields.entries())
|
|
514
678
|
if (map === fieldMap(d, j, i)) {
|
|
515
679
|
const v = this.value(d, key);
|
|
516
|
-
return v.c === j ? this.code(f
|
|
680
|
+
return v.c === j ? this.code(fieldSort(f), v.f[i]) : 0;
|
|
517
681
|
}
|
|
518
682
|
if (c.fields.length && map === ctorMap(d, j)) {
|
|
519
683
|
const as = unkey(key, c.fields.length);
|
|
520
|
-
return this.id(d, { c: j, f: c.fields.map((f, i) => this.decode(f
|
|
684
|
+
return this.id(d, { c: j, f: c.fields.map((f, i) => this.decode(fieldSort(f), as[i])) });
|
|
521
685
|
}
|
|
522
686
|
}
|
|
523
687
|
}
|
|
@@ -576,7 +740,7 @@ export class Model {
|
|
|
576
740
|
}
|
|
577
741
|
}
|
|
578
742
|
}
|
|
579
|
-
export const sizeOf = (d, v) => { const o = v; return 1 + d.ctors[o.c].fields.reduce((s, f, i) => s + (f
|
|
743
|
+
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
744
|
const isqrt = (n) => { let s = Math.floor(Math.sqrt(n)); while (s * s > n)
|
|
581
745
|
s--; while ((s + 1) * (s + 1) <= n)
|
|
582
746
|
s++; return s; };
|
|
@@ -601,6 +765,126 @@ export function refute(l, n = 400, seed = 7) {
|
|
|
601
765
|
}
|
|
602
766
|
return undefined;
|
|
603
767
|
}
|
|
768
|
+
// The function a function f calls at its decreasing argument's field of datatype `to` (a rose tree's sum, at its forest:
|
|
769
|
+
// the forest's sum): the call, the same for every such field, its other arguments f's own parameters or closed terms.
|
|
770
|
+
function partner(f, to) {
|
|
771
|
+
const di = f.decreases;
|
|
772
|
+
if (di === undefined)
|
|
773
|
+
return undefined;
|
|
774
|
+
const own = `${f.name}.${f.params[di].name}`, found = new Map();
|
|
775
|
+
let bad = false;
|
|
776
|
+
const go = (x, fields) => {
|
|
777
|
+
if (!x || typeof x !== 'object')
|
|
778
|
+
return;
|
|
779
|
+
if (Array.isArray(x)) {
|
|
780
|
+
x.forEach(y => go(y, fields));
|
|
781
|
+
return;
|
|
782
|
+
}
|
|
783
|
+
const t = x;
|
|
784
|
+
if (t.op === 'match' && t.of.op === 'var' && t.of.name === own) {
|
|
785
|
+
const d = dataNamed(t.data);
|
|
786
|
+
t.cases.forEach((c, j) => go(c.body, new Set([...fields, ...c.binds.filter((_, i) => { const fl = d.ctors[j].fields[i]; return fl.sort !== 'nat' && fl.sort.data === to.name; })])));
|
|
787
|
+
return;
|
|
788
|
+
}
|
|
789
|
+
if (t.op === 'call') {
|
|
790
|
+
const g = fnOf(t.fn), gd = g.decreases, at = gd === undefined ? undefined : t.args[gd];
|
|
791
|
+
if (at?.op === 'var' && fields.has(at.name)) {
|
|
792
|
+
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));
|
|
793
|
+
if (args.some(a => a === undefined))
|
|
794
|
+
bad = true;
|
|
795
|
+
else
|
|
796
|
+
found.set(JSON.stringify([g.name, args]), { fn: g, args: args });
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
for (const v of Object.values(t))
|
|
800
|
+
go(v, fields);
|
|
801
|
+
};
|
|
802
|
+
go(f.body(), new Set());
|
|
803
|
+
return !bad && found.size === 1 ? [...found.values()][0] : undefined;
|
|
804
|
+
}
|
|
805
|
+
// The law said of a value of datatype `to` in place of v: v a variable of `to`, and each call whose decreasing argument
|
|
806
|
+
// is then of `to` (no longer of its own datatype) its function's partner at `to`; none where a sort no longer fits (an
|
|
807
|
+
// argument, a comparison).
|
|
808
|
+
function companionOf(l, v, to, name) {
|
|
809
|
+
// (a sort unknown, an if's or a match's, fits any)
|
|
810
|
+
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);
|
|
811
|
+
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');
|
|
812
|
+
let ok = true;
|
|
813
|
+
const lift = (t) => {
|
|
814
|
+
if (!ok)
|
|
815
|
+
return t;
|
|
816
|
+
if (Array.isArray(t))
|
|
817
|
+
return t.map(lift);
|
|
818
|
+
if (!t || typeof t !== 'object')
|
|
819
|
+
return t;
|
|
820
|
+
const o = t;
|
|
821
|
+
if (o.op === 'var' && o.name === v)
|
|
822
|
+
return { op: 'var', name };
|
|
823
|
+
if (o.op === 'call') {
|
|
824
|
+
const f = fnOf(o.fn), di = f.decreases, as = o.args.map(a => lift(a));
|
|
825
|
+
if (di !== undefined && !same(sortOf(as[di], true), sortOf(o.args[di], false))) {
|
|
826
|
+
const pt = partner(f, to);
|
|
827
|
+
if (!pt || !same(sortOf(as[di], true), to)) {
|
|
828
|
+
ok = false;
|
|
829
|
+
return o;
|
|
830
|
+
}
|
|
831
|
+
const args = pt.args.map(a => (a === -1 ? as[di] : typeof a === 'number' ? as[a] : a));
|
|
832
|
+
if (args.some((a, i) => i !== pt.fn.decreases && !same(sortOf(a, true), pt.fn.params[i].sort))) {
|
|
833
|
+
ok = false;
|
|
834
|
+
return o;
|
|
835
|
+
}
|
|
836
|
+
return { op: 'call', fn: pt.fn.name, args };
|
|
837
|
+
}
|
|
838
|
+
if (as.some((a, i) => !same(sortOf(a, true), f.params[i].sort))) {
|
|
839
|
+
ok = false;
|
|
840
|
+
return o;
|
|
841
|
+
}
|
|
842
|
+
return { ...o, args: as };
|
|
843
|
+
}
|
|
844
|
+
if (o.op === 'ctor') {
|
|
845
|
+
const d = dataNamed(o.data), as = o.args.map(a => lift(a));
|
|
846
|
+
if (as.some((a, i) => !same(sortOf(a, true), fieldSort(d.ctors[o.ctor].fields[i]))))
|
|
847
|
+
ok = false;
|
|
848
|
+
return { ...o, args: as };
|
|
849
|
+
}
|
|
850
|
+
const out = Object.fromEntries(Object.entries(o).map(([key, x]) => [key, lift(x)]));
|
|
851
|
+
if (out.op === '=' && !same(sortOf(out.a, true), sortOf(out.b, true)))
|
|
852
|
+
ok = false;
|
|
853
|
+
const om = o;
|
|
854
|
+
if (om.op === 'match' && !same(sortOf(out.of, true), dataNamed(om.data)))
|
|
855
|
+
ok = false;
|
|
856
|
+
return out;
|
|
857
|
+
};
|
|
858
|
+
const holds = lift(l.holds);
|
|
859
|
+
if (!ok || !JSON.stringify(holds).includes(JSON.stringify({ op: 'var', name })))
|
|
860
|
+
return undefined;
|
|
861
|
+
return { name: `${l.name}, at ${to.name}`, vars: l.vars.map(x => (x.name === v ? { name, sort: to } : x)), holds, params: l.params };
|
|
862
|
+
}
|
|
863
|
+
// The mutual scheme on v: the companions of the datatypes v's fields reach (through the group), or none.
|
|
864
|
+
function mutualScheme(l, v, d) {
|
|
865
|
+
const at = new Map([[d.name, { law: l, v }]]), todo = [d];
|
|
866
|
+
while (todo.length) {
|
|
867
|
+
const x = todo.shift(), from = at.get(x.name);
|
|
868
|
+
for (const c of x.ctors)
|
|
869
|
+
for (const f of c.fields) {
|
|
870
|
+
if (!inGroup(x, f) || at.has(f.sort.data))
|
|
871
|
+
continue;
|
|
872
|
+
const to = dataNamed(f.sort.data), name = `${v}${to.name}`, law = companionOf(from.law, from.v, to, name);
|
|
873
|
+
if (!law)
|
|
874
|
+
return undefined;
|
|
875
|
+
at.set(to.name, { law, v: name });
|
|
876
|
+
todo.push(to);
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
if (at.size < 2)
|
|
880
|
+
return undefined;
|
|
881
|
+
const order = [...at.keys()];
|
|
882
|
+
const usesOf = (x, xv) => x.ctors.flatMap((c, j) => c.fields.flatMap((f, i) => (inGroup(x, f) && at.has(f.sort.data)
|
|
883
|
+
? [{ 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) }] : [])));
|
|
884
|
+
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) }; }) };
|
|
885
|
+
}
|
|
886
|
+
// The inductions the search tries for a law (its schemes), for tests.
|
|
887
|
+
export const schemesOf = (p, l) => schemes(p, l);
|
|
604
888
|
function schemes(p, l) {
|
|
605
889
|
const out = [], seen = new Set();
|
|
606
890
|
const add = (s) => { const id = JSON.stringify(s.uses); if (!seen.has(id)) {
|
|
@@ -636,6 +920,14 @@ function schemes(p, l) {
|
|
|
636
920
|
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
921
|
return;
|
|
638
922
|
}
|
|
923
|
+
if (t.op === 'natmatch') {
|
|
924
|
+
go(t.of, env, guard);
|
|
925
|
+
const of = encX(p, t.of, env), e2 = new Map(env);
|
|
926
|
+
go(t.zero, env, [...guard, eqC(of, k(0))]);
|
|
927
|
+
e2.set(t.pred, { op: 'sub', a: of, b: k(1) });
|
|
928
|
+
go(t.succ, e2, [...guard, { op: 'not', x: eqC(of, k(0)) }]);
|
|
929
|
+
return;
|
|
930
|
+
}
|
|
639
931
|
if (t.op === 'call' && t.fn === f.name) {
|
|
640
932
|
const subst = {};
|
|
641
933
|
let ok = true;
|
|
@@ -660,18 +952,23 @@ function schemes(p, l) {
|
|
|
660
952
|
if (uses.length)
|
|
661
953
|
add({ name: `recursion of ${f.name} on ${at.name}`, on: { v: at.name, d }, uses });
|
|
662
954
|
});
|
|
663
|
-
// Structural induction on each variable of a datatype, the others fixed
|
|
955
|
+
// Structural induction on each variable of a datatype, the others fixed; mutual where its fields reach another of its
|
|
956
|
+
// group (the law and its companions), plain where they reach none or no companion is found.
|
|
664
957
|
for (const v of l.vars) {
|
|
665
958
|
if (!(v.sort instanceof Data))
|
|
666
959
|
continue;
|
|
667
|
-
const d = v.sort, x = ev(v.name);
|
|
668
|
-
|
|
960
|
+
const d = v.sort, x = ev(v.name), mut = mutualScheme(l, v.name, d);
|
|
961
|
+
if (mut) {
|
|
962
|
+
out.push(mut);
|
|
963
|
+
continue;
|
|
964
|
+
}
|
|
965
|
+
add({ name: `structure of ${v.name}`, on: { v: v.name, d }, uses: d.ctors.flatMap((c, j) => c.fields.flatMap((f, i) => (f.sort !== 'nat' && f.sort.data === d.name ? [{ subst: { [v.name]: get(fieldMap(d, j, i), x) }, guard: eqC(get(tagMap(d.name), x), k(j)) }] : []))) });
|
|
669
966
|
}
|
|
670
967
|
return out;
|
|
671
968
|
}
|
|
672
969
|
function instancesOf(p, fs, w) {
|
|
673
970
|
// Past the attempt's time, what is found so far (the attempt then fails or proves with it).
|
|
674
|
-
const late = () => w.until !== undefined &&
|
|
971
|
+
const late = () => w.until !== undefined && clock() > w.until;
|
|
675
972
|
const tt = performance.now(), lap = (what) => { if (dataTrace)
|
|
676
973
|
console.error(`[data] instances: ${what} at ${Math.round(performance.now() - tt)} ms`); };
|
|
677
974
|
const insts = [], seen = new Set();
|
|
@@ -841,7 +1138,10 @@ function instancesOf(p, fs, w) {
|
|
|
841
1138
|
const as = argsOf(r.key, f.params.length);
|
|
842
1139
|
if (!as)
|
|
843
1140
|
continue;
|
|
844
|
-
const
|
|
1141
|
+
const d0 = f.params[f.decreases].sort;
|
|
1142
|
+
if (!(d0 instanceof Data))
|
|
1143
|
+
continue;
|
|
1144
|
+
const own = as[f.decreases], d = d0, root = cl.find(tid(own));
|
|
845
1145
|
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);
|
|
846
1146
|
const cands = built(own) ? [own] : [...cl.terms.values()].filter(t => built(t) && cl.find(tid(t)) === root);
|
|
847
1147
|
for (const t of cands) {
|
|
@@ -877,8 +1177,8 @@ function instancesOf(p, fs, w) {
|
|
|
877
1177
|
if (c.fields.length && t.map === ctorMap(d, j))
|
|
878
1178
|
return d;
|
|
879
1179
|
for (const [i, fl] of c.fields.entries())
|
|
880
|
-
if (fl.sort
|
|
881
|
-
return
|
|
1180
|
+
if (fl.sort !== 'nat' && t.map === fieldMap(d, j, i))
|
|
1181
|
+
return dataNamed(fl.sort.data);
|
|
882
1182
|
}
|
|
883
1183
|
}
|
|
884
1184
|
return undefined;
|
|
@@ -1163,11 +1463,10 @@ const dataTrace = !!globalThis.process?.env?.DATA_TRACE;
|
|
|
1163
1463
|
// Debugging: every congruence a model's closure makes, not only those explanations use.
|
|
1164
1464
|
const allCong = !!globalThis.process?.env?.DATA_ALLCONG;
|
|
1165
1465
|
function attempt(p, fs, l, scheme, depth, lemmas, level, pool) {
|
|
1166
|
-
const t0 =
|
|
1466
|
+
const t0 = clock();
|
|
1167
1467
|
anchoredAt.clear();
|
|
1168
1468
|
// 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());
|
|
1469
|
+
const few = (ms) => ms.slice(0, 16);
|
|
1171
1470
|
const specs = [], where = new Map();
|
|
1172
1471
|
for (const lm of lemmas) {
|
|
1173
1472
|
const local = new Map();
|
|
@@ -1181,31 +1480,39 @@ function attempt(p, fs, l, scheme, depth, lemmas, level, pool) {
|
|
|
1181
1480
|
specs.push({ ...n, uses: n.uses.map(u => ({ ...u, node: local.get(u.node) ?? u.node })) });
|
|
1182
1481
|
});
|
|
1183
1482
|
}
|
|
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
|
-
|
|
1483
|
+
// The scheme's nodes: the law's, then each companion's (a mutual induction), each its own models. A use of member i
|
|
1484
|
+
// reads node -1 - i until the nodes are placed.
|
|
1485
|
+
const members = [{ law: l, on: scheme?.on, uses: scheme?.uses ?? [], pool }, ...(scheme?.companions ?? []).map(c => ({ ...c, pool: modelsFor(p, c.law, pool.length, 5) }))];
|
|
1486
|
+
const concls = members.map(mb => encY(p, mb.law.holds, new Map()));
|
|
1487
|
+
const base = specs.length, title = `${l.name}${scheme ? ` (${scheme.name})` : ''}`;
|
|
1488
|
+
varSorts = new Map(members.flatMap(mb => mb.law.vars.flatMap(v => (v.sort instanceof Data ? [[v.name, v.sort]] : []))));
|
|
1489
|
+
const lemmaNodes = lemmas.map(lm => ({ law: lm.law, node: where.get(lm.spec.name), concl: specs[where.get(lm.spec.name)].concl }));
|
|
1490
|
+
const useConcl = (u) => substC(u.node < 0 ? concls[-1 - u.node] : specs[u.node].concl, { vars: u.subst, maps: {}, flags: {} });
|
|
1491
|
+
const plans = [];
|
|
1492
|
+
for (const [mi, mb] of members.entries()) {
|
|
1493
|
+
const models = mb.pool.slice(0, 24), concl = concls[mi], name = mi === 0 ? title : `${title}, at ${mb.on.d.name}`;
|
|
1494
|
+
const ih = mb.uses.map(u => ({ node: -1 - (u.member ?? mi), subst: u.subst, guard: u.guard }));
|
|
1495
|
+
const shapesAt = mb.on && mb.on.d !== 'nat' ? [ev(mb.on.v)] : [];
|
|
1496
|
+
const ihConcls = ih.map(useConcl);
|
|
1497
|
+
// Uses of the lemmas, where a lemma's largest read matches a read of this goal (unfolded), its key up to the
|
|
1498
|
+
// equalities the unfolded definitions give (any branch); twice over, the uses' own conclusions read the second time.
|
|
1499
|
+
const pre = instancesOf(p, fs, { roots: [concl], others: ihConcls, depth, shapesAt, etaAt: [], models: [], until: t0 + attemptMs.ms });
|
|
1500
|
+
const lemmaUses = lemmaUsesFor(p, fs, lemmaNodes, [concl, ihConcls], pre, models);
|
|
1501
|
+
const uses = [...ih, ...lemmaUses];
|
|
1502
|
+
if (dataTrace)
|
|
1503
|
+
for (const u of lemmaUses)
|
|
1504
|
+
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('; ')}`);
|
|
1505
|
+
// The lemmas' conclusions are unfolded like the law's (they are what the goal reads, said another way); the cycle's
|
|
1506
|
+
// own uses only from depth 3.
|
|
1507
|
+
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 });
|
|
1508
|
+
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) : [];
|
|
1509
|
+
if (cases.length <= 1) {
|
|
1510
|
+
// One node: four uses of a lemma, twelve in all, the best first (as lemmaUsesFor ranks them).
|
|
1511
|
+
const one = capUses(uses, 12);
|
|
1512
|
+
plans.push([{ name, hyps: [], concl, sub: 0, insts: instsFor(models, one), uses: one }]);
|
|
1513
|
+
continue;
|
|
1514
|
+
}
|
|
1515
|
+
const plan = [{ name, hyps: [], concl, sub: 1, insts: [], uses: [], cases: cases.map(c => andC(yesC, ...c.lits)) }];
|
|
1209
1516
|
for (const [i, c] of cases.entries()) {
|
|
1210
1517
|
// The uses whose guard some model of the case meets (all, for a case no model reaches).
|
|
1211
1518
|
const holdsIn = (g) => !c.models || c.models.some(({ m, env }) => { try {
|
|
@@ -1221,26 +1528,40 @@ function attempt(p, fs, l, scheme, depth, lemmas, level, pool) {
|
|
|
1221
1528
|
catch {
|
|
1222
1529
|
return false;
|
|
1223
1530
|
} }); };
|
|
1224
|
-
const us = uses.filter(u => holdsIn(u.guard) && applies(u));
|
|
1531
|
+
const us = capUses(uses.filter(u => holdsIn(u.guard) && applies(u)));
|
|
1225
1532
|
const ms = few(c.models ?? c.near ?? models);
|
|
1226
|
-
|
|
1533
|
+
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
1534
|
}
|
|
1535
|
+
plans.push(plan);
|
|
1536
|
+
}
|
|
1537
|
+
// Placed: each member's node, then its cases; a main node with cases uses them, at rank (level, size, 1) above theirs.
|
|
1538
|
+
const offsets = plans.map((_, i) => base + plans.slice(0, i).reduce((n, q) => n + q.length, 0));
|
|
1539
|
+
for (const [mi, plan] of plans.entries()) {
|
|
1540
|
+
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));
|
|
1541
|
+
for (const { sub, cases, ...n } of plan)
|
|
1542
|
+
specs.push({ ...n, rank: [k(level), size, 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
1543
|
}
|
|
1544
|
+
const name = title;
|
|
1229
1545
|
// Each attempt within its own time (a goal past it is left unknown, and the attempt fails).
|
|
1230
1546
|
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 };
|
|
1547
|
+
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`);
|
|
1548
|
+
if (clock() > t0 + attemptMs.ms)
|
|
1549
|
+
return { verdict: { proven: false, checked: false, node: name, goal: name, stuck: 'unknown' }, specs, main: base };
|
|
1234
1550
|
const was = pairedKeys.skip;
|
|
1235
1551
|
pairedKeys.skip = true;
|
|
1236
1552
|
try {
|
|
1237
|
-
return { verdict: withDeadline(t0 + attemptMs.ms, () => proveCyclic(fs.facts, specs)), specs };
|
|
1553
|
+
return { verdict: withDeadline(t0 + attemptMs.ms, () => proveCyclic(fs.facts, specs)), specs, main: base };
|
|
1238
1554
|
}
|
|
1239
1555
|
finally {
|
|
1240
1556
|
pairedKeys.skip = was;
|
|
1241
1557
|
}
|
|
1242
1558
|
}
|
|
1243
1559
|
export const attemptMs = { ms: 8000 };
|
|
1560
|
+
// A datatype search's budgets count work, not wall time (kernel/prove.ts's `counted`: the solver's rows and each node's),
|
|
1561
|
+
// 300 units a millisecond: a search finds the same proof on any machine, at any load. On Modal (2 vCPU) the corpus's
|
|
1562
|
+
// searches do 50 to 263 units a millisecond (`runs/modal/cover/data3`, data-cal), so a budget lets each do at least the
|
|
1563
|
+
// work it did there in wall time.
|
|
1564
|
+
const dataRate = 300;
|
|
1244
1565
|
// A case node's instances, those its models reach: from every read of its conclusion and its uses' (and their guards),
|
|
1245
1566
|
// 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
1567
|
// value whose tag is read), the reads of what it gives where the models take it (an if's taken branch, an implication's
|
|
@@ -1386,6 +1707,7 @@ function lemmaUsesFor(p, fs, lemmas, from, insts, models = []) {
|
|
|
1386
1707
|
reads.push(o); });
|
|
1387
1708
|
for (let round = 0; round < 2; round++) {
|
|
1388
1709
|
const fresh = [];
|
|
1710
|
+
const cands = [];
|
|
1389
1711
|
// The newest lemma first (the one the law is tried again for).
|
|
1390
1712
|
for (const lm of [...lemmas].reverse()) {
|
|
1391
1713
|
if (round > 0 && out.some(u => u.node === lm.node))
|
|
@@ -1410,28 +1732,70 @@ function lemmaUsesFor(p, fs, lemmas, from, insts, models = []) {
|
|
|
1410
1732
|
continue;
|
|
1411
1733
|
// Where a model gives the lemma's arguments the goal read's (a match through every branch's classes may be none).
|
|
1412
1734
|
const there = pats.map(a => substE(a, { vars: sub, maps: {}, flags: {} }));
|
|
1413
|
-
|
|
1735
|
+
const score = models.length ? models.filter(({ m, env }) => { try {
|
|
1414
1736
|
return there.every((a, q) => m.evalE(a, env) === m.evalE(args[q], env));
|
|
1415
1737
|
}
|
|
1416
1738
|
catch {
|
|
1417
1739
|
return false;
|
|
1418
|
-
} })
|
|
1740
|
+
} }).length : 1;
|
|
1741
|
+
if (!score)
|
|
1419
1742
|
continue;
|
|
1420
1743
|
const k2 = JSON.stringify([lm.node, sub]);
|
|
1421
|
-
if (seen.has(k2)
|
|
1744
|
+
if (seen.has(k2))
|
|
1422
1745
|
continue;
|
|
1423
1746
|
seen.add(k2);
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1747
|
+
const names = Object.keys(sub).sort();
|
|
1748
|
+
const fp = models.length ? JSON.stringify([lm.node, models.map(({ m, env }) => names.map(x => { try {
|
|
1749
|
+
return m.evalE(sub[x], env);
|
|
1750
|
+
}
|
|
1751
|
+
catch {
|
|
1752
|
+
return -1;
|
|
1753
|
+
} }))]) : k2;
|
|
1754
|
+
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
1755
|
}
|
|
1429
1756
|
}
|
|
1430
1757
|
}
|
|
1758
|
+
// First, as found: four of a lemma, twelve in all (the next round reads their conclusions). Then the rest, best first
|
|
1759
|
+
// (the most models, then the smallest terms), one of each set of values, up to `perLemma` of a lemma and `total` in
|
|
1760
|
+
// all: a case node keeps those its models meet, `perCase` of a lemma (attempt's `applies`), so a use that holds in
|
|
1761
|
+
// one case only (the insertion's other side) isn't crowded out by those found first.
|
|
1762
|
+
const fps = new Set(), first = new Set();
|
|
1763
|
+
const take = (c) => { fps.add(c.fp); out.push(c.use); matchedAt.set(c.use, c.at); };
|
|
1764
|
+
for (const c of cands) {
|
|
1765
|
+
if (out.length >= 12 || out.filter(u => u.node === c.use.node).length >= 4)
|
|
1766
|
+
continue;
|
|
1767
|
+
take(c);
|
|
1768
|
+
first.add(c);
|
|
1769
|
+
walk(substC(lemmas.find(lm => lm.node === c.use.node).concl, { vars: c.use.subst, maps: {}, flags: {} }), o => { if (o.op === 'get')
|
|
1770
|
+
fresh.push(o); });
|
|
1771
|
+
}
|
|
1772
|
+
const rest = cands.filter(c => !first.has(c)).sort((a, b) => b.score - a.score || a.size - b.size);
|
|
1773
|
+
for (const c of rest)
|
|
1774
|
+
if (!fps.has(c.fp) && out.length < lemmaCaps.total && out.filter(u => u.node === c.use.node).length < lemmaCaps.perLemma)
|
|
1775
|
+
take(c);
|
|
1431
1776
|
reads = fresh;
|
|
1432
1777
|
}
|
|
1433
1778
|
return out;
|
|
1434
1779
|
}
|
|
1780
|
+
// How many uses of lemmas a goal gets: the candidates kept (`perLemma` of each lemma, `total`), and of those a case node
|
|
1781
|
+
// keeps (`perCase` of each lemma, those its models meet).
|
|
1782
|
+
export const lemmaCaps = { perLemma: 8, total: 24, perCase: 4 };
|
|
1783
|
+
// A node's uses: the scheme's (the cycle's, read at nodes not yet placed) all, `perCase` of each lemma, `total` lemma
|
|
1784
|
+
// uses in all, in the order given.
|
|
1785
|
+
function capUses(uses, total = Infinity) {
|
|
1786
|
+
const per = new Map();
|
|
1787
|
+
let all = 0;
|
|
1788
|
+
return uses.filter(u => {
|
|
1789
|
+
if (u.node < 0)
|
|
1790
|
+
return true;
|
|
1791
|
+
const n = per.get(u.node) ?? 0;
|
|
1792
|
+
if (n >= lemmaCaps.perCase || all >= total)
|
|
1793
|
+
return false;
|
|
1794
|
+
per.set(u.node, n + 1);
|
|
1795
|
+
all++;
|
|
1796
|
+
return true;
|
|
1797
|
+
});
|
|
1798
|
+
}
|
|
1435
1799
|
// The equalities a condition may give: those in a positive place (a conjunction's parts, a disjunction's disjuncts that
|
|
1436
1800
|
// aren't negations, so an implication's conclusion and never its premise).
|
|
1437
1801
|
function equalities(c) {
|
|
@@ -1459,13 +1823,13 @@ function proveWith(p, fs, l, lemmas, level, opts, until, per, depths) {
|
|
|
1459
1823
|
const ss = opts.induction === false ? [undefined] : schemes(p, l);
|
|
1460
1824
|
for (const depth of depths.filter(d => d <= (opts.depth ?? 3)))
|
|
1461
1825
|
for (const s of ss) {
|
|
1462
|
-
if (
|
|
1826
|
+
if (clock() > until)
|
|
1463
1827
|
return { tried, last };
|
|
1464
1828
|
tried++;
|
|
1465
|
-
const { verdict, specs } = (attemptMs.ms = Math.min(per * (opts.patience ?? 1), until -
|
|
1829
|
+
const { verdict, specs, main } = (attemptMs.ms = Math.min(per * (opts.patience ?? 1), until - clock()), attempt(p, fs, l, s, depth, lemmas, level, models));
|
|
1466
1830
|
last = verdict;
|
|
1467
1831
|
if (verdict.proven && verdict.checked)
|
|
1468
|
-
return { out: { law: l, spec: specs
|
|
1832
|
+
return { out: { law: l, spec: specs[main], nodes: specs }, verdict, scheme: s?.name, tried };
|
|
1469
1833
|
}
|
|
1470
1834
|
return { tried, last };
|
|
1471
1835
|
}
|
|
@@ -1527,7 +1891,7 @@ export function conjectures(p, l, max = 24, maxSize = 6) {
|
|
|
1527
1891
|
addT({ x: { op: 'call', fn: f.name, args: args.map(a => a.x) }, sort: f.ret, size });
|
|
1528
1892
|
for (const d of p.datas)
|
|
1529
1893
|
d.ctors.forEach((c, j) => { if (c.fields.length)
|
|
1530
|
-
for (const args of build(c.fields.map(f => (f
|
|
1894
|
+
for (const args of build(c.fields.map(f => fieldSort(f)), size - 1))
|
|
1531
1895
|
addT({ x: { op: 'ctor', data: d.name, ctor: j, args: args.map(a => a.x) }, sort: d, size }); });
|
|
1532
1896
|
for (const [a2, b2] of build(['nat', 'nat'], size - 1))
|
|
1533
1897
|
if (JSON.stringify(a2.x) <= JSON.stringify(b2.x))
|
|
@@ -1754,6 +2118,7 @@ export const showX = (x) => {
|
|
|
1754
2118
|
return `${d}.${x.ctor}${x.args.length ? `(${x.args.map(showX).join(', ')})` : ''}`;
|
|
1755
2119
|
}
|
|
1756
2120
|
case 'match': return `match ${showX(x.of)}`;
|
|
2121
|
+
case 'natmatch': return `match ${showX(x.of)}`;
|
|
1757
2122
|
}
|
|
1758
2123
|
};
|
|
1759
2124
|
export const showY = (y) => {
|
|
@@ -1771,10 +2136,15 @@ export const showY = (y) => {
|
|
|
1771
2136
|
// Prove a law over datatypes: refuted by a random value where one breaks it; else proven by a cycle the search finds,
|
|
1772
2137
|
// with lemmas found from the values when the law alone isn't (`lemmas: false` turns that off; an array gives lemmas to
|
|
1773
2138
|
// prove and use instead, which the corpus never does).
|
|
1774
|
-
export function proveData(l, opts = {}) {
|
|
1775
|
-
|
|
2139
|
+
export function proveData(l, opts = {}) { return counted(dataRate, () => proveDataCounted(l, opts)); }
|
|
2140
|
+
function proveDataCounted(l, opts) {
|
|
2141
|
+
const t0 = performance.now(), w0 = work.n, until = clock() + (opts.ms ?? 120_000);
|
|
1776
2142
|
const p = programOf([l, ...(Array.isArray(opts.lemmas) ? opts.lemmas : [])]), fs = factsOf(p);
|
|
1777
|
-
const done = (v) =>
|
|
2143
|
+
const done = (v) => {
|
|
2144
|
+
if (dataTrace)
|
|
2145
|
+
console.error(`[data] ${l.name}: ${work.n - w0} units of work in ${Math.round(performance.now() - t0)} ms`);
|
|
2146
|
+
return { law: l.name, ...v, ms: Math.round(performance.now() - t0) };
|
|
2147
|
+
};
|
|
1778
2148
|
const cex = refute(l, opts.samples ?? 400);
|
|
1779
2149
|
if (cex)
|
|
1780
2150
|
return done({ proven: false, checked: false, counterexample: cex, lemmas: [], tried: 0 });
|
|
@@ -1804,11 +2174,15 @@ export function proveData(l, opts = {}) {
|
|
|
1804
2174
|
return ok(proveWith(p, fs, l, [], 1, opts, until, 20000, [2, 3]), []) ?? done({ proven: false, checked: false, lemmas: [], tried, ...stuckOf(first.last) });
|
|
1805
2175
|
const cands = Array.isArray(opts.lemmas) ? opts.lemmas : conjectures(p, l);
|
|
1806
2176
|
const p2 = programOf([l, ...cands]), fs2 = factsOf(p2);
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
2177
|
+
// Each scheme's goals: the law's, and each companion's (a lemma may be what only a companion's goal reads).
|
|
2178
|
+
const goalOf = (law, uses, on, concls) => {
|
|
2179
|
+
const concl = encY(p2, law.holds, new Map()), ihc = uses.map(u => substC(concls[u.member ?? 0] ?? concl, { vars: u.subst, maps: {}, flags: {} }));
|
|
2180
|
+
return { from: [concl, ihc], insts: instancesOf(p2, fs2, { roots: [concl], others: ihc, depth: 2, shapesAt: on && on.d !== 'nat' ? [ev(on.v)] : [], etaAt: [], models: [] }) };
|
|
1810
2181
|
};
|
|
1811
|
-
const goals = schemes(p2, l).
|
|
2182
|
+
const goals = schemes(p2, l).flatMap(s => {
|
|
2183
|
+
const all = [{ law: l, uses: s.uses, on: s.on }, ...(s.companions ?? [])], concls = all.map(m => encY(p2, m.law.holds, new Map()));
|
|
2184
|
+
return all.map((m, i) => goalOf(m.law, m.uses.map(u => ({ ...u, member: u.member ?? i })), m.on, concls));
|
|
2185
|
+
});
|
|
1812
2186
|
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
2187
|
// Those the goal matches first, of those the ones of the law's shape (an implication for an implication) first.
|
|
1814
2188
|
const implication = (y) => y.op === 'or' && y.xs.length === 2 && y.xs[0].op === 'not';
|
|
@@ -1817,11 +2191,11 @@ export function proveData(l, opts = {}) {
|
|
|
1817
2191
|
const proven = [], failed = new Set();
|
|
1818
2192
|
const lemmasAt = (depth) => {
|
|
1819
2193
|
for (const { c, hit } of ranked) {
|
|
1820
|
-
if (
|
|
2194
|
+
if (clock() > until)
|
|
1821
2195
|
return undefined;
|
|
1822
2196
|
if (proven.some(q => q.law === c) || (depth === 2 && !failed.has(c)) || refute(c, 200))
|
|
1823
2197
|
continue;
|
|
1824
|
-
const r = proveWith(p2, fs2, c, proven, 1 + proven.length, opts, Math.min(until,
|
|
2198
|
+
const r = proveWith(p2, fs2, c, proven, 1 + proven.length, opts, Math.min(until, clock() + (depth === 1 ? 8000 : 12000) * (opts.patience ?? 1)), 5000, [depth]);
|
|
1825
2199
|
tried += r.tried;
|
|
1826
2200
|
if (!r.out) {
|
|
1827
2201
|
failed.add(c);
|
|
@@ -1845,7 +2219,7 @@ export function proveData(l, opts = {}) {
|
|
|
1845
2219
|
() => ok(proveWith(p2, fs2, l, proven, 1 + proven.length, opts, until, 12000, [3]), proven, p2),
|
|
1846
2220
|
];
|
|
1847
2221
|
for (const step of steps) {
|
|
1848
|
-
if (
|
|
2222
|
+
if (clock() > until)
|
|
1849
2223
|
break;
|
|
1850
2224
|
const out = step();
|
|
1851
2225
|
if (out)
|