gdp-wired 0.16.0 → 0.18.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.
@@ -0,0 +1,899 @@
1
+ import { fnValue, pairedArgs } from "./machine.js";
2
+ import { check, livenessParts, loopLiveness, loopMachine, loopObligations, substC, substE } from "../kernel/kernel.js";
3
+ import { factsAvoid, lexLt } from "../kernel/cyclic.js";
4
+ import { proveCyclic } from "./cyclic.js";
5
+ import { solve } from "./solver.js";
6
+ import { grow, proveLaws, proveStepLaws, withDeadline } from "../kernel/prove.js";
7
+ import { inferLaws } from "./infer.js";
8
+ // --- terms ----------------------------------------------------------------------------------------------------------
9
+ const v = (name) => ({ op: 'var', name }), k = (value) => ({ op: 'const', value });
10
+ const add = (a, b) => ({ op: 'add', a, b }), sub = (a, b) => ({ op: 'sub', a, b });
11
+ const get = (map, key) => ({ op: 'get', map, key });
12
+ const le = (a, b) => ({ op: '<=', a, b }), eqC = (a, b) => ({ op: '=', a, b });
13
+ const and = (...xs) => (xs.length === 1 ? xs[0] : { op: 'and', xs });
14
+ const yes = le(k(0), k(0));
15
+ const vs = (s) => ({ vars: s, maps: {}, flags: {} });
16
+ const id = (t) => JSON.stringify(t);
17
+ export const pairE = (a, b) => { const s = add(a, b); return add({ op: 'prod', a: s, b: s }, b); };
18
+ export const argsKey = (args) => args.slice(1).reduce(pairE, args[0]);
19
+ // Σ coef·x + c as a term over the naturals: the negative part subtracted (truncated, as the kernel reads sub).
20
+ export const linE = (coef, c) => {
21
+ const pos = [], neg = [];
22
+ if (c > 0)
23
+ pos.push(k(c));
24
+ for (const [x, a] of Object.entries(coef))
25
+ if (a)
26
+ (a > 0 ? pos : neg).push(Math.abs(a) === 1 ? v(x) : { op: 'mul', k: Math.abs(a), a: v(x) });
27
+ if (c < 0)
28
+ neg.push(k(-c));
29
+ const sum = (xs) => xs.reduce((s, x) => add(s, x));
30
+ return !pos.length ? k(0) : !neg.length ? sum(pos) : sub(sum(pos), sum(neg));
31
+ };
32
+ export const showE = (e) => {
33
+ switch (e.op) {
34
+ case 'var': return e.name;
35
+ case 'const': return String(e.value);
36
+ case 'add': return `${showE(e.a)} + ${showE(e.b)}`;
37
+ case 'sub': return `${showE(e.a)} − ${e.b.op === 'add' || e.b.op === 'sub' ? `(${showE(e.b)})` : showE(e.b)}`;
38
+ case 'mul': return `${e.k}·${e.a.op === 'var' || e.a.op === 'const' ? showE(e.a) : `(${showE(e.a)})`}`;
39
+ case 'prod': return `(${showE(e.a)})·(${showE(e.b)})`;
40
+ case 'min':
41
+ case 'max': return `${e.op}(${showE(e.a)}, ${showE(e.b)})`;
42
+ case 'if': return `if ${showC(e.c)} then ${showE(e.then)} else ${showE(e.else)}`;
43
+ case 'get': {
44
+ const as = pairedArgs(e.key, 2), as3 = as && pairedArgs(e.key, 3);
45
+ return `${e.map}(${(as3 ?? as ?? [e.key]).map(showE).join(', ')})`;
46
+ }
47
+ }
48
+ };
49
+ export const showC = (c) => {
50
+ switch (c.op) {
51
+ case '<':
52
+ case '<=':
53
+ case '=': return `${showE(c.a)} ${c.op === '<=' ? '≤' : c.op} ${showE(c.b)}`;
54
+ case 'and': return c.xs.map(x => (x.op === 'or' ? `(${showC(x)})` : showC(x))).join(' ∧ ');
55
+ case 'or': return c.xs.map(showC).join(' ∨ ');
56
+ case 'not': return `¬(${showC(c.x)})`;
57
+ case 'flag': return `?${c.name}`;
58
+ case 'all': return `∀${c.key}. ${showC(c.c)}`;
59
+ }
60
+ };
61
+ const walk = (t, f) => {
62
+ if (Array.isArray(t)) {
63
+ for (const x of t)
64
+ walk(x, f);
65
+ return;
66
+ }
67
+ if (!t || typeof t !== 'object')
68
+ return;
69
+ const o = t;
70
+ f(o);
71
+ for (const x of Object.values(o))
72
+ walk(x, f);
73
+ };
74
+ const readsOf = (t) => { const out = []; walk(t, o => { if (o.op === 'get')
75
+ out.push(o); }); return out; };
76
+ const varsOf = (t) => { const out = new Set(); walk(t, o => { if (o.op === 'var')
77
+ out.add(o.name); }); return [...out].sort(); };
78
+ const constsOf = (t) => { const out = []; walk(t, o => { if (o.op === 'const')
79
+ out.push(o.value); }); return out; };
80
+ const patArg = (e, keys) => (e.op === 'var' && keys.includes(e.name) ? e.name : e.op === 'add' && e.a.op === 'var' && keys.includes(e.a.name) && e.b.op === 'const' ? e.a.name : undefined);
81
+ function equations(facts) {
82
+ const eqs = new Map(), arity = new Map();
83
+ for (const [i, f] of facts.entries()) {
84
+ for (const part of f.holds.op === 'and' ? f.holds.xs : [f.holds]) {
85
+ if (part.op !== '=')
86
+ continue;
87
+ for (const [lhs, body] of [[part.a, part.b], [part.b, part.a]]) {
88
+ if (lhs.op !== 'get')
89
+ continue;
90
+ let n = 4;
91
+ while (n > 1 && !pairedArgs(lhs.key, n))
92
+ n--;
93
+ const pat = pairedArgs(lhs.key, n), bound = pat.map(a => patArg(a, f.keys));
94
+ if (!pat.every((a, j) => bound[j] || a.op === 'const') || new Set(bound.filter(Boolean)).size !== bound.filter(Boolean).length)
95
+ continue;
96
+ if (!varsOf(body).every(x => bound.includes(x)))
97
+ continue;
98
+ if ((arity.get(lhs.map) ?? n) !== n)
99
+ continue;
100
+ arity.set(lhs.map, n);
101
+ eqs.set(lhs.map, [...(eqs.get(lhs.map) ?? []), { fact: i, map: lhs.map, pat, body }]);
102
+ break;
103
+ }
104
+ }
105
+ }
106
+ return { eqs, arity };
107
+ }
108
+ const argsOf = (cx, r) => pairedArgs(r.key, cx.arity.get(r.map) ?? 1) ?? [r.key];
109
+ // The equation's bindings at numbers, or undefined where its pattern doesn't match.
110
+ const matchNums = (e, xs) => {
111
+ const out = {};
112
+ for (const [j, p] of e.pat.entries()) {
113
+ const x = xs[j];
114
+ if (p.op === 'const') {
115
+ if (p.value !== x)
116
+ return undefined;
117
+ }
118
+ else if (p.op === 'var')
119
+ out[p.name] = x;
120
+ else if (p.op === 'add' && p.a.op === 'var' && p.b.op === 'const') {
121
+ if (x < p.b.value)
122
+ return undefined;
123
+ out[p.a.name] = x - p.b.value;
124
+ }
125
+ }
126
+ return out;
127
+ };
128
+ function callF(cx, map, xs) {
129
+ const key = `${map}(${xs.join(',')})`, got = cx.memo.get(key);
130
+ if (got !== undefined)
131
+ return got;
132
+ const f = cx.values[map];
133
+ if (f) {
134
+ let out;
135
+ try {
136
+ out = f(...xs);
137
+ }
138
+ catch {
139
+ out = undefined;
140
+ }
141
+ if (out === undefined || !Number.isSafeInteger(out) || out < 0)
142
+ return undefined;
143
+ cx.memo.set(key, out);
144
+ return out;
145
+ }
146
+ if (cx.fuel <= 0 || cx.depth >= 400)
147
+ return undefined;
148
+ for (const e of cx.eqs.get(map) ?? []) {
149
+ const env = matchNums(e, xs);
150
+ if (!env)
151
+ continue;
152
+ cx.fuel--;
153
+ cx.depth++;
154
+ const out = ev(cx, e.body, env);
155
+ cx.depth--;
156
+ if (out !== undefined)
157
+ cx.memo.set(key, out);
158
+ return out;
159
+ }
160
+ return undefined;
161
+ }
162
+ function ev(cx, e, env, rec) {
163
+ const two = (a, b, f) => { const x = ev(cx, a, env, rec), y = ev(cx, b, env, rec); return x === undefined || y === undefined ? undefined : f(x, y); };
164
+ switch (e.op) {
165
+ case 'var': return env[e.name];
166
+ case 'const': return e.value;
167
+ case 'add': return two(e.a, e.b, (x, y) => x + y);
168
+ case 'sub': return two(e.a, e.b, (x, y) => Math.max(0, x - y));
169
+ case 'min': return two(e.a, e.b, Math.min);
170
+ case 'max': return two(e.a, e.b, Math.max);
171
+ case 'prod': return two(e.a, e.b, (x, y) => x * y);
172
+ case 'mul': {
173
+ const x = ev(cx, e.a, env, rec);
174
+ return x === undefined ? undefined : e.k * x;
175
+ }
176
+ case 'if': {
177
+ const c = evC(cx, e.c, env, rec);
178
+ return c === undefined ? undefined : ev(cx, c ? e.then : e.else, env, rec);
179
+ }
180
+ case 'get': {
181
+ const xs = [];
182
+ for (const a of argsOf(cx, e)) {
183
+ const x = ev(cx, a, env, rec);
184
+ if (x === undefined)
185
+ return undefined;
186
+ xs.push(x);
187
+ }
188
+ const out = callF(cx, e.map, xs);
189
+ if (rec) {
190
+ rec.out.add(`${e.map}(${xs.join(',')})`);
191
+ if (rec.depth > 0)
192
+ for (const q of cx.eqs.get(e.map) ?? []) {
193
+ const at = matchNums(q, xs);
194
+ if (at) {
195
+ ev(cx, q.body, at, { out: rec.out, depth: rec.depth - 1 });
196
+ break;
197
+ }
198
+ }
199
+ }
200
+ return out;
201
+ }
202
+ }
203
+ }
204
+ function evC(cx, c, env, rec) {
205
+ switch (c.op) {
206
+ case '<':
207
+ case '<=':
208
+ case '=': {
209
+ const a = ev(cx, c.a, env, rec), b = ev(cx, c.b, env, rec);
210
+ return a === undefined || b === undefined ? undefined : c.op === '<' ? a < b : c.op === '<=' ? a <= b : a === b;
211
+ }
212
+ case 'and': {
213
+ let out = true;
214
+ for (const x of c.xs) {
215
+ const y = evC(cx, x, env, rec);
216
+ if (y === false)
217
+ return false;
218
+ if (y === undefined)
219
+ out = undefined;
220
+ }
221
+ return out;
222
+ }
223
+ case 'or': {
224
+ let out = false;
225
+ for (const x of c.xs) {
226
+ const y = evC(cx, x, env, rec);
227
+ if (y === true)
228
+ return true;
229
+ if (y === undefined)
230
+ out = undefined;
231
+ }
232
+ return out;
233
+ }
234
+ case 'not': {
235
+ const y = evC(cx, c.x, env, rec);
236
+ return y === undefined ? undefined : !y;
237
+ }
238
+ case 'flag':
239
+ case 'all': return undefined;
240
+ }
241
+ }
242
+ const allTrue = (cx, cs, env) => evC(cx, and(yes, ...cs), env);
243
+ // The fact's keys at a read's arguments, for an equation of it (a key plus a constant at an argument u + c is u, at any
244
+ // other term t is t − c).
245
+ const bindAt = (facts, e, args) => {
246
+ const at = {};
247
+ for (const [j, p] of e.pat.entries()) {
248
+ const t = args[j];
249
+ if (p.op === 'const') {
250
+ if (id(t) !== id(p))
251
+ return undefined;
252
+ }
253
+ else if (p.op === 'var')
254
+ at[p.name] = t;
255
+ else if (p.op === 'add' && p.a.op === 'var' && p.b.op === 'const')
256
+ at[p.a.name] = t.op === 'add' && t.b.op === 'const' && t.b.value === p.b.value ? t.a : sub(t, p.b);
257
+ }
258
+ const keys = facts[e.fact].keys;
259
+ return keys.every(x => x in at) ? keys.map(x => at[x]) : undefined;
260
+ };
261
+ function instancesFor(facts, cx, n, depth) {
262
+ const insts = facts.flatMap((f, i) => (f.keys.length ? [] : [{ fact: i, at: [] }]));
263
+ const seen = new Set(insts.map(id)), readSeen = new Set(), reads = [];
264
+ // A plain fact about every key of one function, read at its key alone: at each key the goal reads that function at.
265
+ const single = facts.flatMap((f, i) => (f.keys.length === 1 && !cx.eqs.size ? readsOf(f.holds).filter(r => id(r.key) === id(v(f.keys[0]))).map(r => ({ fact: i, map: r.map })) : []));
266
+ let front = readsOf([n.hyps, n.concl]);
267
+ for (let d = 0; d <= depth; d++) {
268
+ const next = [];
269
+ for (const r of front) {
270
+ if (readSeen.has(id(r)))
271
+ continue;
272
+ readSeen.add(id(r));
273
+ reads.push(r);
274
+ if (d === depth)
275
+ continue;
276
+ for (const q of cx.eqs.get(r.map) ?? []) {
277
+ const at = bindAt(facts, q, argsOf(cx, r));
278
+ if (!at)
279
+ continue;
280
+ const p = { fact: q.fact, at };
281
+ if (!seen.has(id(p))) {
282
+ seen.add(id(p));
283
+ insts.push(p);
284
+ }
285
+ next.push(...readsOf(substE(q.body, vs(Object.fromEntries(facts[q.fact].keys.map((x, j) => [x, at[j]]))))));
286
+ }
287
+ for (const s of single)
288
+ if (s.map === r.map) {
289
+ const p = { fact: s.fact, at: [r.key] };
290
+ if (!seen.has(id(p))) {
291
+ seen.add(id(p));
292
+ insts.push(p);
293
+ }
294
+ }
295
+ }
296
+ front = next;
297
+ }
298
+ return { insts, reads };
299
+ }
300
+ // Each node m used at the arguments of a call the goal reads of the function m's conclusion reads.
301
+ function usesFor(facts, cx, nodes, i, reads) {
302
+ const n = nodes[i], out = [], seen = new Set();
303
+ for (const [j, m] of nodes.entries()) {
304
+ if (m.level > n.level)
305
+ continue;
306
+ for (const mr of readsOf(m.concl)) {
307
+ const P = argsOf(cx, mr);
308
+ if (!P.some(p => p.op === 'var' && m.vars.includes(p.name)))
309
+ continue;
310
+ for (const r of reads) {
311
+ if (r.map !== mr.map)
312
+ continue;
313
+ const A = argsOf(cx, r);
314
+ if (A.length !== P.length)
315
+ continue;
316
+ const s = {};
317
+ let ok = true;
318
+ for (const [q, p] of P.entries()) {
319
+ if (p.op === 'var' && m.vars.includes(p.name)) {
320
+ if (s[p.name] && id(s[p.name]) !== id(A[q]))
321
+ ok = false;
322
+ else
323
+ s[p.name] = A[q];
324
+ }
325
+ else if (id(p) !== id(A[q]))
326
+ ok = false;
327
+ }
328
+ for (const x of m.vars)
329
+ if (!(x in s)) {
330
+ if (n.vars.includes(x))
331
+ s[x] = v(x);
332
+ else
333
+ ok = false;
334
+ }
335
+ if (!ok || (j === i && m.vars.every(x => id(s[x]) === id(v(x)))) || !factsAvoid(facts, s))
336
+ continue;
337
+ const c = { node: j, subst: s };
338
+ if (!seen.has(id(c))) {
339
+ seen.add(id(c));
340
+ out.push(c);
341
+ }
342
+ }
343
+ }
344
+ }
345
+ return out;
346
+ }
347
+ const grid = (names, bound) => {
348
+ const out = [], total = (bound + 1) ** names.length;
349
+ for (let i = 0; i < total; i++) {
350
+ let rest = i;
351
+ out.push(Object.fromEntries(names.map(x => { const d = rest % (bound + 1); rest = Math.floor(rest / (bound + 1)); return [x, d]; })));
352
+ }
353
+ return out;
354
+ };
355
+ const boundFor = (d, big) => (d === 0 ? 0 : Math.min([0, 400, 40, 12, 6][Math.min(d, 4)], Math.max(24, 2 * big + 20)));
356
+ function samplesOf(cx, n, depth, big) {
357
+ const ok = [];
358
+ for (const env of grid(n.vars, boundFor(n.vars.length, big))) {
359
+ cx.fuel = 20_000;
360
+ if (allTrue(cx, n.hyps, env) !== true)
361
+ continue;
362
+ const calls = new Set(), c = evC(cx, n.concl, env, { out: calls, depth });
363
+ if (c === undefined)
364
+ continue;
365
+ if (!c)
366
+ return { ok, broken: env };
367
+ ok.push({ env, calls });
368
+ }
369
+ return { ok };
370
+ }
371
+ const own = (nodes) => (n, j) => (j === 'b' ? `b:${n}` : `a:${n}:${nodes[n].vars[j]}`);
372
+ const shared = (_n, j) => (j === 'b' ? 'b:*' : `a:*:${j}`);
373
+ // r_n(σ) as a row over the LP's unknowns.
374
+ const rankRow = (n, nodes, s, name, scale = 1) => {
375
+ let out = { [name(n, 'b')]: scale };
376
+ for (const [j, x] of nodes[n].vars.entries())
377
+ if (s[x])
378
+ out = plus(out, { [name(n, j)]: scale * s[x] });
379
+ return out;
380
+ };
381
+ const plus = (a, b) => { const o = { ...a }; for (const [x, c] of Object.entries(b)) {
382
+ o[x] = (o[x] ?? 0) + c;
383
+ if (!o[x])
384
+ delete o[x];
385
+ } return o; };
386
+ const valueAt = (l, w) => Object.entries(l).reduce((s, [x, c]) => s + c * (x === '1' ? 1 : w[x] ?? 0), 0);
387
+ // An integer solution of every row (lin <= 0) of `must` and `pool`, the pool's rows added as they are violated. Its
388
+ // unknowns are bounded, the coefficients first to naturals (a rank with no subtraction, which no truncation bends), then
389
+ // either sign, each bound doubled up to 4 and the constants' first small, then up to the samples' size: the first found
390
+ // is small (x + y, not 33·x + y; total − cur, not 26 − cur), and a rank the samples' size bounds is left to the next
391
+ // component (two phases: (x, y)). A fit's coefficients (c:) go up to 128.
392
+ function lp(must, pool, until, scale = 1, signs = [true, false]) {
393
+ const unknowns = [...new Set([...must, ...pool].flatMap(l => Object.keys(l)).filter(x => x !== '1'))];
394
+ const fit = unknowns.some(x => x.startsWith('c:')), bounds = fit ? [1, 2, 4, 16, 128] : [1, 2, 4];
395
+ const stage = (natural, bound, wide) => {
396
+ const rows = [...must];
397
+ for (const x of unknowns) {
398
+ const b = x.startsWith('b:') ? (wide ? bound * scale : bound) : bound;
399
+ rows.push({ [x]: 1, 1: -b }, natural ? { [x]: -1 } : { [x]: -1, 1: -b });
400
+ }
401
+ for (let round = 0; round < 40 && performance.now() < until; round++) {
402
+ const out = solve(rows.map(lin => ({ lin, label: '' })), 4000);
403
+ if (out.kind !== 'sat')
404
+ break;
405
+ const bad = pool.filter(l => valueAt(l, out.witness) > 0).sort((a, b) => valueAt(b, out.witness) - valueAt(a, out.witness));
406
+ if (!bad.length)
407
+ return out.witness;
408
+ rows.push(...bad.slice(0, 6));
409
+ }
410
+ return undefined;
411
+ };
412
+ // The widest box first: where it has no solution, none of the smaller has.
413
+ if (!stage(signs.at(-1), bounds.at(-1), true))
414
+ return undefined;
415
+ for (const natural of signs)
416
+ for (const bound of bounds)
417
+ for (const wide of [false, true]) {
418
+ const w = stage(natural, bound, wide);
419
+ if (w)
420
+ return w;
421
+ }
422
+ return undefined;
423
+ }
424
+ // Lexicographic ranks over the edges (each from a node at a sample to a node at its arguments), every component >= 0
425
+ // at every sample: Bradley–Manna–Sipma's successive elimination, each component the sum of one solution per edge it can
426
+ // make drop. An edge no component makes drop is left (its use's guard keeps it where the rank drops, if anywhere).
427
+ function lexRanks(idx, nodes, samples, edges, name, until) {
428
+ const points = [...new Map(idx.flatMap(n => samples[n].map(s => { const l = rankRow(n, nodes, s.env, name, -1); return [id(l), l]; }))).values()];
429
+ const delta = (e) => plus(rankRow(e.to, nodes, e.t, name), rankRow(e.from, nodes, e.s, name, -1));
430
+ const scale = 1 + Math.max(0, ...idx.flatMap(n => samples[n].flatMap(s => Object.values(s.env))));
431
+ let left = [...new Map(edges.map(e => { const l = delta(e); return [id(l), l]; })).values()];
432
+ const comps = [];
433
+ while (left.length && comps.length < 4 && performance.now() < until) {
434
+ let sum = {};
435
+ const strict = (l) => valueAt(l, sum) < 0;
436
+ // One try per pattern of signs (which unknowns an edge raises and lowers): edges alike drop alike, mostly.
437
+ const tried = new Set(), sign = (l) => id(Object.keys(l).filter(x => x !== '1').sort().map(x => `${x}${Math.sign(l[x])}`));
438
+ for (const l of left) {
439
+ if (strict(l) || performance.now() > until || tried.has(sign(l)) || tried.size > 40)
440
+ continue;
441
+ tried.add(sign(l));
442
+ const w = lp([plus(l, { 1: 1 })], [...left, ...points], until, scale);
443
+ if (w)
444
+ sum = plus(sum, w);
445
+ }
446
+ if (!Object.keys(sum).length)
447
+ break;
448
+ // The component divided by its entries' common divisor (each edge's drop stays a positive integer).
449
+ const g = Object.values(sum).reduce((a, b) => gcd(a, Math.abs(b)), 0);
450
+ if (g > 1)
451
+ sum = Object.fromEntries(Object.entries(sum).map(([x, c]) => [x, c / g]));
452
+ comps.push(sum);
453
+ const kept = left.filter(l => !strict(l));
454
+ if (kept.length === left.length)
455
+ break;
456
+ left = kept;
457
+ }
458
+ return { comps, left: left.length };
459
+ }
460
+ const gcd = (a, b) => (b === 0 ? a : gcd(b, a % b));
461
+ const compE = (c, n, node, name) => linE(Object.fromEntries(node.vars.map((x, j) => [x, c[name(n, j)] ?? 0])), c[name(n, 'b')] ?? 0);
462
+ // Strongly connected components (Tarjan), each node's component's index.
463
+ function sccs(graph) {
464
+ const index = [], low = [], on = [], stack = [], comp = [];
465
+ let next = 0, count = 0;
466
+ const visit = (u) => {
467
+ index[u] = low[u] = next++;
468
+ stack.push(u);
469
+ on[u] = true;
470
+ for (const w of graph[u]) {
471
+ if (index[w] === undefined) {
472
+ visit(w);
473
+ low[u] = Math.min(low[u], low[w]);
474
+ }
475
+ else if (on[w])
476
+ low[u] = Math.min(low[u], index[w]);
477
+ }
478
+ if (low[u] === index[u]) {
479
+ let w;
480
+ do {
481
+ w = stack.pop();
482
+ on[w] = false;
483
+ comp[w] = count;
484
+ } while (w !== u);
485
+ count++;
486
+ }
487
+ };
488
+ for (let u = 0; u < graph.length; u++)
489
+ if (index[u] === undefined)
490
+ visit(u);
491
+ return comp;
492
+ }
493
+ function build(facts, cx, nodes, depth, big, until) {
494
+ const inst = nodes.map(n => instancesFor(facts, cx, n, depth));
495
+ const samples = [];
496
+ for (const n of nodes) {
497
+ const s = samplesOf(cx, n, depth, big);
498
+ if (s.broken)
499
+ return { broken: { node: n.name, env: s.broken } };
500
+ samples.push(s.ok);
501
+ }
502
+ const cands = nodes.map((_, i) => usesFor(facts, cx, nodes, i, inst[i].reads));
503
+ const edges = [], active = cands.map(cs => cs.map(() => false));
504
+ for (const [i, cs] of cands.entries())
505
+ for (const [u, c] of cs.entries()) {
506
+ const m = nodes[c.node], mr = readsOf(m.concl);
507
+ for (const s of samples[i]) {
508
+ cx.fuel = 20_000;
509
+ const t = {};
510
+ let ok = true;
511
+ for (const x of m.vars) {
512
+ const val = ev(cx, c.subst[x], s.env);
513
+ if (val === undefined) {
514
+ ok = false;
515
+ break;
516
+ }
517
+ t[x] = val;
518
+ }
519
+ if (!ok || allTrue(cx, m.hyps, t) !== true)
520
+ continue;
521
+ const called = mr.some(r => { const xs = argsOf(cx, r).map(a => ev(cx, a, t)); return xs.every(x => x !== undefined) && s.calls.has(`${r.map}(${xs.join(',')})`); });
522
+ // A node at the very point it is (a read of f((x − 1) + 1) at f(x)) is no back-edge.
523
+ if (!called || (c.node === i && m.vars.every(x => t[x] === s.env[x])))
524
+ continue;
525
+ active[i][u] = true;
526
+ edges.push({ from: i, to: c.node, s: s.env, t });
527
+ }
528
+ }
529
+ // Components of the uses: a use into a component below drops by the first part of the rank, the component's height;
530
+ // within a component, by the ranks the runs give (shared by its nodes first, then each its own).
531
+ const graph = nodes.map((_, i) => [...new Set(cands[i].filter((_, u) => active[i][u]).map(c => c.node))].filter(j => j !== i));
532
+ const comp = sccs(graph), height = [];
533
+ const heightOf = (c) => (height[c] ??= Math.max(0, ...nodes.flatMap((_, i) => (comp[i] === c ? graph[i].filter(j => comp[j] !== c).map(j => heightOf(comp[j]) + 1) : []))));
534
+ const parts = new Map();
535
+ for (const c of new Set(comp)) {
536
+ const idx = nodes.flatMap((_, i) => (comp[i] === c ? [i] : [])), within = edges.filter(e => comp[e.from] === c && comp[e.to] === c);
537
+ if (!within.length) {
538
+ for (const i of idx)
539
+ parts.set(i, []);
540
+ continue;
541
+ }
542
+ let best = { ...lexRanks(idx, nodes, samples, within, shared, until), name: shared };
543
+ if (best.left && idx.length > 1 && idx.length <= 4) {
544
+ const o = lexRanks(idx, nodes, samples, within, own(nodes), until);
545
+ if (o.left < best.left)
546
+ best = { ...o, name: own(nodes) };
547
+ }
548
+ for (const i of idx)
549
+ parts.set(i, best.comps.map(cp => compE(cp, i, nodes[i], best.name)));
550
+ }
551
+ const width = Math.max(0, ...[...parts.values()].map(p => p.length)), tiers = graph.some(g => g.length);
552
+ const ranks = nodes.map((_, i) => [...(tiers ? [k(heightOf(comp[i]))] : []), ...parts.get(i), ...Array.from({ length: width - parts.get(i).length }, () => k(0))]);
553
+ let uses = 0;
554
+ const specs = nodes.map((n, i) => ({
555
+ name: n.name, hyps: n.hyps, concl: n.concl, rank: ranks[i], insts: inst[i].insts,
556
+ uses: cands[i].filter((_, u) => active[i][u]).map(c => {
557
+ uses++;
558
+ const m = nodes[c.node], s = vs(c.subst);
559
+ return { node: c.node, subst: c.subst, guard: and(...m.hyps.map(h => substC(h, s)), lexLt(ranks[c.node].map(r => substE(r, s)), ranks[i])) };
560
+ }),
561
+ }));
562
+ return { specs, ranks, uses };
563
+ }
564
+ // Exact integer fits: Σ c·x + c0 = value at every sample (rows generated from the violated ones).
565
+ function fitExact(names, points, until) {
566
+ if (!points.length)
567
+ return undefined;
568
+ const row = (p, sign) => { const l = { 'b:c': sign, 1: -sign * p.value }; for (const x of names)
569
+ if (p.env[x])
570
+ l[`c:${x}`] = sign * p.env[x]; return l; };
571
+ const pool = points.flatMap(p => [row(p, 1), row(p, -1)]);
572
+ const w = lp(pool.slice(0, 2 * Math.min(points.length, names.length + 2)), pool, until, 1 + Math.max(...points.map(p => p.value)), [false]);
573
+ return w && { coef: Object.fromEntries(names.map(x => [x, w[`c:${x}`] ?? 0])), c: w['b:c'] ?? 0 };
574
+ }
575
+ // A claim's constants abstracted: each read whose argument is a constant read at a fresh variable there, the
576
+ // conclusion (an equation with that read on one side) fitted exactly to the values over the claim's variables and the
577
+ // new ones. The companion is below the claim (its rank's first component), which uses it at the constants.
578
+ function abstracted(cx, n, i, big, until) {
579
+ const c = n.concl;
580
+ if (c.op !== '=')
581
+ return undefined;
582
+ for (const [read, other] of [[c.a, c.b], [c.b, c.a]]) {
583
+ if (read.op !== 'get')
584
+ continue;
585
+ const args = argsOf(cx, read);
586
+ if (args.length < 2 || !args.some(a => a.op === 'const'))
587
+ continue;
588
+ const fresh = args.map((a, j) => (a.op === 'const' ? `${read.map}${j}$${i}` : undefined));
589
+ const general = get(read.map, argsKey(args.map((a, j) => (fresh[j] ? v(fresh[j]) : a))));
590
+ const names = [...n.vars, ...fresh.filter((x) => !!x)].sort();
591
+ if (!varsOf(other).every(x => n.vars.includes(x)))
592
+ continue;
593
+ const points = [];
594
+ for (const env of grid(names, boundFor(names.length, big))) {
595
+ cx.fuel = 20_000;
596
+ if (allTrue(cx, n.hyps, env) !== true)
597
+ continue;
598
+ const val = ev(cx, general, env);
599
+ if (val !== undefined)
600
+ points.push({ env, value: val });
601
+ }
602
+ const fit = fitExact(names, points, until);
603
+ if (!fit)
604
+ continue;
605
+ return { name: `${n.name}, its constants as variables`, hyps: n.hyps, concl: eqC(general, linE(fit.coef, fit.c)), vars: names, level: n.level - 1, given: false };
606
+ }
607
+ return undefined;
608
+ }
609
+ // Companions beside the claims, about each function of one argument their goals read: first by analogy (another
610
+ // function under a claim's own bound, f(x) ≤ 2x giving g(x) ≤ 2x), then every bound fitted to the values (for each
611
+ // slope from 0 to 3 the tightest constant, above and below, or the line when one fits exactly).
612
+ function beside(cx, facts, nodes, big, until, analog) {
613
+ const out = [], maps = new Set();
614
+ for (const n of nodes)
615
+ for (const r of instancesFor(facts, cx, n, 1).reads)
616
+ if ((cx.arity.get(r.map) ?? 1) === 1 && (cx.eqs.has(r.map) || cx.values[r.map]))
617
+ maps.add(r.map);
618
+ for (const map of [...maps].sort()) {
619
+ const x = `x$${map}`, read = get(map, v(x)), pts = [];
620
+ for (let q = 0; q <= boundFor(1, big); q++) {
621
+ cx.fuel = 20_000;
622
+ const val = callF(cx, map, [q]);
623
+ if (val !== undefined)
624
+ pts.push({ env: { [x]: q }, value: val });
625
+ }
626
+ if (pts.length < 4)
627
+ continue;
628
+ const holds = (c) => pts.every(p => evC(cx, c, p.env) === true);
629
+ const mk = (concl) => ({ name: `${map}: ${showC(concl)}`, hyps: [], concl, vars: [x], level: nodes[0].level, given: false });
630
+ if (analog) {
631
+ for (const n of nodes) {
632
+ const c = n.concl;
633
+ if ((c.op !== '<=' && c.op !== '=' && c.op !== '<') || n.vars.length !== 1 || n.hyps.length)
634
+ continue;
635
+ const at = vs({ [n.vars[0]]: v(x) }), swap = (t) => (t.op === 'get' && readsOf(c).length === 1 && id(t) === id(readsOf(c)[0]) ? read : substE(t, at));
636
+ if (readsOf(c).length !== 1 || readsOf(c)[0].map === map || id(readsOf(c)[0].key) !== id(v(n.vars[0])))
637
+ continue;
638
+ const cand = { op: c.op, a: swap(c.a), b: swap(c.b) };
639
+ if (holds(cand))
640
+ out.push(mk(cand));
641
+ }
642
+ continue;
643
+ }
644
+ const fit = fitExact([x], pts, until);
645
+ if (fit) {
646
+ out.push(mk(eqC(read, linE(fit.coef, fit.c))));
647
+ continue;
648
+ }
649
+ for (const c of [0, 1, 2, 3]) {
650
+ const d = Math.max(...pts.map(p => p.value - c * p.env[x])), lo = Math.min(...pts.map(p => p.value - c * p.env[x]));
651
+ // An upper constant only where it is one: the values reach it in the samples' last half.
652
+ if (c > 0 || pts.slice(pts.length >> 1).some(p => p.value === d))
653
+ out.push(mk(d >= 0 ? le(read, linE({ [x]: c }, d)) : le(add(read, k(-d)), linE({ [x]: c }, 0))));
654
+ if (c > 0)
655
+ out.push(mk(lo >= 0 ? le(linE({ [x]: c }, lo), read) : le(linE({ [x]: c }, 0), add(read, k(-lo)))));
656
+ }
657
+ }
658
+ return [...new Map(out.map(n => [n.name, n])).values()];
659
+ }
660
+ const trace = !!globalThis.process?.env?.CYCLES_TRACE;
661
+ const nodeOf = (c, level, given) => ({ name: c.name, hyps: c.hyps ?? [], concl: c.concl, vars: varsOf([c.hyps ?? [], c.concl]), level, given });
662
+ // The facts at the terms a claim's goal reads (and at those the facts read, `depth` times over): what the search gives
663
+ // each node, for a caller building nodes of its own (research/frontier's today, the builders' shape).
664
+ export function claimInstances(facts, claim, depth = 2) {
665
+ const { eqs, arity } = equations(facts);
666
+ return instancesFor(facts, { eqs, arity, values: {}, memo: new Map(), fuel: 0, depth: 0 }, nodeOf(claim, 0, true), depth).insts;
667
+ }
668
+ export function findCycles(facts, claims, opts = {}) {
669
+ const t0 = performance.now(), until = t0 + (opts.ms ?? 10_000), depth = opts.depth ?? 1;
670
+ const { eqs, arity } = equations(facts);
671
+ const cx = { eqs, arity, values: opts.values ?? {}, memo: new Map(), fuel: 20_000, depth: 0 };
672
+ const big = Math.max(0, ...constsOf([facts.map(f => f.holds), claims]).filter(c => c <= 1000));
673
+ const given = claims.map(c => nodeOf(c, 1, !opts.houdini));
674
+ let last = { proven: false, checked: false, why: 'nothing tried' };
675
+ const once = (nodes, depth) => {
676
+ const b = withDeadline(until, () => build(facts, cx, nodes, depth, big, until));
677
+ if ('broken' in b) {
678
+ const at = nodes.find(n => n.name === b.broken.node);
679
+ return { failed: at, verdict: { proven: false, checked: false, goal: at.name, stuck: 'sat', witness: b.broken.env, why: 'the facts, run, break it' } };
680
+ }
681
+ if ('why' in b)
682
+ return { failed: nodes[0], verdict: { proven: false, checked: false, why: b.why } };
683
+ if (trace)
684
+ for (const sp of b.specs)
685
+ console.error(`[cycles] ${sp.name}: rank (${sp.rank.map(showE).join(', ')}), ${sp.insts.length} instances, uses ${sp.uses.map(u => `${u.node}@{${Object.entries(u.subst).map(([x, t]) => `${x} := ${showE(t)}`).join(', ')}}`).join(' ')}`);
686
+ const out = withDeadline(until, () => proveCyclic(facts, b.specs));
687
+ if (trace)
688
+ console.error(`[cycles] ${out.proven ? 'proven' : `${out.node}: ${out.goal} ${out.stuck}`} at ${Math.round(performance.now() - t0)} ms`);
689
+ if (!out.proven) {
690
+ const at = nodes.find(n => n.name === out.node) ?? nodes[0];
691
+ return { failed: at, verdict: { proven: false, checked: false, goal: out.goal, stuck: out.stuck } };
692
+ }
693
+ const found = { ranks: Object.fromEntries(nodes.map((n, i) => [n.name, `(${b.ranks[i].map(showE).join(', ')})`])), companions: nodes.filter(n => !n.given && !claims.some(c => c.name === n.name)).map(n => `${showC(n.concl)}${n.hyps.length ? ` where ${showC(and(...n.hyps))}` : ''}`), uses: b.uses };
694
+ return { proven: { proven: true, checked: out.checked, proof: out.proof, specs: b.specs, kept: nodes.map(n => n.name), found, ...(out.why ? { why: out.why } : {}) } };
695
+ };
696
+ // Houdini over the nodes that may be dropped (each that fails, dropped, the rest proven again); then each companion
697
+ // dropped in turn where the rest are proven without it (only what the claims need is kept).
698
+ const attempt = (start, depth) => {
699
+ let nodes = start, done;
700
+ for (let round = 0; round <= start.length && nodes.length && performance.now() < until && !done; round++) {
701
+ const t = once(nodes, depth);
702
+ if ('proven' in t) {
703
+ done = t.proven;
704
+ break;
705
+ }
706
+ if (t.failed.given)
707
+ return t.verdict;
708
+ nodes = nodes.filter(n => n !== t.failed);
709
+ }
710
+ if (!done)
711
+ return { proven: false, checked: false, why: performance.now() >= until ? 'out of time' : 'every candidate dropped' };
712
+ if (opts.houdini)
713
+ return done;
714
+ for (const c of nodes.filter(n => !n.given)) {
715
+ if (performance.now() >= until)
716
+ break;
717
+ const rest = nodes.filter(n => n !== c), t = once(rest, depth);
718
+ if ('proven' in t && t.proven.checked)
719
+ (nodes = rest, done = t.proven);
720
+ }
721
+ return done;
722
+ };
723
+ // The claims alone, with the facts at the terms they read and then at those the facts read; their constants as
724
+ // variables; companions beside them by analogy, then every bound.
725
+ const tries = [
726
+ [depth, () => given], [depth + 1, () => given],
727
+ ...(opts.generalize === false ? [] : [
728
+ [depth, () => given.flatMap((n, i) => { const g = abstracted(cx, n, i, big, until); return g ? [n, g] : [n]; })],
729
+ [depth + 1, () => [...given, ...beside(cx, facts, given, big, until, true)]],
730
+ [depth + 1, () => [...given, ...beside(cx, facts, given, big, until, false)]],
731
+ ]),
732
+ ];
733
+ const tried = new Set();
734
+ for (const [d, t] of tries) {
735
+ if (performance.now() >= until)
736
+ break;
737
+ const nodes = t();
738
+ if (tried.has(id([d, nodes.map(n => n.name)])))
739
+ continue;
740
+ tried.add(id([d, nodes.map(n => n.name)]));
741
+ const out = attempt(nodes, d);
742
+ if (out.proven || out.stuck === 'sat' && out.why)
743
+ return out;
744
+ last = out;
745
+ }
746
+ return last;
747
+ }
748
+ const seeded = (seed) => { let x = seed; return (n) => { x = (x * 1103515245 + 12345) % 2 ** 31; return Math.floor(x / 2 ** 31 * n); }; };
749
+ const goalsProven = (goals) => {
750
+ const trees = {};
751
+ for (const g of goals) {
752
+ const out = grow(g);
753
+ if (!('tree' in out) || !check(g, out.tree))
754
+ return { goal: g.name, stuck: 'tree' in out ? 'the kernel refused the tree' : out.stuck, ...('witness' in out && out.witness ? { witness: out.witness } : {}) };
755
+ trees[g.name] = out.tree;
756
+ }
757
+ return { trees };
758
+ };
759
+ // A loop's rank from its runs: random params (the precondition's), turns with random inputs (the assumption's), each
760
+ // turn that stays in the loop an edge; then the kernel's halting goals, with invariants inferLaws finds for them where
761
+ // the rank needs one.
762
+ export function findRank(l, laws = [], opts = {}) {
763
+ const t0 = performance.now(), until = t0 + (opts.ms ?? 10_000), rand = seeded(23);
764
+ const cx = { eqs: new Map(), arity: new Map(), values: {}, memo: new Map(), fuel: Infinity, depth: 0 };
765
+ const state = Object.keys(l.state), names = [...state, ...l.params].sort();
766
+ const node = { name: l.name, hyps: [], concl: yes, vars: names, level: 0, given: true };
767
+ const points = new Map(), edges = new Map();
768
+ for (const bound of [2, 4, 8, 16, 32])
769
+ for (let trial = 0; trial < 40 && performance.now() < t0 + 1000; trial++) {
770
+ const ps = Object.fromEntries(l.params.map(p => [p, rand(bound + 1)]));
771
+ if (l.pre && evC(cx, l.pre, ps) !== true)
772
+ continue;
773
+ let st = {};
774
+ for (const s of state) {
775
+ const x = ev(cx, l.state[s], ps);
776
+ if (x === undefined)
777
+ break;
778
+ st[s] = x;
779
+ }
780
+ for (let turn = 0; turn < 300; turn++) {
781
+ const here = { ...ps, ...st };
782
+ if (evC(cx, l.while, here) !== true)
783
+ break;
784
+ points.set(id(here), here);
785
+ let inp;
786
+ for (let tries = 0; tries < 20 && !inp; tries++) {
787
+ const i = Object.fromEntries(l.inputs.map(x => [x, rand(bound + 1)]));
788
+ if (!l.assume || evC(cx, l.assume, { ...here, ...i }) === true)
789
+ inp = i;
790
+ }
791
+ if (!inp)
792
+ break;
793
+ const next = { ...st };
794
+ for (const [s, e] of Object.entries(l.body)) {
795
+ const x = ev(cx, e, { ...here, ...inp });
796
+ if (x !== undefined)
797
+ next[s] = x;
798
+ }
799
+ const there = { ...ps, ...next };
800
+ if (evC(cx, l.while, there) === true)
801
+ edges.set(id([here, there]), { from: 0, to: 0, s: here, t: there });
802
+ st = next;
803
+ }
804
+ }
805
+ const samples = [[...points.values()].map(env => ({ env, calls: new Set() }))];
806
+ const { comps } = lexRanks([0], [node], samples, [...edges.values()], shared, until);
807
+ if (!comps.length)
808
+ return { proven: false, checked: false, why: `no linear rank, in order, drops on every turn of ${edges.size} sampled` };
809
+ const rank = comps.map(c => compE(c, 0, node, shared)), text = `(${rank.map(showE).join(', ')})`;
810
+ const first = withDeadline(until, () => goalsProven(loopObligations(l, laws, rank)));
811
+ if ('trees' in first)
812
+ return { proven: true, checked: true, rank, laws: [], found: { rank: text, laws: [] }, trees: first.trees };
813
+ // Invariants the rank needs: the descent as the loop's step laws, inferLaws's candidates chosen by its counterexamples.
814
+ const m = loopMachine(l), { stay, cover, helps } = livenessParts(m, loopLiveness(l, rank));
815
+ const ms = until - performance.now();
816
+ if (ms > 300) {
817
+ const inf = inferLaws(m, laws, [stay, cover, ...helps], { ms });
818
+ const kept = inf.found.filter(f => !f.law.forall && !f.law.keys?.length), more = kept.map(f => f.law);
819
+ if (inf.proven && more.length) {
820
+ const all = [...laws, ...more], again = withDeadline(until + 5000, () => goalsProven(loopObligations(l, all, rank)));
821
+ if ('trees' in again)
822
+ return { proven: true, checked: true, rank, laws: more, found: { rank: text, laws: kept.map(f => f.text) }, trees: again.trees };
823
+ }
824
+ }
825
+ return { proven: false, checked: false, rank, found: { rank: text, laws: [] }, goal: first.goal, stuck: first.stuck, ...(first.witness ? { witness: first.witness } : {}), why: `rank ${text} from the runs; no invariant found that it needs` };
826
+ }
827
+ export function cycleLemmas(m, laws, stepLaws = [], opts = {}) {
828
+ const until = performance.now() + (opts.ms ?? 10_000);
829
+ const fns = Object.entries(m.fns ?? {}).filter(([, f]) => !f.recur && (f.arity ?? 1) === 1);
830
+ if (!fns.length || !m.facts?.length)
831
+ return { proven: false, checked: false, found: [], why: 'no function known by its facts' };
832
+ const facts = m.facts.map(f => ({ name: f.name, keys: [...(f.forall ? [f.forall] : []), ...(f.keys ?? [])], holds: f.holds }));
833
+ const values = {};
834
+ for (const [name, f] of fns)
835
+ values[name] = (q) => fnValue(f, q);
836
+ const ts = [...new Set(constsOf([m, laws, stepLaws]).filter(c => c >= 0 && c <= 1000).flatMap(c => [c - 1, c, c + 1]).filter(c => c >= 0))].sort((a, b) => a - b);
837
+ const big = Math.max(24, ...ts), x = 'k$', claims = [];
838
+ for (const [name] of fns) {
839
+ const f = values[name];
840
+ if (!f)
841
+ continue;
842
+ const vals = [];
843
+ for (let q = 0; q <= 2 * big + 20; q++) {
844
+ let y;
845
+ try {
846
+ y = f(q);
847
+ }
848
+ catch {
849
+ break;
850
+ }
851
+ if (!Number.isSafeInteger(y) || y < 0)
852
+ break;
853
+ vals.push(y);
854
+ }
855
+ if (vals.length < 4)
856
+ continue;
857
+ const read = get(name, v(x)), pts = vals.map((value, q) => ({ env: { [x]: q }, value }));
858
+ const line = fitExact([x], pts, until);
859
+ if (line)
860
+ claims.push({ name: `${name}(k) = ${showE(linE({ k: line.coef[x] }, line.c))}`, concl: eqC(read, linE(line.coef, line.c)) });
861
+ for (const t of ts) {
862
+ if (t >= vals.length)
863
+ continue;
864
+ const below = vals.slice(0, t + 1), above = pts.slice(t);
865
+ if (below.every(y => y === below[0]))
866
+ claims.push({ name: `${name}(k) = ${below[0]} for k ≤ ${t}`, hyps: [le(v(x), k(t))], concl: eqC(read, k(below[0])) });
867
+ const fit = above.length >= 4 && fitExact([x], above, until);
868
+ if (fit && (fit.coef[x] !== line?.coef[x] || fit.c !== line?.c))
869
+ claims.push({ name: `${name}(k) = ${showE(linE({ k: fit.coef[x] }, fit.c))} for k ≥ ${t}`, hyps: [le(k(t), v(x))], concl: eqC(read, linE(fit.coef, fit.c)) });
870
+ }
871
+ const top = Math.max(...vals);
872
+ if (vals.slice(vals.length >> 1).includes(top))
873
+ claims.push({ name: `${name}(k) ≤ ${top}`, concl: le(read, k(top)) });
874
+ }
875
+ const unique = [...new Map(claims.map(c => [c.name, c])).values()];
876
+ if (!unique.length)
877
+ return { proven: false, checked: false, found: [], why: 'no lemma fits the values' };
878
+ const out = findCycles(facts, unique, { ms: Math.max(0, until - performance.now()) * 0.6, values, houdini: true, generalize: false });
879
+ if (!out.proven || !out.checked || !out.kept?.length)
880
+ return { proven: false, checked: false, found: [], why: out.why ?? 'no lemma proven' };
881
+ const asLaw = (c) => ({ law: { name: c.name, forall: x, holds: c.hyps?.length ? { op: 'or', xs: [{ op: 'not', x: and(...c.hyps) }, c.concl] } : c.concl }, text: c.name });
882
+ let found = unique.filter(c => out.kept.includes(c.name)).map(asLaw);
883
+ const proveWith = (fs) => {
884
+ const mm = { ...m, facts: [...m.facts, ...fs.map(f => f.law)] };
885
+ const r = withDeadline(until, () => (stepLaws.length ? proveStepLaws(mm, laws, stepLaws) : proveLaws(mm, laws)));
886
+ return { ok: r.proven && r.checked, machine: mm };
887
+ };
888
+ let best = proveWith(found);
889
+ if (!best.ok)
890
+ return { proven: false, checked: false, found: [], why: `lemmas proven (${found.map(f => f.text).join('; ')}), the laws not with them` };
891
+ for (const f of [...found]) {
892
+ if (performance.now() >= until)
893
+ break;
894
+ const rest = found.filter(g => g !== f), again = proveWith(rest);
895
+ if (again.ok)
896
+ (found = rest, best = again);
897
+ }
898
+ return { proven: true, checked: true, machine: best.machine, found, ...(out.proof ? { proof: out.proof } : {}) };
899
+ }