gdp-wired 0.19.0 → 0.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/core/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.sort === 'nat' ? 'nat' : d, { op: 'var', name: binds[i] })]));
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 the datatype itself (`self`). Its
79
- // constructors are ordered as Lean's bijection numbers them: those of no field, then those with no field of the
80
- // datatype, then the rest; a datatype with no constructor of the first two kinds has no value and is refused.
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
- constructor(name, decl) {
101
+ seq = ++declaredSeq;
102
+ constructor(name, decl, group = [name]) {
85
103
  this.name = name;
86
- const all = Object.entries(decl).map(([c, fs]) => ({ name: c, fields: Object.entries(fs).map(([f, s]) => ({ name: f, sort: s === self ? 'self' : 'nat' })) }));
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.sort === 'nat' ? 'nat' : this, args[i])) });
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
- // `data('List', {nil: {}, cons: {head: nat, tail: self}})`: `List.nil`, `List.cons(h, t)`, and `xs.match({...})`.
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 norm = Object.fromEntries(Object.entries(decl).map(([c, fs]) => [c, Object.fromEntries(Object.entries(fs).map(([f, s]) => [f, s === self ? self : 'nat']))]));
107
- const d = new Data(name, norm);
108
- for (const c of d.ctors)
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 parameter every recursive call passes a field of (bound by a match on that parameter), or a refusal.
152
- function decreasing(fn, body) {
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 === 'call' && o.fn === fn.name)
170
- calls.push({ args: o.args, fieldsOf });
171
- if (o.op === 'call' && o.fn !== fn.name && !fns.has(o.fn))
172
- throw new Error(`${fn.name} calls ${o.fn}, which isn't defined`);
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
- if (!calls.length)
178
- return undefined;
179
- for (const [i, p] of fn.params.entries()) {
180
- if (p.sort === 'nat' || p.sort === 'bool')
181
- continue;
182
- const own = `${fn.name}.${p.name}`;
183
- if (calls.every(c => { const a = c.args[i]; return a.op === 'var' && c.fieldsOf.get(a.name) === own; }))
184
- return i;
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
- throw new Error(`${fn.name}: not structural recursion (no parameter whose field every recursive call passes)`);
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.sort === 'nat' ? 'nat' : d, v.f[i])).join(', ')})` : c.name;
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
- const leaves = s.ctors.filter(c => c.fields.every(f => f.sort === 'nat'));
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.sort === 'self').length;
258
- return { c: j, f: c.fields.map(f => randomValue(f.sort === 'nat' ? 'nat' : s, rand, Math.floor((size - 1) / Math.max(1, rec)), natMax)) };
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
- const addSort = (s) => { if (s instanceof Data)
296
- ds.set(s.name, s); };
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
- const datas = [...new Map([...ds.values()].map(d => [d.name, d])).values()];
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.sort === 'self' ? [size(get(fieldMap(d, j, i), x))] : [])).reduce((a, b) => ({ op: 'add', a, b }), k(1));
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(d, x); }) };
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(d, o.f[i])))) + (o.c - z));
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(d, v.f[i])) : 0; }; });
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(d, xs[i]))) });
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 === 'self' ? [implies(eqC(get(tagMap(d.name), ev('x$')), k(j)), { op: '<', a: get(fieldMap(d, j, i), ev('x$')), b: ev('x$') })] : [])))) }));
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.sort === 'nat' ? 'nat' : d, v.f[i]) : 0;
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.sort === 'nat' ? 'nat' : d, as[i])) });
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.sort === 'self' ? sizeOf(d, o.f[i]) : 0), 0); };
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
- add({ name: `structure of ${v.name}`, on: { v: v.name, d }, uses: d.ctors.flatMap((c, j) => c.fields.flatMap((f, i) => (f.sort === 'self' ? [{ subst: { [v.name]: get(fieldMap(d, j, i), x) }, guard: eqC(get(tagMap(d.name), x), k(j)) }] : []))) });
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 && performance.now() > w.until;
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 own = as[f.decreases], d = f.params[f.decreases].sort, root = cl.find(tid(own));
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 === 'self' && t.map === fieldMap(d, j, i))
881
- return d;
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 = performance.now();
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 models = pool.slice(0, 24), few = (ms) => ms.slice(0, 16);
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
- const me = specs.length, name = `${l.name}${scheme ? ` (${scheme.name})` : ''}`;
1185
- varSorts = new Map(l.vars.flatMap(v => (v.sort instanceof Data ? [[v.name, v.sort]] : [])));
1186
- const size = scheme?.on ? get(sizeMap(scheme.on.d.name), ev(scheme.on.v)) : k(0);
1187
- const rank = (sub) => [k(level), size, k(sub)];
1188
- const ih = (scheme?.uses ?? []).map(u => ({ node: me, subst: u.subst, guard: u.guard }));
1189
- const shapesAt = scheme?.on ? [ev(scheme.on.v)] : [];
1190
- const ihConcls = ih.map(u => substC(concl, { vars: u.subst, maps: {}, flags: {} }));
1191
- // Uses of the lemmas, where a lemma's largest read matches a read of this goal (unfolded), its key up to the equalities
1192
- // the unfolded definitions give (any branch); twice over, the uses' own conclusions read the second time.
1193
- const pre = instancesOf(p, fs, { roots: [concl], others: ihConcls, depth, shapesAt, etaAt: [], models: [], until: t0 + attemptMs.ms });
1194
- const lemmaUses = lemmaUsesFor(p, fs, lemmas.map(lm => ({ law: lm.law, node: where.get(lm.spec.name), concl: specs[where.get(lm.spec.name)].concl })), [concl, ihConcls], pre, models);
1195
- const uses = [...ih, ...lemmaUses];
1196
- if (dataTrace)
1197
- for (const u of lemmaUses)
1198
- 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('; ')}`);
1199
- const useConcl = (u) => substC(u.node === me ? concl : specs[u.node].concl, { vars: u.subst, maps: {}, flags: {} });
1200
- // The lemmas' conclusions are unfolded like the law's (they are what the goal reads, said another way); the cycle's
1201
- // own uses only from depth 3.
1202
- const instsFor = (ms, us, every = false) => instancesOf(p, fs, { roots: [concl], others: us.map(useConcl), depth, shapesAt, etaAt: [...shapesAt, ...valuesCompared(p, l)], models: ms, every, until: t0 + attemptMs.ms });
1203
- const cases = scheme ? casesOf(p, pre.filter(q => fs.facts[q.fact].name.startsWith('fn ')).map(q => holdsAt(fs, q)), new Set(l.vars.map(v => v.name)), pool) : [];
1204
- if (cases.length <= 1) {
1205
- specs.push({ name, hyps: [], concl, rank: rank(0), insts: instsFor(models, uses), uses });
1206
- }
1207
- else {
1208
- specs.push({ name, hyps: [], concl, rank: rank(1), insts: [], uses: cases.map((c, i) => ({ node: me + 1 + i, subst: {}, guard: andC(yesC, ...c.lits) })) });
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
- specs.push({ name: `${name}, case ${i + 1}`, hyps: c.lits, concl, rank: rank(0), insts: reached(p, fs, instsFor(ms, us), [c.lits, concl, us.map(u => [u.guard, useConcl(u)]), shapesAt], ms), uses: us });
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(performance.now() - t0)} ms, ${specs.length} nodes, ${specs.reduce((n, x) => n + x.insts.length, 0)} instances`);
1232
- if (performance.now() > t0 + attemptMs.ms)
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
- if (models.length && !models.some(({ m, env }) => { try {
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) || out.length >= 12 || out.filter(u => u.node === lm.node).length >= 4)
1744
+ if (seen.has(k2))
1422
1745
  continue;
1423
1746
  seen.add(k2);
1424
- out.push({ node: lm.node, subst: sub, guard: yesC });
1425
- matchedAt.set(out.at(-1), { goal: t, lemma: substE(top, { vars: sub, maps: {}, flags: {} }) });
1426
- walk(substC(lm.concl, { vars: sub, maps: {}, flags: {} }), o => { if (o.op === 'get')
1427
- fresh.push(o); });
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 (performance.now() > until)
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 - performance.now()), attempt(p, fs, l, s, depth, lemmas, level, models));
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.find(n => n.name === specs[specs.length - 1].name.replace(/, case \d+$/, '')), nodes: specs }, verdict, scheme: s?.name, tried };
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.sort === 'nat' ? 'nat' : d)), size - 1))
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
- const t0 = performance.now(), until = t0 + (opts.ms ?? 120_000);
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) => ({ law: l.name, ...v, ms: Math.round(performance.now() - t0) });
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
- const goalOf = (s) => {
1808
- const concl = encY(p2, l.holds, new Map()), ihc = (s?.uses ?? []).map(u => substC(concl, { vars: u.subst, maps: {}, flags: {} }));
1809
- return { from: [concl, ihc], insts: instancesOf(p2, fs2, { roots: [concl], others: ihc, depth: 2, shapesAt: s?.on ? [ev(s.on.v)] : [], etaAt: [], models: [] }) };
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).map(goalOf);
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 (performance.now() > until)
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, performance.now() + (depth === 1 ? 8000 : 12000) * (opts.patience ?? 1)), 5000, [depth]);
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 (performance.now() > until)
2222
+ if (clock() > until)
1849
2223
  break;
1850
2224
  const out = step();
1851
2225
  if (out)