gdp-wired 0.19.0 → 0.21.0

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