gdp-wired 0.20.0 → 0.21.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/core/data.js CHANGED
@@ -2,13 +2,21 @@ import { substC, substE } from "../kernel/kernel.js";
2
2
  import { proveCyclic } from "./cyclic.js";
3
3
  import { clock, counted, pairedKeys, withDeadline } from "../kernel/prove.js";
4
4
  import { work } from "./solver.js";
5
+ import { ranksOfRuns } from "./cycles.js";
6
+ import { proveThrough, transferPlan } from "./transfer.js";
5
7
  // --- sorts and the AST ------------------------------------------------------------------------------------------------
6
8
  export const self = { self: true };
7
9
  export const natural = 'nat';
8
10
  export const boolean = 'bool';
11
+ // A signed integer (Lean's Int): opt-in, beside the naturals, whose truncated subtraction keeps its meaning. Encoded as
12
+ // its zigzag code (z ↦ 2z for z ≥ 0, −2z − 1 below), a bijection with the naturals like a datatype's, so a value is one
13
+ // natural and two values are equal exactly when their codes are; its arithmetic reads a code as two naturals, p − n
14
+ // (from the code's quotient and remainder by 2), and writes the result's code back (`encI`, `codeOfPair`).
15
+ export const integer = 'int';
16
+ export const isDataField = (s) => typeof s === 'object';
9
17
  // --- the surface ------------------------------------------------------------------------------------------------------
10
18
  let fresh = 0;
11
- const bindName = (field) => `${field}#${++fresh}`;
19
+ export const bindName = (field) => `${field}#${++fresh}`;
12
20
  export class Nat {
13
21
  x;
14
22
  constructor(x) {
@@ -17,6 +25,11 @@ export class Nat {
17
25
  plus(b) { return new Nat({ op: 'add', a: this.x, b: natX(b) }); }
18
26
  minus(b) { return new Nat({ op: 'sub', a: this.x, b: natX(b) }); }
19
27
  times(k) { return new Nat({ op: 'mul', k, a: this.x }); }
28
+ // A product of two naturals (the kernel multiplies it out; nonlinear: what proves it is products of facts and squares).
29
+ mul(b) { return new Nat({ op: 'prod', a: this.x, b: natX(b) }); }
30
+ modBy(k) { return new Nat({ op: 'mod', k: divisorOf(k), a: this.x }); }
31
+ divBy(k) { return new Nat({ op: 'div', k: divisorOf(k), a: this.x }); }
32
+ toInt() { return new Integer({ op: 'ofnat', a: this.x }); }
20
33
  lt(b) { return new Bool({ op: '<', a: this.x, b: natX(b) }); }
21
34
  atMost(b) { return new Bool({ op: '<=', a: this.x, b: natX(b) }); }
22
35
  gt(b) { return new Bool({ op: '<', a: natX(b), b: this.x }); }
@@ -27,12 +40,41 @@ export class Nat {
27
40
  // oxlint-disable-next-line typescript/no-explicit-any
28
41
  match(cases) {
29
42
  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);
43
+ const int = zero instanceof Integer || succ instanceof Integer;
44
+ const body = (o) => (typeof o === 'number' ? (int ? intX(o) : natX(o)) : o instanceof Bool ? { op: 'bool', c: o.y } : o.x);
31
45
  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));
46
+ return (zero instanceof Bool ? new Bool({ op: 'holds', x }) : zero instanceof Val ? new Val(zero.data, x) : int ? new Integer(x) : new Nat(x));
33
47
  }
34
48
  }
35
49
  const natX = (t) => (typeof t === 'number' ? { op: 'nat', value: t } : t.x);
50
+ const divisorOf = (k) => { if (!Number.isSafeInteger(k) || k < 1)
51
+ throw new RangeError(`a divisor is a whole number at least 1, not ${k}`); return k; };
52
+ // An integer's term; a number is a literal (any sign).
53
+ export class Integer {
54
+ x;
55
+ constructor(x) {
56
+ this.x = x;
57
+ }
58
+ plus(b) { return new Integer({ op: 'iadd', a: this.x, b: intX(b) }); }
59
+ minus(b) { return new Integer({ op: 'isub', a: this.x, b: intX(b) }); }
60
+ negate() { return new Integer({ op: 'ineg', a: this.x }); }
61
+ times(k) { if (!Number.isSafeInteger(k))
62
+ throw new RangeError(`${k} is not an integer`); return new Integer({ op: 'imul', k, a: this.x }); }
63
+ modBy(k) { return new Integer({ op: 'imod', k: divisorOf(k), a: this.x }); }
64
+ divBy(k) { return new Integer({ op: 'idiv', k: divisorOf(k), a: this.x }); }
65
+ lt(b) { return new Bool({ op: 'i<', a: this.x, b: intX(b) }); }
66
+ atMost(b) { return new Bool({ op: 'i<=', a: this.x, b: intX(b) }); }
67
+ gt(b) { return new Bool({ op: 'i<', a: intX(b), b: this.x }); }
68
+ atLeast(b) { return new Bool({ op: 'i<=', a: intX(b), b: this.x }); }
69
+ eq(b) { return new Bool({ op: '=', a: this.x, b: intX(b) }); }
70
+ ne(b) { return this.eq(b).not(); }
71
+ }
72
+ // An integer function's result: a natural literal in a branch (a match's case, an if's arm) is the integer's.
73
+ const intResult = (x) => (x.op === 'nat' ? { op: 'int', value: x.value } : x.op === 'if' ? { ...x, then: intResult(x.then), else: intResult(x.else) }
74
+ : x.op === 'match' ? { ...x, cases: x.cases.map(c => ({ ...c, body: intResult(c.body) })) } : x.op === 'natmatch' ? { ...x, zero: intResult(x.zero), succ: intResult(x.succ) } : x);
75
+ const intX = (t) => { if (typeof t !== 'number')
76
+ return t.x; if (!Number.isSafeInteger(t))
77
+ throw new RangeError(`${t} is not an integer`); return { op: 'int', value: t }; };
36
78
  export class Bool {
37
79
  y;
38
80
  constructor(y) {
@@ -66,23 +108,28 @@ export class Val {
66
108
  return { binds, out: f(fields) };
67
109
  });
68
110
  const first = built[0].out;
69
- const body = (o) => (typeof o === 'number' ? natX(o) : o instanceof Bool ? { op: 'bool', c: o.y } : o.x);
111
+ // A case that is an integer makes every case's number an integer's literal.
112
+ const int = built.some(b => b.out instanceof Integer);
113
+ const body = (o) => (typeof o === 'number' ? (int ? intX(o) : natX(o)) : o instanceof Bool ? { op: 'bool', c: o.y } : o.x);
70
114
  const x = { op: 'match', data: d.name, of: this.x, cases: built.map(b => ({ binds: b.binds, body: body(b.out) })) };
71
- return (first instanceof Bool ? new Bool({ op: 'holds', x }) : first instanceof Val ? new Val(first.data, x) : new Nat(x));
115
+ return (first instanceof Bool ? new Bool({ op: 'holds', x }) : first instanceof Val ? new Val(first.data, x) : int ? new Integer(x) : new Nat(x));
72
116
  }
73
117
  is(ctor) { return new Bool({ op: 'is', data: this.data.name, of: this.x, ctor: this.data.index(ctor) }); }
118
+ // Its size: 1 plus the sizes of its fields of its datatype's group (a list's length plus 1, a tree's nodes and leaves).
119
+ size() { return new Nat({ op: 'size', data: this.data.name, of: this.x }); }
74
120
  eq(b) { return new Bool({ op: '=', a: this.x, b: b.x }); }
75
121
  ne(b) { return this.eq(b).not(); }
76
122
  }
77
123
  // A value of a sort from its term.
78
- export const wrap = (s, x) => (s === 'nat' ? new Nat(x) : s === 'bool' ? new Bool({ op: 'holds', x }) : new Val(s, x));
79
- const xOf = (s, v) => (s === 'nat' ? natX(v) : s === 'bool' ? { op: 'bool', c: v.y } : v.x);
124
+ export const wrap = (s, x) => (s === 'nat' ? new Nat(x) : s === 'bool' ? new Bool({ op: 'holds', x }) : s === 'int' ? new Integer(x) : new Val(s, x));
125
+ const xOf = (s, v) => (s === 'nat' ? natX(v) : s === 'bool' ? { op: 'bool', c: v.y } : s === 'int' ? intX(v) : v.x);
80
126
  export function when(c, a, b) {
81
127
  if (a instanceof Bool)
82
128
  return new Bool({ op: 'or', xs: [{ op: 'and', xs: [c.y, a.y] }, { op: 'and', xs: [{ op: 'not', x: c.y }, b.y] }] });
83
- const ax = typeof a === 'number' ? natX(a) : a.x, bx = typeof b === 'number' ? natX(b) : b.x;
129
+ const int = a instanceof Integer || b instanceof Integer, lit = (t) => (int ? intX(t) : natX(t));
130
+ const ax = typeof a === 'number' ? lit(a) : a.x, bx = typeof b === 'number' ? lit(b) : b.x;
84
131
  const x = { op: 'if', c: c.y, then: ax, else: bx };
85
- return (a instanceof Val ? new Val(a.data, x) : new Nat(x));
132
+ return (a instanceof Val ? new Val(a.data, x) : int ? new Integer(x) : new Nat(x));
86
133
  }
87
134
  // A datatype: its constructors, each with fields that are naturals or values of a datatype (`self`, another of its group,
88
135
  // or one declared before it). Its constructors are ordered as Lean's bijection numbers them: those of no field, then
@@ -92,8 +139,8 @@ let declaredSeq = 0;
92
139
  export const dataNamed = (name) => { const d = declared.get(name); if (!d)
93
140
  throw new Error(`no datatype ${name}`); return d; };
94
141
  // 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);
142
+ export const fieldSort = (f) => (f.sort === 'nat' || f.sort === 'int' ? f.sort : dataNamed(f.sort.data));
143
+ export const inGroup = (d, f) => isDataField(f.sort) && d.group.includes(f.sort.data);
97
144
  export class Data {
98
145
  name;
99
146
  group;
@@ -107,9 +154,9 @@ export class Data {
107
154
  const all = Object.entries(decl).map(([c, fs]) => ({ name: c, fields: Object.entries(fs).map(([f, s]) => ({ name: f, sort: s })) }));
108
155
  for (const c of all)
109
156
  for (const f of c.fields)
110
- if (f.sort !== 'nat' && !group.includes(f.sort.data) && !declared.has(f.sort.data))
157
+ if (isDataField(f.sort) && !group.includes(f.sort.data) && !declared.has(f.sort.data))
111
158
  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);
159
+ const rank = (c) => (!c.fields.length ? 0 : c.fields.every(f => !(isDataField(f.sort) && group.includes(f.sort.data))) ? 1 : 2);
113
160
  this.ctors = [...all].sort((a, b) => rank(a) - rank(b));
114
161
  declared.set(name, this);
115
162
  }
@@ -133,7 +180,7 @@ function groupHeights(group) {
133
180
  changed = false;
134
181
  for (const n of group)
135
182
  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);
183
+ const v = c.fields.reduce((m, f) => (isDataField(f.sort) && group.includes(f.sort.data) ? Math.max(m, 1 + h.get(f.sort.data)) : m), 0);
137
184
  if (v < h.get(n)) {
138
185
  h.set(n, v);
139
186
  changed = true;
@@ -170,7 +217,7 @@ export function weakOrder(ds) {
170
217
  busy.add(d.name);
171
218
  const o = 1 + Math.max(0, ...d.ctors.flatMap(c => c.fields.flatMap(f => (inGroup(d, f) && f.sort.data !== d.name ? [visit(dataNamed(f.sort.data))] : []))));
172
219
  busy.delete(d.name);
173
- if (d.ctors.some(c => c.fields.some(f => f.sort !== 'nat' && f.sort.data === d.name)) && weak(d))
220
+ if (d.ctors.some(c => c.fields.some(f => isDataField(f.sort) && f.sort.data === d.name)) && weak(d))
174
221
  throw new Error(`${d.name}: no constructor of no field and its first constructor has a field of ${d.name}, which the bijection with the naturals doesn't take`);
175
222
  out.set(d.name, o);
176
223
  return o;
@@ -183,11 +230,13 @@ const normField = (own, group) => (s) => {
183
230
  return { data: own };
184
231
  if (s === 'nat' || (s && typeof s === 'object' && 'nat' in s))
185
232
  return 'nat';
233
+ if (s === 'int')
234
+ return 'int';
186
235
  if (s instanceof Data)
187
236
  return { data: s.name };
188
237
  if (typeof s === 'string' && group.includes(s))
189
238
  return { data: s };
190
- throw new Error(`${own}: a field is natural, self, a datatype declared before, or a name of its group`);
239
+ throw new Error(`${own}: a field is natural, integer, self, a datatype declared before, or a name of its group`);
191
240
  };
192
241
  const withCtors = (d) => { for (const c of d.ctors)
193
242
  Object.defineProperty(d, c.name, c.fields.length ? { value: (...args) => d.make(c.name, ...args) } : { get: () => d.make(c.name) }); };
@@ -210,21 +259,35 @@ export function mutual(decls) {
210
259
  return Object.fromEntries(ds.map(d => [d.name, d]));
211
260
  }
212
261
  const fns = new Map();
213
- export function define(name, params, ret, body) {
262
+ export function define(name, params, ret, body, opts = {}) {
214
263
  if (!/^[A-Za-z][A-Za-z0-9_]*$/.test(name))
215
264
  throw new Error(`${name}: a function's name is letters and digits`);
216
265
  const ps = Object.entries(params).map(([n, s]) => ({ name: n, sort: s }));
217
- let built, rec;
218
- const fn = { name, params: ps, ret, paramFns: [], body: () => {
266
+ const stated = opts.measure && measureOf(name, ps, opts.measure);
267
+ let built, rec, failed;
268
+ // The recursion, checked once (a refusal kept and given again).
269
+ const got = () => {
270
+ if (failed)
271
+ throw failed;
272
+ try {
273
+ return (rec ??= recursion(fn));
274
+ }
275
+ catch (e) {
276
+ failed = e;
277
+ throw e;
278
+ }
279
+ };
280
+ const fn = { name, params: ps, ret, paramFns: [], stated, body: () => {
219
281
  if (!built) {
220
282
  const args = Object.fromEntries(ps.map(p => [p.name, wrap(p.sort, { op: 'var', name: `${name}.${p.name}` })]));
221
- built = xOf(ret, body(args));
283
+ built = ret === 'int' ? intResult(xOf(ret, body(args))) : xOf(ret, body(args));
222
284
  fn.paramFns = [...new Set(paramsIn(built))];
223
285
  walk(built, o => { if (o.op === 'call' && !fns.has(o.fn))
224
286
  throw new Error(`${name} calls ${String(o.fn)}, which isn't defined`); });
225
287
  }
226
288
  return built;
227
- }, get decreases() { return (rec ??= recursion(fn)).decreases; }, get ring() { return (rec ??= recursion(fn)).ring; }, set rec(r) { rec = r; } };
289
+ }, get decreases() { return got().decreases; }, get ring() { return got().ring; }, get measure() { return got().measure; }, get termination() { return got().termination; },
290
+ set rec(r) { rec = r; } };
228
291
  fns.set(name, fn);
229
292
  const call = (...args) => {
230
293
  if (args.length !== ps.length)
@@ -318,13 +381,318 @@ function recursion(fn) {
318
381
  }
319
382
  return undefined;
320
383
  };
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(', ')}` : ''})`);
384
+ const got = fn.stated ? undefined : choose(0, []);
385
+ if (!got) {
386
+ const why = `${fn.name}: not structural recursion (no parameter whose field every recursive call passes${members.length > 1 ? `, in its ring ${members.map(f => f.name).join(', ')}` : ''}${fn.stated ? '; a measure is given' : ''})`;
387
+ if (members.length > 1)
388
+ throw new Error(`${why}, and a ring of functions is taken by structural recursion only`);
389
+ const t = termination(fn, why);
390
+ return { ring: [fn.name], measure: t.measure, termination: t };
391
+ }
324
392
  const names = members.map(f => f.name);
325
393
  members.forEach((f, a) => { f.rec = { decreases: got[a], ring: names }; });
326
394
  return { decreases: got[0], ring: names };
327
395
  }
396
+ export const terminationMs = { ms: 10_000 };
397
+ const measurable = (s) => s === 'nat' || s instanceof Data;
398
+ function measureOf(name, ps, m) {
399
+ if (!m.length)
400
+ throw new Error(`${name}: an empty measure`);
401
+ return { comps: m.map(c => {
402
+ const names = typeof c === 'string' ? [c] : c;
403
+ return { coef: ps.map(p => names.filter(n => n === p.name).length), c: names.every(n => ps.some(p => p.name === n && measurable(p.sort))) ? 0 : (() => { throw new Error(`${name}: a measure names its parameters of a datatype or naturals (${names.join(', ')})`); })() };
404
+ }) };
405
+ }
406
+ export const showMeasure = (f, m) => {
407
+ const comp = (c) => {
408
+ const parts = c.coef.flatMap((k, i) => (k ? [`${k < 0 ? '−' : ''}${Math.abs(k) === 1 ? '' : `${Math.abs(k)}·`}${f.params[i].sort === 'nat' ? f.params[i].name : `size(${f.params[i].name})`}`] : []));
409
+ const t = [...parts, ...(c.c ? [String(c.c)] : [])].join(' + ').replace(/\+ −/g, '− ');
410
+ return t || '0';
411
+ };
412
+ return m.comps.length === 1 ? comp(m.comps[0]) : `(${m.comps.map(comp).join(', ')})`;
413
+ };
414
+ // A component at arguments, as a term (its negative part subtracted, truncated as the encoding reads it).
415
+ const sizeX = (s, a) => (s === 'nat' ? a : { op: 'size', data: s.name, of: a });
416
+ export function measureX(f, c, args) {
417
+ const pos = [], neg = [];
418
+ c.coef.forEach((k, i) => { if (!k)
419
+ return; const t = sizeX(f.params[i].sort, args[i]); (k > 0 ? pos : neg).push(Math.abs(k) === 1 ? t : { op: 'mul', k: Math.abs(k), a: t }); });
420
+ if (c.c > 0)
421
+ pos.push({ op: 'nat', value: c.c });
422
+ if (c.c < 0)
423
+ neg.push({ op: 'nat', value: -c.c });
424
+ const sum = (ts) => (ts.length ? ts.reduce((a, b) => ({ op: 'add', a, b })) : { op: 'nat', value: 0 });
425
+ return neg.length ? { op: 'sub', a: sum(pos), b: sum(neg) } : sum(pos);
426
+ }
427
+ const lexY = (as, bs) => (as.length === 1 ? { op: '<', a: as[0], b: bs[0] } : { op: 'or', xs: [{ op: '<', a: as[0], b: bs[0] }, { op: 'and', xs: [{ op: '=', a: as[0], b: bs[0] }, lexY(as.slice(1), bs.slice(1))] }] });
428
+ function callSites(fn) {
429
+ const out = [], yesX = { op: 'bool', c: yes.y };
430
+ const at = (sh, name) => { let t = sh.get(name); while (t && t.op === 'var' && sh.has(t.name))
431
+ t = sh.get(t.name); return t; };
432
+ const goY = (y, w) => {
433
+ switch (y.op) {
434
+ case '<':
435
+ case '<=':
436
+ case '=':
437
+ case 'i<':
438
+ case 'i<=':
439
+ goX(y.a, w);
440
+ goX(y.b, w);
441
+ return;
442
+ case 'and':
443
+ case 'or':
444
+ y.xs.forEach(z => goY(z, w));
445
+ return;
446
+ case 'not':
447
+ goY(y.x, w);
448
+ return;
449
+ case 'is':
450
+ goX(y.of, w);
451
+ return;
452
+ case 'holds':
453
+ goX(y.x, w);
454
+ return;
455
+ }
456
+ };
457
+ const goX = (x, w) => {
458
+ switch (x.op) {
459
+ case 'var':
460
+ case 'nat': return;
461
+ case 'add':
462
+ case 'sub':
463
+ case 'min':
464
+ case 'max':
465
+ case 'prod':
466
+ case 'iadd':
467
+ case 'isub':
468
+ goX(x.a, w);
469
+ goX(x.b, w);
470
+ return;
471
+ case 'mul':
472
+ case 'mod':
473
+ case 'div':
474
+ case 'imul':
475
+ case 'imod':
476
+ case 'idiv':
477
+ case 'ineg':
478
+ case 'ofnat':
479
+ goX(x.a, w);
480
+ return;
481
+ case 'int': return;
482
+ case 'apply':
483
+ goX(x.arg, w);
484
+ return;
485
+ case 'size':
486
+ goX(x.of, w);
487
+ return;
488
+ case 'bool':
489
+ goY(x.c, w);
490
+ return;
491
+ case 'ctor':
492
+ x.args.forEach(a => goX(a, w));
493
+ return;
494
+ case 'call':
495
+ x.args.forEach(a => goX(a, w));
496
+ if (x.fn === fn.name)
497
+ out.push({ args: x.args, ...w });
498
+ return;
499
+ case 'if':
500
+ goY(x.c, w);
501
+ goX(x.then, { ...w, wrap: y => w.wrap({ op: 'or', xs: [{ op: 'not', x: x.c }, y] }) });
502
+ goX(x.else, { ...w, wrap: y => w.wrap({ op: 'or', xs: [x.c, y] }) });
503
+ return;
504
+ case 'match': {
505
+ goX(x.of, w);
506
+ const d = dataNamed(x.data), v = x.of.op === 'var' ? x.of.name : undefined, had = v ? at(w.shape, v) : undefined;
507
+ x.cases.forEach((c, j) => {
508
+ if (v && had?.op === 'ctor') {
509
+ // matched before on this path: the same case, its fields the ones bound then; another, never reached
510
+ if (had.ctor === j)
511
+ goX(c.body, { ...w, shape: new Map([...w.shape, ...c.binds.map((b, i) => [b, had.args[i]])]) });
512
+ return;
513
+ }
514
+ if (v && !had) {
515
+ const fields = c.binds.map((b, i) => ({ name: b, sort: fieldSort(d.ctors[j].fields[i]) }));
516
+ goX(c.body, { wrap: w.wrap, shape: new Map([...w.shape, [v, { op: 'ctor', data: x.data, ctor: j, args: c.binds.map(b => ({ op: 'var', name: b })) }]]), vars: [...w.vars, ...fields] });
517
+ return;
518
+ }
519
+ goX(c.body, { ...w, wrap: y => w.wrap({ op: 'holds', x: { op: 'match', data: x.data, of: x.of, cases: x.cases.map((c2, i) => ({ binds: c2.binds, body: i === j ? { op: 'bool', c: y } : yesX })) } }) });
520
+ });
521
+ return;
522
+ }
523
+ case 'natmatch': {
524
+ goX(x.of, w);
525
+ const v = x.of.op === 'var' ? x.of.name : undefined;
526
+ if (v && !at(w.shape, v)) {
527
+ goX(x.zero, { ...w, shape: new Map([...w.shape, [v, { op: 'nat', value: 0 }]]) });
528
+ goX(x.succ, { wrap: w.wrap, shape: new Map([...w.shape, [v, { op: 'add', a: { op: 'var', name: x.pred }, b: { op: 'nat', value: 1 } }]]), vars: [...w.vars, { name: x.pred, sort: 'nat' }] });
529
+ return;
530
+ }
531
+ goX(x.zero, { ...w, wrap: y => w.wrap({ op: 'holds', x: { op: 'natmatch', of: x.of, zero: { op: 'bool', c: y }, pred: x.pred, succ: yesX } }) });
532
+ goX(x.succ, { ...w, wrap: y => w.wrap({ op: 'holds', x: { op: 'natmatch', of: x.of, zero: yesX, pred: x.pred, succ: { op: 'bool', c: y } } }) });
533
+ return;
534
+ }
535
+ }
536
+ };
537
+ goX(fn.body(), { wrap: y => y, shape: new Map(), vars: fn.params.map(q => ({ name: `${fn.name}.${q.name}`, sort: q.sort })) });
538
+ return out;
539
+ }
540
+ // The obligation of a call: under its path, the measure at its arguments below the measure at the parameters, a law over
541
+ // the path's variables (the parameters not matched, the fields of the cases taken).
542
+ function obligation(fn, m, s, i) {
543
+ const own = fn.params.map(q => ({ op: 'var', name: `${fn.name}.${q.name}` }));
544
+ const shaped = (t) => (Array.isArray(t) ? t.map(shaped) : t && typeof t === 'object' ? (t.op === 'var' && s.shape.has(t.name) ? shaped(s.shape.get(t.name)) : Object.fromEntries(Object.entries(t).map(([key, v]) => [key, shaped(v)]))) : t);
545
+ const holds = shaped(s.wrap(lexY(m.comps.map(c => measureX(fn, c, s.args)), m.comps.map(c => measureX(fn, c, own)))));
546
+ return { name: `${fn.name}'s call ${i + 1} lowers ${showMeasure(fn, m)}`, vars: s.vars.filter(v => !s.shape.has(v.name)), holds, params: [...new Set(paramsIn(holds))] };
547
+ }
548
+ // The calls the function's runs make, each from its parameters' sizes to its arguments' (random arguments, up to 2,000
549
+ // calls a run: a run past that is cut, its calls kept).
550
+ let tracing;
551
+ function runEdges(fn) {
552
+ const rand = rng(17), edges = new Map();
553
+ const feat = (vs) => fn.params.map((p, i) => (p.sort === 'nat' ? vs[i] : p.sort instanceof Data ? sizeOf(p.sort, vs[i]) : 0));
554
+ const tables = new Map();
555
+ const params = new Proxy({}, { get: (_, key) => tables.get(key) ?? (tables.set(key, randomFn(rand)), tables.get(key)) });
556
+ for (let i = 0; i < 120 && edges.size < 400; i++) {
557
+ const vals = fn.params.map(p => randomValue(p.sort, rand, 1 + (i % 7)));
558
+ tracing = { fn: fn.name, stack: [], fuel: 2000, edge: (a, b) => { const e = { s: feat(a), t: feat(b) }; edges.set(JSON.stringify(e), e); } };
559
+ try {
560
+ evalX({ op: 'call', fn: fn.name, args: vals.map((_, j) => ({ op: 'var', name: `$a${j}` })) }, new Map(vals.map((v, j) => [`$a${j}`, v])), params);
561
+ }
562
+ catch { /* cut: its calls kept */ }
563
+ finally {
564
+ tracing = undefined;
565
+ }
566
+ }
567
+ return [...edges.values()];
568
+ }
569
+ const valueOf = (c, xs) => Math.max(0, c.coef.reduce((a, k, i) => a + k * xs[i], c.c));
570
+ const lowers = (m, e) => {
571
+ for (const c of m.comps) {
572
+ const a = valueOf(c, e.t), b = valueOf(c, e.s);
573
+ if (a < b)
574
+ return true;
575
+ if (a > b)
576
+ return false;
577
+ }
578
+ return false;
579
+ };
580
+ function candidates(fn, edges) {
581
+ if (fn.stated)
582
+ return [fn.stated];
583
+ const ix = fn.params.flatMap((p, i) => (measurable(p.sort) ? [i] : [])), n = fn.params.length;
584
+ const one = (i) => ({ coef: fn.params.map((_, j) => (j === i ? 1 : 0)), c: 0 }), sum = (is) => ({ coef: fn.params.map((_, j) => (is.includes(j) ? 1 : 0)), c: 0 });
585
+ const out = ix.map(i => ({ comps: [one(i)] }));
586
+ for (const i of ix)
587
+ for (const j of ix)
588
+ if (i !== j)
589
+ out.push({ comps: [one(i), one(j)] });
590
+ for (const [a, i] of ix.entries())
591
+ for (const j of ix.slice(a + 1))
592
+ out.push({ comps: [sum([i, j])] });
593
+ if (ix.length <= 4)
594
+ for (const i of ix)
595
+ for (const j of ix)
596
+ for (const k of ix)
597
+ if (i !== j && j !== k && i !== k)
598
+ out.push({ comps: [one(i), one(j), one(k)] });
599
+ if (ix.length > 2)
600
+ out.push({ comps: [sum(ix)] });
601
+ // The LP's rank over the runs (coefficients and constants), where the runs give edges.
602
+ if (edges.length) {
603
+ const vars = ix.map(i => `p${i}`), env = (xs) => Object.fromEntries(ix.map(i => [`p${i}`, xs[i]]));
604
+ const r = ranksOfRuns(vars, edges.flatMap(e => [env(e.s), env(e.t)]), edges.map(e => ({ s: env(e.s), t: env(e.t) })));
605
+ if (!r.left && r.comps.length)
606
+ out.push({ comps: r.comps.map(c => ({ coef: Array.from({ length: n }, (_, j) => c.coef[`p${j}`] ?? 0), c: c.c })) });
607
+ }
608
+ return out;
609
+ }
610
+ // An obligation as one node of a cyclic proof (no use, no models): its conclusion, each definition it reads unfolded
611
+ // once, the constructors' facts at the values built, the shape at each size it reads and at its variables of a
612
+ // datatype, the congruences 16 random models' explanations use. The kernel checks it as any cyclic proof; undefined where it isn't proven.
613
+ function directly(law, ms) {
614
+ const p = programOf([law]), fs = factsOf(p), concl = encY(p, law.holds, new Map());
615
+ varSorts = new Map(law.vars.flatMap(v => (v.sort instanceof Data ? [[v.name, v.sort]] : [])));
616
+ const until = clock() + ms;
617
+ const insts = instancesOf(p, fs, { roots: [concl], others: [], depth: 1, shapesAt: [...varSorts.keys()].map(ev), etaAt: [], models: modelsFor(p, law, 16, 3), until });
618
+ if (clock() > until)
619
+ return undefined;
620
+ const was = pairedKeys.skip;
621
+ pairedKeys.skip = true;
622
+ try {
623
+ const v = withDeadline(until, () => proveCyclic(fs.facts, [{ name: law.name, hyps: [], concl, rank: [k(0)], insts, uses: [] }]));
624
+ return v.proven && v.checked ? v : undefined;
625
+ }
626
+ finally {
627
+ pairedKeys.skip = was;
628
+ }
629
+ }
630
+ function termination(fn, why) {
631
+ return counted(dataRate, () => {
632
+ if (dataTrace)
633
+ console.error(`[data] termination of ${fn.name}`);
634
+ const t0 = performance.now(), sites = callSites(fn);
635
+ if (!fn.params.some(p => measurable(p.sort)))
636
+ throw new Error(`${why}, and it has no parameter a measure reads (a natural or a datatype value)`);
637
+ if (dataTrace)
638
+ console.error(`[data] ${sites.length} calls`);
639
+ const edges = runEdges(fn), until = clock() + terminationMs.ms, tried = [], seen = new Set();
640
+ if (dataTrace)
641
+ console.error(`[data] its runs: ${edges.length} calls at ${Math.round(performance.now() - t0)} ms`);
642
+ for (const m of candidates(fn, edges)) {
643
+ if (dataTrace)
644
+ console.error(`[data] ${showMeasure(fn, m)} at ${Math.round(performance.now() - t0)} ms`);
645
+ if (clock() > until) {
646
+ tried.push('out of time');
647
+ break;
648
+ }
649
+ const text = showMeasure(fn, m), bad = edges.find(e => !lowers(m, e));
650
+ if (seen.has(text))
651
+ continue;
652
+ seen.add(text);
653
+ if (bad) {
654
+ if (fn.stated || !tried.length)
655
+ tried.push(`${text}: a call of its runs doesn't lower it (from ${JSON.stringify(bad.s)} to ${JSON.stringify(bad.t)}, the parameters' sizes)`);
656
+ continue;
657
+ }
658
+ const obligations = [];
659
+ let failed;
660
+ for (const [i, site] of sites.entries()) {
661
+ const law = obligation(fn, m, site, i);
662
+ // A call nested in its own argument or condition: the obligation would read the function being taken.
663
+ if (programOf([law]).fns.some(g => g.name === fn.name))
664
+ throw new Error(`${why}, and a call to itself is nested in the argument or the condition of another (nested recursion isn't taken)`);
665
+ // First one node, the definitions unfolded once (most calls lower the measure by their cases alone); else
666
+ // proveData: by induction where the obligation has a scheme (a call on a filter of the tail: the filter's own
667
+ // recursion), else, or then, the definitions unfolded alone.
668
+ const left = () => Math.max(1, until - clock()), cex = refute(law, 200);
669
+ if (cex) {
670
+ failed = `${text}: its call ${i + 1} doesn't lower it at ${JSON.stringify(cex)}`;
671
+ break;
672
+ }
673
+ const direct = directly(law, Math.min(2000, left()));
674
+ if (direct) {
675
+ obligations.push({ law, proof: direct, lemmas: [] });
676
+ continue;
677
+ }
678
+ const inductive = schemes(programOf([law]), law).length > 0;
679
+ // (no conjectured lemmas: their making isn't counted in the budget, and a call's decrease mostly needs none)
680
+ let v = proveData(law, inductive ? { ms: Math.min(12_000, left()), lemmas: false } : { induction: false, ms: Math.min(4000, left()) });
681
+ if (inductive && !(v.proven && v.checked) && !v.counterexample && clock() < until)
682
+ v = proveData(law, { induction: false, ms: Math.min(4000, left()) });
683
+ if (!(v.proven && v.checked)) {
684
+ failed = `${text}: its call ${i + 1} ${v.counterexample ? `doesn't lower it at ${JSON.stringify(v.counterexample)}` : 'not proven to lower it'}`;
685
+ break;
686
+ }
687
+ obligations.push({ law, proof: v.proof, scheme: v.scheme, lemmas: v.lemmas });
688
+ }
689
+ if (!failed)
690
+ return { measure: m, text, obligations, ms: Math.round(performance.now() - t0) };
691
+ tried.push(failed);
692
+ }
693
+ throw new Error(`${why}, and no measure is proven to fall at every call${tried.length ? ` (${tried.slice(0, 3).join('; ')})` : ''}`);
694
+ });
695
+ }
328
696
  export function law(name, vars, holds) {
329
697
  const vs = Object.entries(vars).map(([n, s]) => ({ name: n, sort: s }));
330
698
  const y = holds(Object.fromEntries(vs.map(v => [v.name, wrap(v.sort, { op: 'var', name: v.name })]))).y;
@@ -332,8 +700,20 @@ export function law(name, vars, holds) {
332
700
  }
333
701
  const fnOf = (name) => { const f = fns.get(name); if (!f)
334
702
  throw new Error(`no function ${name}`); return f; };
703
+ // A function by its name, if one is defined (core/transfer.ts names its copies apart from every other).
704
+ export const fnNamed = (name) => fns.get(name);
335
705
  const asNat = (v) => v;
336
- export function evalX(x, env, params) {
706
+ // Evaluation within bounds: a natural past 2^53 (no longer exact) always throws EvalLimit, and within the search
707
+ // (`evalBounds.on`: proveData, refute) so do calls nested past `depth` or past `calls` of one call's evaluation, which
708
+ // recursive powers and factorials reach on small values. The sample that needs one is skipped (refute), the model's
709
+ // read is unknown (the search).
710
+ export class EvalLimit extends RangeError {
711
+ }
712
+ export const evalBounds = { on: false, depth: 1000, calls: 20_000 };
713
+ let evalCalls = 0;
714
+ const exact = (n) => { if (n > Number.MAX_SAFE_INTEGER)
715
+ throw new EvalLimit('past 2^53'); return n; };
716
+ export function evalX(x, env, params, d = 0) {
337
717
  switch (x.op) {
338
718
  case 'var': {
339
719
  const v = env.get(x.name);
@@ -342,53 +722,92 @@ export function evalX(x, env, params) {
342
722
  return v;
343
723
  }
344
724
  case 'nat': return x.value;
345
- case 'add': return asNat(evalX(x.a, env, params)) + asNat(evalX(x.b, env, params));
346
- case 'sub': return Math.max(0, asNat(evalX(x.a, env, params)) - asNat(evalX(x.b, env, params)));
347
- case 'min': return Math.min(asNat(evalX(x.a, env, params)), asNat(evalX(x.b, env, params)));
348
- case 'max': return Math.max(asNat(evalX(x.a, env, params)), asNat(evalX(x.b, env, params)));
349
- case 'mul': return x.k * asNat(evalX(x.a, env, params));
350
- case 'if': return evalY(x.c, env, params) ? evalX(x.then, env, params) : evalX(x.else, env, params);
351
- case 'bool': return evalY(x.c, env, params) ? 1 : 0;
352
- case 'apply': return params[x.param](asNat(evalX(x.arg, env, params)));
353
- case 'ctor': return { c: x.ctor, f: x.args.map(a => evalX(a, env, params)) };
725
+ case 'add': return exact(asNat(evalX(x.a, env, params, d)) + asNat(evalX(x.b, env, params, d)));
726
+ case 'sub': return Math.max(0, asNat(evalX(x.a, env, params, d)) - asNat(evalX(x.b, env, params, d)));
727
+ case 'min': return Math.min(asNat(evalX(x.a, env, params, d)), asNat(evalX(x.b, env, params, d)));
728
+ case 'max': return Math.max(asNat(evalX(x.a, env, params, d)), asNat(evalX(x.b, env, params, d)));
729
+ case 'mul': return exact(x.k * asNat(evalX(x.a, env, params, d)));
730
+ case 'prod': return exact(asNat(evalX(x.a, env, params, d)) * asNat(evalX(x.b, env, params, d)));
731
+ case 'mod': return asNat(evalX(x.a, env, params, d)) % x.k;
732
+ case 'div': return Math.floor(asNat(evalX(x.a, env, params, d)) / x.k);
733
+ case 'int': return x.value;
734
+ case 'iadd': return exact(asNat(evalX(x.a, env, params, d)) + asNat(evalX(x.b, env, params, d)));
735
+ case 'isub': return exact(asNat(evalX(x.a, env, params, d)) - asNat(evalX(x.b, env, params, d)));
736
+ case 'ineg': return -asNat(evalX(x.a, env, params, d));
737
+ case 'imul': return exact(x.k * asNat(evalX(x.a, env, params, d)));
738
+ case 'ofnat': return asNat(evalX(x.a, env, params, d));
739
+ case 'imod': return euclidMod(asNat(evalX(x.a, env, params, d)), x.k);
740
+ case 'idiv': {
741
+ const v = asNat(evalX(x.a, env, params, d));
742
+ return (v - euclidMod(v, x.k)) / x.k;
743
+ }
744
+ case 'if': return evalY(x.c, env, params, d) ? evalX(x.then, env, params, d) : evalX(x.else, env, params, d);
745
+ case 'bool': return evalY(x.c, env, params, d) ? 1 : 0;
746
+ case 'apply': return params[x.param](asNat(evalX(x.arg, env, params, d)));
747
+ case 'size': return sizeOf(dataNamed(x.data), evalX(x.of, env, params, d));
748
+ case 'ctor': return { c: x.ctor, f: x.args.map(a => evalX(a, env, params, d)) };
354
749
  case 'natmatch': {
355
- const n = asNat(evalX(x.of, env, params));
750
+ const n = asNat(evalX(x.of, env, params, d));
356
751
  if (n === 0)
357
- return evalX(x.zero, env, params);
752
+ return evalX(x.zero, env, params, d);
358
753
  const e2 = new Map(env);
359
754
  e2.set(x.pred, n - 1);
360
- return evalX(x.succ, e2, params);
755
+ return evalX(x.succ, e2, params, d);
361
756
  }
362
757
  case 'match': {
363
- const v = evalX(x.of, env, params), cs = x.cases[v.c];
758
+ const v = evalX(x.of, env, params, d), cs = x.cases[v.c];
364
759
  const e2 = new Map(env);
365
760
  cs.binds.forEach((b, i) => e2.set(b, v.f[i]));
366
- return evalX(cs.body, e2, params);
761
+ return evalX(cs.body, e2, params, d);
367
762
  }
368
763
  case 'call': {
369
- const f = fnOf(x.fn), body = f.body();
370
- const e2 = new Map(f.params.map((p, i) => [`${f.name}.${p.name}`, evalX(x.args[i], env, params)]));
371
- return evalX(body, e2, params);
764
+ const f = fnOf(x.fn), body = f.body(), vals = x.args.map(a => evalX(a, env, params, d));
765
+ const e2 = new Map(f.params.map((p, i) => [`${f.name}.${p.name}`, vals[i]]));
766
+ if (evalBounds.on) {
767
+ if (d === 0)
768
+ evalCalls = 0;
769
+ if (d >= evalBounds.depth || ++evalCalls > evalBounds.calls)
770
+ throw new EvalLimit(`${f.name}: past the evaluation's bounds`);
771
+ }
772
+ if (tracing?.fn !== f.name)
773
+ return evalX(body, e2, params, d + 1);
774
+ // A run of a function whose termination is being proven: each call recorded, the run cut past its fuel.
775
+ const t = tracing;
776
+ if (--t.fuel < 0)
777
+ throw new RangeError('out of fuel');
778
+ const top = t.stack.at(-1);
779
+ if (top)
780
+ t.edge(top, vals);
781
+ t.stack.push(vals);
782
+ try {
783
+ return evalX(body, e2, params, d + 1);
784
+ }
785
+ finally {
786
+ t.stack.pop();
787
+ }
372
788
  }
373
789
  }
374
790
  }
375
- export function evalY(y, env, params) {
791
+ export function evalY(y, env, params, d = 0) {
376
792
  switch (y.op) {
377
- case '<': return asNat(evalX(y.a, env, params)) < asNat(evalX(y.b, env, params));
378
- case '<=': return asNat(evalX(y.a, env, params)) <= asNat(evalX(y.b, env, params));
379
- case '=': return same(evalX(y.a, env, params), evalX(y.b, env, params));
380
- case 'and': return y.xs.every(z => evalY(z, env, params));
381
- case 'or': return y.xs.some(z => evalY(z, env, params));
382
- case 'not': return !evalY(y.x, env, params);
383
- case 'is': return evalX(y.of, env, params).c === y.ctor;
384
- case 'holds': return evalX(y.x, env, params) === 1;
793
+ case '<': return asNat(evalX(y.a, env, params, d)) < asNat(evalX(y.b, env, params, d));
794
+ case '<=': return asNat(evalX(y.a, env, params, d)) <= asNat(evalX(y.b, env, params, d));
795
+ case 'i<': return asNat(evalX(y.a, env, params, d)) < asNat(evalX(y.b, env, params, d));
796
+ case 'i<=': return asNat(evalX(y.a, env, params, d)) <= asNat(evalX(y.b, env, params, d));
797
+ case '=': return same(evalX(y.a, env, params, d), evalX(y.b, env, params, d));
798
+ case 'and': return y.xs.every(z => evalY(z, env, params, d));
799
+ case 'or': return y.xs.some(z => evalY(z, env, params, d));
800
+ case 'not': return !evalY(y.x, env, params, d);
801
+ case 'is': return evalX(y.of, env, params, d).c === y.ctor;
802
+ case 'holds': return evalX(y.x, env, params, d) === 1;
385
803
  }
386
804
  }
805
+ const euclidMod = (v, k) => ((v % k) + k) % k;
387
806
  const same = (a, b) => (typeof a === 'number' || typeof b === 'number' ? a === b : a.c === b.c && a.f.every((x, i) => same(x, b.f[i])));
388
807
  export const showValue = (d, v) => {
389
808
  if (typeof v === 'number')
390
809
  return String(v);
391
- if (d === 'nat' || d === 'bool')
810
+ if (d === 'nat' || d === 'bool' || d === 'int')
392
811
  return JSON.stringify(v);
393
812
  const c = d.ctors[v.c];
394
813
  return c.fields.length ? `${c.name}(${c.fields.map((f, i) => showValue(fieldSort(f), v.f[i])).join(', ')})` : c.name;
@@ -399,6 +818,8 @@ export function randomValue(s, rand, size, natMax = 5) {
399
818
  return Math.floor(rand() * (natMax + 1));
400
819
  if (s === 'bool')
401
820
  return rand() < 0.5 ? 0 : 1;
821
+ if (s === 'int')
822
+ return Math.floor(rand() * (2 * natMax + 1)) - natMax;
402
823
  // The leaves: constructors of no field of the group, or (a datatype with none, a rose tree) those of least height.
403
824
  const base = s.ctors.filter(c => c.fields.every(f => !inGroup(s, f)));
404
825
  const leaves = base.length ? base : s.ctors.filter(c => s.ctorHeight(c) === s.height);
@@ -428,7 +849,7 @@ const yesC = leC(k(0), k(0));
428
849
  const pairE = (a, b) => { const s = { op: 'add', a, b }; return { op: 'add', a: { op: 'prod', a: s, b: s }, b }; };
429
850
  export const keyOf = (args) => args.slice(1).reduce(pairE, args[0]);
430
851
  // The arguments of a key built by keyOf (n of them), read back from its shape.
431
- function argsOf(key, n) {
852
+ export function argsOf(key, n) {
432
853
  if (n === 1)
433
854
  return [key];
434
855
  if (key.op !== 'add' || key.a.op !== 'prod' || key.a.a.op !== 'add')
@@ -459,6 +880,11 @@ export function programOf(laws, extra = []) {
459
880
  }
460
881
  if (o.op === 'apply')
461
882
  ps.add(o.param);
883
+ if (o.op === 'size')
884
+ addSort(dataNamed(o.data));
885
+ // (a datatype read only through a constructor, a match or a test of one: a law with a definition unfolded)
886
+ if ((o.op === 'ctor' || o.op === 'match' || o.op === 'is') && typeof o.data === 'string')
887
+ addSort(dataNamed(o.data));
462
888
  });
463
889
  for (const l of laws) {
464
890
  l.vars.forEach(v => addSort(v.sort));
@@ -478,11 +904,92 @@ export function programOf(laws, extra = []) {
478
904
  // (the recursion of each checked)
479
905
  for (const f of order)
480
906
  void f.decreases;
481
- return { datas, fns: order, params: [...ps] };
907
+ const divs = new Set();
908
+ let ints = false;
909
+ const scan = (t) => walk(t, o => { if (o.op === 'mod' || o.op === 'div' || o.op === 'imod' || o.op === 'idiv')
910
+ divs.add(o.k); if (typeof o.op === 'string' && (intOps.has(o.op) || o.op === 'i<' || o.op === 'i<='))
911
+ ints = true; });
912
+ for (const l of laws) {
913
+ scan(l.holds);
914
+ if (l.vars.some(v => v.sort === 'int'))
915
+ ints = true;
916
+ }
917
+ for (const f of order) {
918
+ scan(f.body());
919
+ if (f.ret === 'int' || f.params.some(q => q.sort === 'int'))
920
+ ints = true;
921
+ }
922
+ for (const d of datas)
923
+ if (d.ctors.some(c => c.fields.some(f => f.sort === 'int')))
924
+ ints = true;
925
+ if (ints)
926
+ divs.add(2);
927
+ return { datas, fns: order, params: [...ps], divisors: [...divs].sort((a, b) => a - b), ints };
482
928
  }
483
929
  const dataOf = (p, name) => p.datas.find(d => d.name === name);
930
+ // Division and remainder by a constant: the maps div$k and mod$k (core/machine.ts names them so), known by Euclid's fact
931
+ // at each key read (`arith k`).
932
+ export const divMap = (k) => `div$${k}`, modMap = (k) => `mod$${k}`;
933
+ const arithMap = /^(?:div|mod)\$(\d+)$/;
934
+ // Integers by their zigzag code: a code c is the integer p − n, p and n its value's two parts as maps of the code
935
+ // (int$pos, int$neg), known by one fact (`zcode`): c + c mod 2 = 2p + 2n, p = 0 where c is odd, n = 0 where it is even;
936
+ // a pair (p, n) has the code 2(p ∸ n) + (2(n ∸ p) ∸ 1). Neither direction needs an if, so the kernel splits only where a
937
+ // proof needs the sign or the parity.
938
+ export const zig = (z) => (z >= 0 ? 2 * z : -2 * z - 1);
939
+ export const unzig = (c) => (c % 2 === 0 ? c / 2 : -(c + 1) / 2);
940
+ const sumE = (a, b) => (a.op === 'const' && a.value === 0 ? b : b.op === 'const' && b.value === 0 ? a : { op: 'add', a, b });
941
+ export const posMap = 'int$pos', negMap = 'int$neg';
942
+ const pairOfCode = (c) => {
943
+ if (c.op === 'const') {
944
+ const z = unzig(c.value);
945
+ return [k(Math.max(z, 0)), k(Math.max(-z, 0))];
946
+ }
947
+ return [get(posMap, c), get(negMap, c)];
948
+ };
949
+ const codeOfPair = ([pos, neg]) => {
950
+ if (neg.op === 'const' && neg.value === 0)
951
+ return { op: 'mul', k: 2, a: pos };
952
+ return { op: 'add', a: { op: 'mul', k: 2, a: { op: 'sub', a: pos, b: neg } }, b: { op: 'sub', a: { op: 'mul', k: 2, a: { op: 'sub', a: neg, b: pos } }, b: k(1) } };
953
+ };
954
+ const intOps = new Set(['int', 'iadd', 'isub', 'ineg', 'imul', 'ofnat', 'imod', 'idiv']);
955
+ // An integer term as the two naturals p − n of its value.
956
+ function encI(p, x, env) {
957
+ if (x.op === 'int')
958
+ return [k(Math.max(x.value, 0)), k(Math.max(-x.value, 0))];
959
+ if (x.op === 'iadd' || x.op === 'isub') {
960
+ const [a, b] = [encI(p, x.a, env), encI(p, x.b, env)];
961
+ return x.op === 'iadd' ? [sumE(a[0], b[0]), sumE(a[1], b[1])] : [sumE(a[0], b[1]), sumE(a[1], b[0])];
962
+ }
963
+ if (x.op === 'ineg') {
964
+ const [pos, neg] = encI(p, x.a, env);
965
+ return [neg, pos];
966
+ }
967
+ if (x.op === 'imul') {
968
+ const [pos, neg] = encI(p, x.a, env), m = Math.abs(x.k), sc = (e) => (m === 1 ? e : { op: 'mul', k: m, a: e });
969
+ return x.k >= 0 ? [sc(pos), sc(neg)] : [sc(neg), sc(pos)];
970
+ }
971
+ if (x.op === 'ofnat')
972
+ return [encX(p, x.a, env), k(0)];
973
+ // pos + (k − 1)·neg is the value plus k·neg: the same remainder, and the quotient plus neg.
974
+ if (x.op === 'imod' || x.op === 'idiv') {
975
+ const [pos, neg] = encI(p, x.a, env), t = x.k === 1 ? pos : sumE(pos, { op: 'mul', k: x.k - 1, a: neg });
976
+ return x.op === 'imod' ? [get(modMap(x.k), t), k(0)] : [get(divMap(x.k), t), neg];
977
+ }
978
+ return pairOfCode(encX(p, x, env));
979
+ }
484
980
  export function encX(p, x, env) {
485
981
  switch (x.op) {
982
+ case 'prod': return { op: 'prod', a: encX(p, x.a, env), b: encX(p, x.b, env) };
983
+ case 'mod': return get(modMap(x.k), encX(p, x.a, env));
984
+ case 'div': return get(divMap(x.k), encX(p, x.a, env));
985
+ case 'int': return k(zig(x.value));
986
+ case 'iadd':
987
+ case 'isub':
988
+ case 'ineg':
989
+ case 'imul':
990
+ case 'ofnat':
991
+ case 'imod':
992
+ case 'idiv': return codeOfPair(encI(p, x, env));
486
993
  case 'var': return env.get(x.name) ?? ev(x.name);
487
994
  case 'nat': return k(x.value);
488
995
  case 'add':
@@ -493,6 +1000,7 @@ export function encX(p, x, env) {
493
1000
  case 'if': return { op: 'if', c: encY(p, x.c, env), then: encX(p, x.then, env), else: encX(p, x.else, env) };
494
1001
  case 'bool': return { op: 'if', c: encY(p, x.c, env), then: k(1), else: k(0) };
495
1002
  case 'apply': return get(x.param, encX(p, x.arg, env));
1003
+ case 'size': return get(sizeMap(x.data), encX(p, x.of, env));
496
1004
  case 'call': return get(x.fn, keyOf(x.args.map(a => encX(p, a, env))));
497
1005
  case 'ctor': {
498
1006
  const d = dataOf(p, x.data);
@@ -512,9 +1020,20 @@ export function encX(p, x, env) {
512
1020
  }
513
1021
  export function encY(p, y, env) {
514
1022
  switch (y.op) {
1023
+ case 'i<':
1024
+ case 'i<=': {
1025
+ const [a, b] = [encI(p, y.a, env), encI(p, y.b, env)];
1026
+ return { op: y.op === 'i<' ? '<' : '<=', a: sumE(a[0], b[1]), b: sumE(b[0], a[1]) };
1027
+ }
1028
+ // Two integers where one is arithmetic: equal values (the same as equal codes, without writing the sum's code).
1029
+ case '=':
1030
+ if (intOps.has(y.a.op) || intOps.has(y.b.op)) {
1031
+ const [a, b] = [encI(p, y.a, env), encI(p, y.b, env)];
1032
+ return { op: '=', a: sumE(a[0], b[1]), b: sumE(b[0], a[1]) };
1033
+ }
1034
+ return { op: '=', a: encX(p, y.a, env), b: encX(p, y.b, env) };
515
1035
  case '<':
516
- case '<=':
517
- case '=': return { op: y.op, a: encX(p, y.a, env), b: encX(p, y.b, env) };
1036
+ case '<=': return { op: y.op, a: encX(p, y.a, env), b: encX(p, y.b, env) };
518
1037
  case 'and':
519
1038
  case 'or': return { op: y.op, xs: y.xs.map(z => encY(p, z, env)) };
520
1039
  case 'not': return { op: 'not', x: encY(p, y.x, env) };
@@ -552,6 +1071,18 @@ export function factsOf(p) {
552
1071
  const body = encX(p, f.body(), new Map());
553
1072
  put(`fn ${f.name}`, { name: '', keys, holds: andC(eqC(get(f.name, keyOf(keys.map(ev))), body), ...(f.ret === 'bool' ? [leC(get(f.name, keyOf(keys.map(ev))), k(1))] : [])) });
554
1073
  }
1074
+ // Euclid's division by each divisor: x = k·(x div k) + x mod k, x mod k < k.
1075
+ for (const k2 of p.divisors) {
1076
+ put(`arith ${k2}`, { name: '', keys: ['x$'], holds: andC(eqC(x, { op: 'add', a: { op: 'mul', k: k2, a: get(divMap(k2), x) }, b: get(modMap(k2), x) }), { op: '<', a: get(modMap(k2), x), b: k(k2) }) });
1077
+ arity.set(divMap(k2), 1);
1078
+ arity.set(modMap(k2), 1);
1079
+ }
1080
+ if (p.ints) {
1081
+ const pos = get(posMap, x), neg = get(negMap, x), par = get(modMap(2), x);
1082
+ put('zcode', { name: '', keys: ['x$'], holds: andC(eqC({ op: 'add', a: x, b: par }, { op: 'add', a: { op: 'mul', k: 2, a: pos }, b: { op: 'mul', k: 2, a: neg } }), { op: 'or', xs: [eqC(par, k(0)), eqC(pos, k(0))] }, { op: 'or', xs: [eqC(par, k(1)), eqC(neg, k(0))] }) });
1083
+ arity.set(posMap, 1);
1084
+ arity.set(negMap, 1);
1085
+ }
555
1086
  // Every map the encoding reads: the datatypes' tag, size and fields too (each of one argument).
556
1087
  for (const d of p.datas) {
557
1088
  arity.set(tagMap(d.name), 1);
@@ -584,14 +1115,14 @@ export function decode(d, n) {
584
1115
  if (n < z)
585
1116
  return { c: n, f: [] };
586
1117
  const q = Math.floor((n - z) / m), j = z + ((n - z) % m), c = d.ctors[j];
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); }) };
1118
+ return { c: j, f: c.fields.map((f, i) => { const x = tup(q, c.fields.length, i); return f.sort === 'nat' ? x : f.sort === 'int' ? unzig(x) : decode(dataNamed(f.sort.data), x); }) };
588
1119
  }
589
1120
  export function encode(d, v) {
590
1121
  const o = v, z = d.nullary, m = d.ctors.length - z;
591
1122
  if (o.c < z)
592
1123
  return o.c;
593
1124
  const c = d.ctors[o.c];
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));
1125
+ return safe(z + m * tupk(c.fields.map((f, i) => (f.sort === 'nat' ? o.f[i] : f.sort === 'int' ? zig(o.f[i]) : encode(dataNamed(f.sort.data), o.f[i])))) + (o.c - z));
595
1126
  }
596
1127
  // Each map of the encoding as a function of its arguments, on the codes (for core/cycles.ts's runs).
597
1128
  export function codeValues(p) {
@@ -600,20 +1131,28 @@ export function codeValues(p) {
600
1131
  out[tagMap(d.name)] = n => decode(d, n).c;
601
1132
  out[sizeMap(d.name)] = n => sizeOf(d, decode(d, n));
602
1133
  d.ctors.forEach((c, j) => {
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; }; });
1134
+ c.fields.forEach((f, i) => { out[fieldMap(d, j, i)] = n => { const v = decode(d, n); return v.c === j ? (f.sort === 'nat' ? v.f[i] : f.sort === 'int' ? zig(v.f[i]) : encode(dataNamed(f.sort.data), v.f[i])) : 0; }; });
604
1135
  if (c.fields.length)
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]))) });
1136
+ out[ctorMap(d, j)] = (...xs) => encode(d, { c: j, f: c.fields.map((f, i) => (f.sort === 'nat' ? xs[i] : f.sort === 'int' ? unzig(xs[i]) : decode(dataNamed(f.sort.data), xs[i]))) });
606
1137
  });
607
1138
  }
608
1139
  for (const f of p.fns)
609
1140
  out[f.name] = (...xs) => {
610
- const v = evalX(f.body(), new Map(f.params.map((q, i) => [`${f.name}.${q.name}`, q.sort instanceof Data ? decode(q.sort, xs[i]) : xs[i]])), {});
611
- return f.ret instanceof Data ? encode(f.ret, v) : v;
1141
+ const v = evalX(f.body(), new Map(f.params.map((q, i) => [`${f.name}.${q.name}`, q.sort instanceof Data ? decode(q.sort, xs[i]) : q.sort === 'int' ? unzig(xs[i]) : xs[i]])), {});
1142
+ return f.ret instanceof Data ? encode(f.ret, v) : f.ret === 'int' ? zig(v) : v;
612
1143
  };
1144
+ for (const k2 of p.divisors) {
1145
+ out[divMap(k2)] = n => Math.floor(n / k2);
1146
+ out[modMap(k2)] = n => n % k2;
1147
+ }
1148
+ if (p.ints) {
1149
+ out[posMap] = n => Math.max(unzig(n), 0);
1150
+ out[negMap] = n => Math.max(-unzig(n), 0);
1151
+ }
613
1152
  return out;
614
1153
  }
615
1154
  // That a value's fields of its datatype have smaller codes (true of the codes above: each field at most q).
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$') })] : [])))) }));
1155
+ export const orderFacts = (p) => p.datas.map(d => ({ name: `order ${d.name}`, keys: ['x$'], holds: andC(yesC, ...d.ctors.flatMap((c, j) => c.fields.flatMap((f, i) => (isDataField(f.sort) && f.sort.data === d.name ? [implies(eqC(get(tagMap(d.name), ev('x$')), k(j)), { op: '<', a: get(fieldMap(d, j, i), ev('x$')), b: ev('x$') })] : [])))) }));
617
1156
  // --- concrete models: a value of the encoding at random values (which terms are equal, for congruences and matches) ----
618
1157
  // Values interned per datatype: a constructor of no field is its tag; the rest numbered from there. A key is a
619
1158
  // pairing's natural, read back through its square root.
@@ -654,8 +1193,8 @@ export class Model {
654
1193
  return v;
655
1194
  }
656
1195
  // A value of a sort as the encoding's natural, and back.
657
- code(s, v) { return s instanceof Data ? this.id(s, v) : v; }
658
- decode(s, n) { return s instanceof Data ? this.value(s, n) : n; }
1196
+ code(s, v) { return s instanceof Data ? this.id(s, v) : s === 'int' ? zig(v) : v; }
1197
+ decode(s, n) { return s instanceof Data ? this.value(s, n) : s === 'int' ? unzig(n) : n; }
659
1198
  memo = new Map();
660
1199
  read(map, key) {
661
1200
  const at = `${map}|${key}`, had = this.memo.get(at);
@@ -690,8 +1229,17 @@ export class Model {
690
1229
  const as = unkey(key, f.params.length);
691
1230
  const env = new Map(f.params.map((q, i) => [`${f.name}.${q.name}`, this.decode(q.sort, as[i])]));
692
1231
  const out = evalX(f.body(), env, this.params);
693
- return f.ret === 'nat' || f.ret === 'bool' ? out : this.id(f.ret, out);
1232
+ return f.ret === 'nat' || f.ret === 'bool' ? out : f.ret === 'int' ? zig(out) : this.id(f.ret, out);
694
1233
  }
1234
+ const ar = arithMap.exec(map);
1235
+ if (ar) {
1236
+ const k2 = Number(ar[1]);
1237
+ return map.startsWith('mod') ? key % k2 : Math.floor(key / k2);
1238
+ }
1239
+ if (map === posMap)
1240
+ return Math.max(unzig(key), 0);
1241
+ if (map === negMap)
1242
+ return Math.max(-unzig(key), 0);
695
1243
  const prm = this.params[map];
696
1244
  if (prm)
697
1245
  return prm(key);
@@ -751,20 +1299,31 @@ export function unkey(key, n) {
751
1299
  return [...unkey(a, n - 1), b];
752
1300
  }
753
1301
  // Random values of the law's variables: a law false at one is refuted with it.
1302
+ // A sample whose evaluation passes the bounds (EvalLimit, or the stack) is skipped.
754
1303
  export function refute(l, n = 400, seed = 7) {
755
- for (const s of samples(l.vars, programOf([l]).params, n, seed)) {
756
- let ok;
757
- try {
758
- ok = evalY(l.holds, s.env, s.params);
759
- }
760
- catch (e) {
761
- throw new Error(`${l.name}: ${e.message}`);
1304
+ return bounded(() => {
1305
+ for (const s of samples(l.vars, programOf([l]).params, n, seed)) {
1306
+ let ok;
1307
+ try {
1308
+ ok = evalY(l.holds, s.env, s.params);
1309
+ }
1310
+ catch (e) {
1311
+ if (e instanceof RangeError)
1312
+ continue;
1313
+ throw new Error(`${l.name}: ${e.message}`);
1314
+ }
1315
+ if (!ok)
1316
+ return Object.fromEntries(l.vars.map(v => [v.name, showValue(v.sort, s.env.get(v.name))]));
762
1317
  }
763
- if (!ok)
764
- return Object.fromEntries(l.vars.map(v => [v.name, showValue(v.sort, s.env.get(v.name))]));
765
- }
766
- return undefined;
1318
+ return undefined;
1319
+ });
767
1320
  }
1321
+ const bounded = (f) => { const was = evalBounds.on; evalBounds.on = true; try {
1322
+ return f();
1323
+ }
1324
+ finally {
1325
+ evalBounds.on = was;
1326
+ } };
768
1327
  // The function a function f calls at its decreasing argument's field of datatype `to` (a rose tree's sum, at its forest:
769
1328
  // the forest's sum): the call, the same for every such field, its other arguments f's own parameters or closed terms.
770
1329
  function partner(f, to) {
@@ -783,7 +1342,7 @@ function partner(f, to) {
783
1342
  const t = x;
784
1343
  if (t.op === 'match' && t.of.op === 'var' && t.of.name === own) {
785
1344
  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; })])));
1345
+ t.cases.forEach((c, j) => go(c.body, new Set([...fields, ...c.binds.filter((_, i) => { const fl = d.ctors[j].fields[i]; return isDataField(fl.sort) && fl.sort.data === to.name; })])));
787
1346
  return;
788
1347
  }
789
1348
  if (t.op === 'call') {
@@ -883,6 +1442,31 @@ function mutualScheme(l, v, d) {
883
1442
  ? [{ subst: { [at.get(f.sort.data).v]: get(fieldMap(x, j, i), ev(xv)) }, guard: eqC(get(tagMap(x.name), ev(xv)), k(j)), member: order.indexOf(f.sort.data) }] : [])));
884
1443
  return { name: `mutual structure of ${v}`, on: { v, d }, uses: usesOf(d, v), companions: order.slice(1).map(n => { const x = dataNamed(n), a = at.get(n); return { law: a.law, on: { v: a.v, d: x }, uses: usesOf(x, a.v) }; }) };
885
1444
  }
1445
+ // The width of a program's ranks' middle: its widest measure (1 when every recursion is structural).
1446
+ const rankWidth = (p) => Math.max(1, ...p.fns.map(f => (f.decreases === undefined ? f.measure?.comps.length ?? 1 : 1)));
1447
+ // Where a well-founded scheme's node wants a datatype's shape: at the values its measure reads, and at those its measure
1448
+ // reads at each use (a constructor's value a call passes: its size from its fields).
1449
+ function rankShapes(shapes, rank, uses) {
1450
+ const out = new Map(shapes.map(e => [tid(e), e]));
1451
+ walk(uses.map(u => rank.map(e => substE(e, { vars: u.subst, maps: {}, flags: {} }))), o => { if (o.op === 'get' && o.map.endsWith('.size'))
1452
+ out.set(tid(o.key), o.key); });
1453
+ return [...out.values()];
1454
+ }
1455
+ // The parameters a function's definition is decided at when they are a constructor's value: a structural one's
1456
+ // decreasing parameter (a datatype's), a well-founded one's parameters of a datatype its body matches on.
1457
+ function unfoldAt(f) {
1458
+ if (f.decreases !== undefined)
1459
+ return f.params[f.decreases].sort instanceof Data ? [f.decreases] : [];
1460
+ if (!f.measure)
1461
+ return [];
1462
+ const out = new Set();
1463
+ walk(f.body(), o => { if (o.op === 'match' && o.of.op === 'var') {
1464
+ const i = f.params.findIndex(q => `${f.name}.${q.name}` === o.of.name);
1465
+ if (i >= 0 && f.params[i].sort instanceof Data)
1466
+ out.add(i);
1467
+ } });
1468
+ return [...out];
1469
+ }
886
1470
  // The inductions the search tries for a law (its schemes), for tests.
887
1471
  export const schemesOf = (p, l) => schemes(p, l);
888
1472
  function schemes(p, l) {
@@ -898,12 +1482,13 @@ function schemes(p, l) {
898
1482
  return;
899
1483
  const f = fnOf(o.fn), args = o.args;
900
1484
  f.body();
901
- if (f.decreases === undefined)
1485
+ const m = f.decreases === undefined ? f.measure : undefined;
1486
+ if (f.decreases === undefined && !m)
902
1487
  return;
903
- const at = args[f.decreases];
904
- if (at.op !== 'var' || !l.vars.some(v => v.name === at.name))
1488
+ const at = args[f.decreases ?? 0];
1489
+ if (!m && (at.op !== 'var' || !l.vars.some(v => v.name === at.name)))
905
1490
  return;
906
- const d = f.params[f.decreases].sort;
1491
+ const d = f.params[f.decreases ?? 0].sort;
907
1492
  const formal = new Map(f.params.map((q, i) => [`${f.name}.${q.name}`, args[i].op === 'var' && l.vars.some(v => v.name === args[i].name) ? ev(args[i].name) : undefined]));
908
1493
  const uses = [];
909
1494
  const go = (x, env, guard) => {
@@ -928,6 +1513,14 @@ function schemes(p, l) {
928
1513
  go(t.succ, e2, [...guard, { op: 'not', x: eqC(of, k(0)) }]);
929
1514
  return;
930
1515
  }
1516
+ // (a well-founded call's descent may need its if's condition: x − y below x where y ≤ x)
1517
+ if (m && t.op === 'if') {
1518
+ const c = encY(p, t.c, env);
1519
+ go(t.c, env, guard);
1520
+ go(t.then, env, [...guard, c]);
1521
+ go(t.else, env, [...guard, { op: 'not', x: c }]);
1522
+ return;
1523
+ }
931
1524
  if (t.op === 'call' && t.fn === f.name) {
932
1525
  const subst = {};
933
1526
  let ok = true;
@@ -949,8 +1542,16 @@ function schemes(p, l) {
949
1542
  };
950
1543
  const env = new Map([...formal].flatMap(([key, e]) => (e ? [[key, e]] : [])));
951
1544
  go(f.body(), env, []);
952
- if (uses.length)
1545
+ if (!uses.length)
1546
+ return;
1547
+ if (!m) {
953
1548
  add({ name: `recursion of ${f.name} on ${at.name}`, on: { v: at.name, d }, uses });
1549
+ return;
1550
+ }
1551
+ // Well-founded: ranked by the measure at the law's arguments; the law's variables it reads named.
1552
+ const read = f.params.flatMap((q, i) => (m.comps.some(c => c.coef[i]) && args[i].op === 'var' && l.vars.some(v => v.name === args[i].name) ? [{ i, v: args[i].name }] : []));
1553
+ add({ name: `recursion of ${f.name} on ${read.map(r => r.v).join(', ') || 'its arguments'}`, uses, rank: m.comps.map(c => encX(p, measureX(f, c, args), new Map())),
1554
+ shapes: read.filter(r => f.params[r.i].sort instanceof Data).map(r => ev(r.v)) });
954
1555
  });
955
1556
  // Structural induction on each variable of a datatype, the others fixed; mutual where its fields reach another of its
956
1557
  // group (the law and its companions), plain where they reach none or no companion is found.
@@ -962,7 +1563,7 @@ function schemes(p, l) {
962
1563
  out.push(mut);
963
1564
  continue;
964
1565
  }
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)) }] : []))) });
1566
+ add({ name: `structure of ${v.name}`, on: { v: v.name, d }, uses: d.ctors.flatMap((c, j) => c.fields.flatMap((f, i) => (isDataField(f.sort) && f.sort.data === d.name ? [{ subst: { [v.name]: get(fieldMap(d, j, i), x) }, guard: eqC(get(tagMap(d.name), x), k(j)) }] : []))) });
966
1567
  }
967
1568
  return out;
968
1569
  }
@@ -1024,10 +1625,27 @@ function instancesOf(p, fs, w) {
1024
1625
  inst(`fn ${f.name}`, as);
1025
1626
  const body = reads(holdsAt(`fn ${f.name}`, as));
1026
1627
  ctors(note(body));
1027
- next.push(...body);
1628
+ for (const b of body)
1629
+ next.push(b);
1028
1630
  }
1029
1631
  frontier = next;
1030
1632
  }
1633
+ // Euclid's fact at every key a remainder or quotient is read at (a read the facts bring is read too).
1634
+ const ariths = () => {
1635
+ for (let pass = 0; pass < 2; pass++)
1636
+ for (const r of [...all]) {
1637
+ if (r.map === posMap || r.map === negMap) {
1638
+ inst('zcode', [r.key]);
1639
+ note(reads(holdsAt('zcode', [r.key])));
1640
+ }
1641
+ const m = arithMap.exec(r.map);
1642
+ if (m) {
1643
+ inst(`arith ${m[1]}`, [r.key]);
1644
+ note(reads(holdsAt(`arith ${m[1]}`, [r.key])));
1645
+ }
1646
+ }
1647
+ };
1648
+ ariths();
1031
1649
  lap(`unfolded, ${insts.length} instances, ${all.length} reads`);
1032
1650
  const closure = (model) => {
1033
1651
  const parent = new Map(), terms = new Map(), adj = new Map();
@@ -1133,32 +1751,31 @@ function instancesOf(p, fs, w) {
1133
1751
  }
1134
1752
  for (const r of [...all]) {
1135
1753
  const f = fnOfMap(r.map);
1136
- if (!f || f.decreases === undefined)
1754
+ if (!f)
1137
1755
  continue;
1138
1756
  const as = argsOf(r.key, f.params.length);
1139
1757
  if (!as)
1140
1758
  continue;
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));
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);
1146
- const cands = built(own) ? [own] : [...cl.terms.values()].filter(t => built(t) && cl.find(tid(t)) === root);
1147
- for (const t of cands) {
1148
- const as2 = as.map((a, i) => (i === f.decreases ? t : a)), rid = JSON.stringify({ map: f.name, key: keyOf(as2) }), key2 = JSON.stringify([fs.at.get(`fn ${f.name}`), as2]);
1149
- const anchor = { read: tid(get(f.name, r.key)), arg: own, value: t }, had = anchoredAt.get(key2) ?? [];
1150
- if (!had.some(x => x.read === anchor.read))
1151
- anchoredAt.set(key2, [...had, anchor]);
1152
- if (unfolded.has(rid) || extra >= 12 || done.has(sigOf(f.name, as2)))
1153
- continue;
1154
- unfolded.add(rid);
1155
- extra++;
1156
- done.add(sigOf(f.name, as2));
1157
- if (dataTrace)
1158
- console.error(`[data] unfold ${f.name} at ${JSON.stringify(as2).slice(0, 160)} for ${JSON.stringify(r.key).slice(0, 80)}`);
1159
- inst(`fn ${f.name}`, as2);
1160
- note([{ map: f.name, key: keyOf(as2) }]);
1161
- ctors(note(reads(holdsAt(`fn ${f.name}`, as2))));
1759
+ for (const di of unfoldAt(f)) {
1760
+ const own = as[di], d = f.params[di].sort, root = cl.find(tid(own));
1761
+ const built = (t) => (t.op === 'get' && d.ctors.some((c, j) => c.fields.length && t.map === ctorMap(d, j))) || (t.op === 'const' && t.value < d.nullary);
1762
+ const cands = built(own) ? [own] : [...cl.terms.values()].filter(t => built(t) && cl.find(tid(t)) === root);
1763
+ for (const t of cands) {
1764
+ const as2 = as.map((a, i) => (i === di ? t : a)), rid = JSON.stringify({ map: f.name, key: keyOf(as2) }), key2 = JSON.stringify([fs.at.get(`fn ${f.name}`), as2]);
1765
+ const anchor = { read: tid(get(f.name, r.key)), arg: own, value: t }, had = anchoredAt.get(key2) ?? [];
1766
+ if (!had.some(x => x.read === anchor.read))
1767
+ anchoredAt.set(key2, [...had, anchor]);
1768
+ if (unfolded.has(rid) || extra >= 12 || done.has(sigOf(f.name, as2)))
1769
+ continue;
1770
+ unfolded.add(rid);
1771
+ extra++;
1772
+ done.add(sigOf(f.name, as2));
1773
+ if (dataTrace)
1774
+ console.error(`[data] unfold ${f.name} at ${JSON.stringify(as2).slice(0, 160)} for ${JSON.stringify(r.key).slice(0, 80)}`);
1775
+ inst(`fn ${f.name}`, as2);
1776
+ note([{ map: f.name, key: keyOf(as2) }]);
1777
+ ctors(note(reads(holdsAt(`fn ${f.name}`, as2))));
1778
+ }
1162
1779
  }
1163
1780
  }
1164
1781
  }
@@ -1177,7 +1794,7 @@ function instancesOf(p, fs, w) {
1177
1794
  if (c.fields.length && t.map === ctorMap(d, j))
1178
1795
  return d;
1179
1796
  for (const [i, fl] of c.fields.entries())
1180
- if (fl.sort !== 'nat' && t.map === fieldMap(d, j, i))
1797
+ if (isDataField(fl.sort) && t.map === fieldMap(d, j, i))
1181
1798
  return dataNamed(fl.sort.data);
1182
1799
  }
1183
1800
  }
@@ -1189,6 +1806,12 @@ function instancesOf(p, fs, w) {
1189
1806
  } };
1190
1807
  for (const t of w.shapesAt)
1191
1808
  shape(t, typeOfVar(p, t));
1809
+ // A size the goal reads (a measure's, in a well-founded call's obligation): the shape of its value, which gives the
1810
+ // size from the fields.
1811
+ for (const r of reads([w.roots, w.others]))
1812
+ for (const d of p.datas)
1813
+ if (r.map === sizeMap(d.name))
1814
+ shape(r.key, d);
1192
1815
  if (w.depth >= 2)
1193
1816
  for (const r of [...all])
1194
1817
  for (const d of p.datas)
@@ -1345,6 +1968,9 @@ function instancesOf(p, fs, w) {
1345
1968
  for (const d of used.values())
1346
1969
  if (!w.every || d.n >= w.models.length)
1347
1970
  inst(`cong ${d.map}`, d.at);
1971
+ for (const q of [...insts])
1972
+ note(reads(holdsAt(fs.facts[q.fact].name, q.at)));
1973
+ ariths();
1348
1974
  lap(`congruences, ${insts.length} instances, ${w.models.length} models`);
1349
1975
  return insts;
1350
1976
  }
@@ -1482,7 +2108,7 @@ function attempt(p, fs, l, scheme, depth, lemmas, level, pool) {
1482
2108
  }
1483
2109
  // The scheme's nodes: the law's, then each companion's (a mutual induction), each its own models. A use of member i
1484
2110
  // 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) }))];
2111
+ const members = [{ law: l, on: scheme?.on, uses: scheme?.uses ?? [], pool, rank: scheme?.rank, shapes: scheme?.shapes }, ...(scheme?.companions ?? []).map(c => ({ ...c, pool: modelsFor(p, c.law, pool.length, 5) }))];
1486
2112
  const concls = members.map(mb => encY(p, mb.law.holds, new Map()));
1487
2113
  const base = specs.length, title = `${l.name}${scheme ? ` (${scheme.name})` : ''}`;
1488
2114
  varSorts = new Map(members.flatMap(mb => mb.law.vars.flatMap(v => (v.sort instanceof Data ? [[v.name, v.sort]] : []))));
@@ -1492,7 +2118,7 @@ function attempt(p, fs, l, scheme, depth, lemmas, level, pool) {
1492
2118
  for (const [mi, mb] of members.entries()) {
1493
2119
  const models = mb.pool.slice(0, 24), concl = concls[mi], name = mi === 0 ? title : `${title}, at ${mb.on.d.name}`;
1494
2120
  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)] : [];
2121
+ const shapesAt = mb.rank ? rankShapes(mb.shapes ?? [], mb.rank, mb.uses) : mb.on && mb.on.d !== 'nat' ? [ev(mb.on.v)] : [];
1496
2122
  const ihConcls = ih.map(useConcl);
1497
2123
  // Uses of the lemmas, where a lemma's largest read matches a read of this goal (unfolded), its key up to the
1498
2124
  // equalities the unfolded definitions give (any branch); twice over, the uses' own conclusions read the second time.
@@ -1538,8 +2164,10 @@ function attempt(p, fs, l, scheme, depth, lemmas, level, pool) {
1538
2164
  const offsets = plans.map((_, i) => base + plans.slice(0, i).reduce((n, q) => n + q.length, 0));
1539
2165
  for (const [mi, plan] of plans.entries()) {
1540
2166
  const mb = members[mi], size = !mb.on ? k(0) : mb.on.d === 'nat' ? ev(mb.on.v) : get(sizeMap(mb.on.d.name), ev(mb.on.v));
2167
+ // (level, the measure or the size, 0 or 1), the measure's place padded to the program's widest measure
2168
+ const comps = mb.rank ?? [size], mid = [...comps, ...Array.from({ length: rankWidth(p) - comps.length }, () => k(0))];
1541
2169
  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)) });
2170
+ specs.push({ ...n, rank: [k(level), ...mid, k(sub)], uses: cases ? cases.map((g, i) => ({ node: offsets[mi] + 1 + i, subst: {}, guard: g })) : n.uses.map(u => (u.node < 0 ? { ...u, node: offsets[-1 - u.node] } : u)) });
1543
2171
  }
1544
2172
  const name = title;
1545
2173
  // Each attempt within its own time (a goal past it is left unknown, and the attempt fails).
@@ -1613,6 +2241,12 @@ function reached(p, fs, insts, from, ms) {
1613
2241
  return [get(name.slice(3), keyOf(q.at))];
1614
2242
  if (name.startsWith('ctor '))
1615
2243
  return [get(name.slice(5), keyOf(q.at))];
2244
+ if (name.startsWith('arith ')) {
2245
+ const k2 = Number(name.slice(6));
2246
+ return [get(divMap(k2), q.at[0]), get(modMap(k2), q.at[0])];
2247
+ }
2248
+ if (name === 'zcode')
2249
+ return [get(posMap, q.at[0]), get(negMap, q.at[0])];
1616
2250
  if (name.startsWith('shape ') || name.startsWith('eta ')) {
1617
2251
  const d = name.split(' ')[1];
1618
2252
  return [get(tagMap(d), q.at[0]), get(sizeMap(d), q.at[0]), ...p.datas.filter(x => x.name === d).flatMap(x => x.ctors.flatMap((c, j) => c.fields.map((_, i) => get(fieldMap(x, j, i), q.at[0]))))];
@@ -1817,10 +2451,14 @@ function modelsFor(p, l, n, seed) {
1817
2451
  });
1818
2452
  }
1819
2453
  // Prove a law with the lemmas given (each proven, with its nodes), trying each scheme at each depth.
1820
- function proveWith(p, fs, l, lemmas, level, opts, until, per, depths) {
2454
+ // `without`: 'plain', the law with no induction where no scheme applies; 'cases', no induction, then by the cases of
2455
+ // each variable (a natural's zero and successor, a datatype's constructors), none of them using the law again.
2456
+ function proveWith(p, fs, l, lemmas, level, opts, until, per, depths, without) {
1821
2457
  const models = modelsFor(p, l, 240, opts.seed ?? 3);
1822
2458
  let tried = 0, last;
1823
- const ss = opts.induction === false ? [undefined] : schemes(p, l);
2459
+ const cases = l.vars.flatMap((v) => (v.sort === 'nat' || v.sort instanceof Data ? [{ name: `cases of ${v.name}`, on: { v: v.name, d: v.sort }, uses: [] }] : []));
2460
+ const found = opts.induction === false || without === 'cases' ? [] : schemes(p, l);
2461
+ const ss = opts.induction === false ? [undefined] : without === 'cases' ? [undefined, ...cases] : without === 'plain' && !found.length ? [undefined] : found;
1824
2462
  for (const depth of depths.filter(d => d <= (opts.depth ?? 3)))
1825
2463
  for (const s of ss) {
1826
2464
  if (clock() > until)
@@ -1829,7 +2467,7 @@ function proveWith(p, fs, l, lemmas, level, opts, until, per, depths) {
1829
2467
  const { verdict, specs, main } = (attemptMs.ms = Math.min(per * (opts.patience ?? 1), until - clock()), attempt(p, fs, l, s, depth, lemmas, level, models));
1830
2468
  last = verdict;
1831
2469
  if (verdict.proven && verdict.checked)
1832
- return { out: { law: l, spec: specs[main], nodes: specs }, verdict, scheme: s?.name, tried };
2470
+ return { out: { law: l, spec: specs[main], nodes: specs }, verdict, scheme: s?.name ?? (without ? 'no induction' : undefined), tried };
1833
2471
  }
1834
2472
  return { tried, last };
1835
2473
  }
@@ -2005,8 +2643,10 @@ export function conjectures(p, l, max = 24, maxSize = 6) {
2005
2643
  const kept = [];
2006
2644
  // The definitions, unfolded at a constructor: f(.., C(x, y), ..) → its case's body, where that is a plain term.
2007
2645
  for (const f of fnsUsed) {
2008
- const body = f.body(), di = f.decreases;
2009
- if (body.op !== 'match' || di === undefined || body.of.op !== 'var' || body.of.name !== `${f.name}.${f.params[di].name}`)
2646
+ // (a structural function's decreasing parameter, a well-founded one's the body matches on first)
2647
+ const body = f.body(), top = body.op === 'match' && body.of.op === 'var' ? f.params.findIndex(q => `${f.name}.${q.name}` === body.of.name) : -1;
2648
+ const di = f.decreases ?? (f.measure ? top : -1);
2649
+ if (body.op !== 'match' || di < 0 || di !== top)
2010
2650
  continue;
2011
2651
  const d = f.params[di].sort;
2012
2652
  body.cases.forEach((c, j) => {
@@ -2109,9 +2749,21 @@ export const showX = (x) => {
2109
2749
  case 'min':
2110
2750
  case 'max': return `${x.op}(${showX(x.a)}, ${showX(x.b)})`;
2111
2751
  case 'mul': return `${x.k}·${showX(x.a)}`;
2752
+ case 'prod': return `${showX(x.a)}·${showX(x.b)}`;
2753
+ case 'mod':
2754
+ case 'imod': return `${showX(x.a)} mod ${x.k}`;
2755
+ case 'div':
2756
+ case 'idiv': return `${showX(x.a)} div ${x.k}`;
2757
+ case 'int': return String(x.value);
2758
+ case 'iadd': return `${showX(x.a)} + ${showX(x.b)}`;
2759
+ case 'isub': return `${showX(x.a)} − ${showX(x.b)}`;
2760
+ case 'ineg': return `−${showX(x.a)}`;
2761
+ case 'imul': return `${x.k}·${showX(x.a)}`;
2762
+ case 'ofnat': return showX(x.a);
2112
2763
  case 'if': return `if ${showY(x.c)} then ${showX(x.then)} else ${showX(x.else)}`;
2113
2764
  case 'bool': return showY(x.c);
2114
2765
  case 'apply': return `${x.param}(${showX(x.arg)})`;
2766
+ case 'size': return `size(${showX(x.of)})`;
2115
2767
  case 'call': return `${x.fn}(${x.args.map(showX).join(', ')})`;
2116
2768
  case 'ctor': {
2117
2769
  const d = [...fns.values()].length >= 0 ? x.data : x.data;
@@ -2126,6 +2778,8 @@ export const showY = (y) => {
2126
2778
  case '<':
2127
2779
  case '<=':
2128
2780
  case '=': return `${showX(y.a)} ${y.op} ${showX(y.b)}`;
2781
+ case 'i<':
2782
+ case 'i<=': return `${showX(y.a)} ${y.op.slice(1)} ${showX(y.b)}`;
2129
2783
  case 'and': return y.xs.map(showY).join(' and ');
2130
2784
  case 'or': return y.xs.map(showY).join(' or ');
2131
2785
  case 'not': return `not (${showY(y.x)})`;
@@ -2136,21 +2790,49 @@ export const showY = (y) => {
2136
2790
  // Prove a law over datatypes: refuted by a random value where one breaks it; else proven by a cycle the search finds,
2137
2791
  // with lemmas found from the values when the law alone isn't (`lemmas: false` turns that off; an array gives lemmas to
2138
2792
  // prove and use instead, which the corpus never does).
2139
- export function proveData(l, opts = {}) { return counted(dataRate, () => proveDataCounted(l, opts)); }
2793
+ export function proveData(l, opts = {}) { return counted(dataRate, () => bounded(() => proveDataCounted(l, opts))); }
2140
2794
  function proveDataCounted(l, opts) {
2141
- const t0 = performance.now(), w0 = work.n, until = clock() + (opts.ms ?? 120_000);
2142
- const p = programOf([l, ...(Array.isArray(opts.lemmas) ? opts.lemmas : [])]), fs = factsOf(p);
2795
+ const t0 = performance.now(), until = clock() + (opts.ms ?? 120_000);
2796
+ // Where the law calls a non-recursive function, or reaches a recursive one that agrees with a built-in (core/transfer.ts):
2797
+ // the law alone at one unfolding first, then the law said through them, then the rest of the search on the law.
2798
+ const plan = opts.transfer === false || opts.induction === false || Array.isArray(opts.lemmas) ? undefined : transferPlan(l);
2799
+ if (!plan)
2800
+ return searchIn(l, opts, until).verdict;
2801
+ const quick = searchIn(l, opts, until, undefined, 'first').verdict;
2802
+ if (quick.proven || quick.counterexample)
2803
+ return quick;
2804
+ // (a law said through its definitions past what the encoding's terms take is left as stated)
2805
+ let through;
2806
+ try {
2807
+ through = proveThrough(l, plan, opts, until);
2808
+ }
2809
+ catch (e) {
2810
+ if (!(e instanceof RangeError))
2811
+ throw e;
2812
+ }
2813
+ if (through?.proven)
2814
+ return { ...through, ms: Math.round(performance.now() - t0) };
2815
+ const rest = searchIn(l, opts, until, undefined, 'rest').verdict;
2816
+ return { ...rest, tried: rest.tried + quick.tried + (through?.tried ?? 0), ms: Math.round(performance.now() - t0), ...(rest.goal || rest.proven ? {} : { goal: quick.goal, stuck: quick.stuck }) };
2817
+ }
2818
+ export function searchIn(l, opts, until, ctx, phase = 'all') {
2819
+ const t0 = performance.now(), w0 = work.n;
2820
+ const p = ctx?.p ?? programOf([l, ...(Array.isArray(opts.lemmas) ? opts.lemmas : [])]), fs = ctx?.fs ?? factsOf(p);
2821
+ const base = ctx?.base ?? [], lv = ctx?.level ?? 0;
2822
+ let out;
2143
2823
  const done = (v) => {
2144
2824
  if (dataTrace)
2145
2825
  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) };
2826
+ return { verdict: { law: l.name, ...v, ms: Math.round(performance.now() - t0) }, out };
2147
2827
  };
2148
- const cex = refute(l, opts.samples ?? 400);
2149
- if (cex)
2150
- return done({ proven: false, checked: false, counterexample: cex, lemmas: [], tried: 0 });
2828
+ if (phase !== 'rest') {
2829
+ const cex = refute(l, opts.samples ?? 400);
2830
+ if (cex)
2831
+ return done({ proven: false, checked: false, counterexample: cex, lemmas: [], tried: 0 });
2832
+ }
2151
2833
  // The law alone at one unfolding; then lemmas at one unfolding each (the law tried again after each its goal
2152
- // matches); then the law at two; then lemmas at two; last the law at three. A lemma is named in the verdict when the
2153
- // proof uses it.
2834
+ // matches); then the law at two; then lemmas at two; last the law at three, and without induction (by cases). A lemma
2835
+ // is named in the verdict when the proof uses it.
2154
2836
  let tried = 0;
2155
2837
  const used = (r, lemmas) => {
2156
2838
  const nodes = r.nodes, seen = new Set(), todo = [nodes.indexOf(r.spec)];
@@ -2164,38 +2846,49 @@ function proveDataCounted(l, opts) {
2164
2846
  }
2165
2847
  return lemmas.filter(q => seen.has(nodes.indexOf(nodes.find(n => n.name === q.spec.name)))).map(q => q.law.name);
2166
2848
  };
2167
- const ok = (r, lemmas, prog = p) => (tried += r.tried, r.out ? done({ proven: true, checked: true, proof: r.verdict, lemmas: used(r.out, lemmas), scheme: r.scheme, tried, program: prog, node: r.out.nodes.indexOf(r.out.spec) }) : undefined);
2168
- const first = proveWith(p, fs, l, [], 1, opts, until, 6000, [1]);
2169
- const one = ok(first, []);
2849
+ const ok = (r, lemmas, prog = p) => (tried += r.tried, r.out ? (out = r.out, done({ proven: true, checked: true, proof: r.verdict, lemmas: used(r.out, lemmas), scheme: r.scheme, tried, program: prog, node: r.out.nodes.indexOf(r.out.spec) })) : undefined);
2850
+ // A law that calls no function (arithmetic alone: an integer's, a remainder's) has nothing to induct on: its cases.
2851
+ if (phase !== 'rest' && opts.induction !== false && !JSON.stringify(l.holds).includes('"op":"call"')) {
2852
+ const c = ok(proveWith(p, fs, l, base, lv + 1, opts, until, 3000, [1, 2], 'cases'), base);
2853
+ if (c)
2854
+ return c;
2855
+ }
2856
+ const first = phase === 'rest' ? undefined : proveWith(p, fs, l, base, lv + 1, opts, until, 6000, [1], 'plain');
2857
+ const one = first && ok(first, base);
2170
2858
  if (one)
2171
2859
  return one;
2860
+ if (phase === 'first')
2861
+ return done({ proven: false, checked: false, lemmas: [], tried, ...stuckOf(first.last) });
2172
2862
  const without = opts.lemmas === false || opts.induction === false;
2173
2863
  if (without)
2174
- return ok(proveWith(p, fs, l, [], 1, opts, until, 20000, [2, 3]), []) ?? done({ proven: false, checked: false, lemmas: [], tried, ...stuckOf(first.last) });
2864
+ return ok(proveWith(p, fs, l, base, lv + 1, opts, until, 20000, [2, 3]), base) ?? (opts.induction === false ? undefined : ok(proveWith(p, fs, l, base, lv + 1, opts, until, 3000, [1, 2], 'cases'), base)) ?? done({ proven: false, checked: false, lemmas: [], tried, ...stuckOf(first?.last) });
2175
2865
  const cands = Array.isArray(opts.lemmas) ? opts.lemmas : conjectures(p, l);
2176
- const p2 = programOf([l, ...cands]), fs2 = factsOf(p2);
2866
+ const p2 = ctx ? p : programOf([l, ...cands]), fs2 = ctx ? fs : factsOf(p2);
2177
2867
  // 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) => {
2868
+ const goalOf = (law, uses, on, concls, shapes) => {
2179
2869
  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: [] }) };
2870
+ return { from: [concl, ihc], insts: instancesOf(p2, fs2, { roots: [concl], others: ihc, depth: 2, shapesAt: shapes ?? (on && on.d !== 'nat' ? [ev(on.v)] : []), etaAt: [], models: [] }) };
2181
2871
  };
2182
2872
  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));
2873
+ const all = [{ law: l, uses: s.uses, on: s.on, shapes: s.shapes }, ...(s.companions ?? [])], concls = all.map(m => encY(p2, m.law.holds, new Map()));
2874
+ return all.map((m, i) => goalOf(m.law, m.uses.map(u => ({ ...u, member: u.member ?? i })), m.on, concls, 'shapes' in m ? m.shapes : undefined));
2185
2875
  });
2186
2876
  const matchesGoal = (c) => goals.some(g => lemmaUsesFor(p2, fs2, [{ law: c, node: 0, concl: encY(p2, c.holds, new Map()) }], g.from, g.insts).length > 0);
2187
2877
  // Those the goal matches first, of those the ones of the law's shape (an implication for an implication) first.
2188
2878
  const implication = (y) => y.op === 'or' && y.xs.length === 2 && y.xs[0].op === 'not';
2189
2879
  const score = (c, hit) => (hit ? 2 : 0) + (implication(c.holds) === implication(l.holds) ? 1 : 0);
2880
+ if (dataTrace)
2881
+ console.error(`[data] ${l.name}: ${cands.length} conjectures at ${Math.round(clock())} of ${Math.round(until)}`);
2190
2882
  const ranked = cands.map(c => ({ c, hit: matchesGoal(c) })).sort((a, b) => score(b.c, b.hit) - score(a.c, a.hit));
2191
- const proven = [], failed = new Set();
2883
+ // The lemmas proven: those given (`base`) and those found, each found one a level above the last.
2884
+ const proven = [...base], failed = new Set(), level = () => lv + 1 + proven.length - base.length;
2192
2885
  const lemmasAt = (depth) => {
2193
2886
  for (const { c, hit } of ranked) {
2194
2887
  if (clock() > until)
2195
2888
  return undefined;
2196
2889
  if (proven.some(q => q.law === c) || (depth === 2 && !failed.has(c)) || refute(c, 200))
2197
2890
  continue;
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]);
2891
+ const r = proveWith(p2, fs2, c, proven, level(), opts, Math.min(until, clock() + (depth === 1 ? 8000 : 12000) * (opts.patience ?? 1)), 5000, [depth]);
2199
2892
  tried += r.tried;
2200
2893
  if (!r.out) {
2201
2894
  failed.add(c);
@@ -2205,7 +2898,7 @@ function proveDataCounted(l, opts) {
2205
2898
  proven.push(r.out);
2206
2899
  if (!hit && implication(c.holds) !== implication(l.holds))
2207
2900
  continue;
2208
- const again = ok(proveWith(p2, fs2, l, proven, 1 + proven.length, opts, until, 6000, depth === 1 ? [1] : [1, 2]), proven, p2);
2901
+ const again = ok(proveWith(p2, fs2, l, proven, level(), opts, until, 6000, depth === 1 ? [1] : [1, 2]), proven, p2);
2209
2902
  if (again)
2210
2903
  return again;
2211
2904
  }
@@ -2213,19 +2906,23 @@ function proveDataCounted(l, opts) {
2213
2906
  };
2214
2907
  const steps = [
2215
2908
  () => lemmasAt(1),
2216
- () => ok(proveWith(p, fs, l, [], 1, opts, until, 30000, [2]), []),
2217
- () => (proven.length ? ok(proveWith(p2, fs2, l, proven, 1 + proven.length, opts, until, 30000, [2]), proven, p2) : undefined),
2909
+ () => ok(proveWith(p, fs, l, base, lv + 1, opts, until, 30000, [2]), base),
2910
+ () => (proven.length > base.length ? ok(proveWith(p2, fs2, l, proven, level(), opts, until, 30000, [2]), proven, p2) : undefined),
2218
2911
  () => lemmasAt(2),
2219
- () => ok(proveWith(p2, fs2, l, proven, 1 + proven.length, opts, until, 12000, [3]), proven, p2),
2912
+ () => ok(proveWith(p2, fs2, l, proven, level(), opts, until, 12000, [3]), proven, p2),
2913
+ // No induction: the law by the cases of a variable (or none), its definitions unfolded.
2914
+ () => ok(proveWith(p, fs, l, base, lv + 1, opts, until, 3000, [1, 2], 'cases'), base),
2220
2915
  ];
2221
- for (const step of steps) {
2916
+ for (const [i, step] of steps.entries()) {
2222
2917
  if (clock() > until)
2223
2918
  break;
2224
- const out = step();
2225
- if (out)
2226
- return out;
2919
+ if (dataTrace)
2920
+ console.error(`[data] ${l.name}: step ${i} at ${Math.round(clock())} of ${Math.round(until)}`);
2921
+ const r = step();
2922
+ if (r)
2923
+ return r;
2227
2924
  }
2228
- return done({ proven: false, checked: false, lemmas: [], tried, ...stuckOf(first.last) });
2925
+ return done({ proven: false, checked: false, lemmas: [], tried, ...stuckOf(first?.last) });
2229
2926
  }
2230
2927
  const stuckOf = (v) => (v && !v.proven ? { goal: v.goal, stuck: v.stuck } : v && !v.checked ? { goal: 'check', stuck: v.why } : {});
2231
2928
  // The law with no cycle: its definitions unfolded, the kernel's prover alone (what the library proves without the search).