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/README.md +37 -6
- package/dist/core/cycles.d.ts +35 -2
- package/dist/core/cycles.js +458 -51
- package/dist/core/cyclic.d.ts +18 -4
- package/dist/core/cyclic.js +319 -263
- package/dist/core/data.d.ts +196 -15
- package/dist/core/data.js +1284 -213
- package/dist/core/discover.js +54 -33
- package/dist/core/dsl.d.ts +31 -3
- package/dist/core/dsl.js +168 -38
- package/dist/core/exists.d.ts +26 -0
- package/dist/core/exists.js +625 -0
- package/dist/core/infer.d.ts +13 -0
- package/dist/core/infer.js +258 -41
- package/dist/core/machine.d.ts +27 -0
- package/dist/core/machine.js +45 -0
- package/dist/core/solver.d.ts +3 -0
- package/dist/core/solver.js +5 -0
- package/dist/core/transfer.d.ts +17 -0
- package/dist/core/transfer.js +468 -0
- package/dist/core/translate.js +10 -4
- package/dist/core/watch.js +5 -1
- package/dist/kernel/prove.d.ts +8 -1
- package/dist/kernel/prove.js +106 -55
- package/dist/lean-kernel/emit.d.ts +4 -1
- package/dist/lean-kernel/emit.js +336 -69
- package/dist/lean-kernel/library.js +2 -2
- package/package.json +2 -2
package/dist/core/cycles.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
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";
|
|
1
|
+
import { compileFunction, evalC as evalMC, evalE as evalME, fnValue, interpret, pairedArgs } from "./machine.js";
|
|
2
|
+
import { check, livenessHelpful, livenessParts, loopLiveness, loopMachine, loopObligations, substC, substE } from "../kernel/kernel.js";
|
|
3
|
+
import { checkCyclic, factsAvoid, lexLt } from "../kernel/cyclic.js";
|
|
4
4
|
import { proveCyclic } from "./cyclic.js";
|
|
5
5
|
import { solve } from "./solver.js";
|
|
6
|
-
import { grow, proveLaws, proveStepLaws, withDeadline } from "../kernel/prove.js";
|
|
6
|
+
import { grow, proveLaws, proveLiveness, proveStepLaws, withDeadline } from "../kernel/prove.js";
|
|
7
7
|
import { inferLaws } from "./infer.js";
|
|
8
8
|
// --- terms ----------------------------------------------------------------------------------------------------------
|
|
9
9
|
const v = (name) => ({ op: 'var', name }), k = (value) => ({ op: 'const', value });
|
|
@@ -36,7 +36,10 @@ export const showE = (e) => {
|
|
|
36
36
|
case 'add': return `${showE(e.a)} + ${showE(e.b)}`;
|
|
37
37
|
case 'sub': return `${showE(e.a)} − ${e.b.op === 'add' || e.b.op === 'sub' ? `(${showE(e.b)})` : showE(e.b)}`;
|
|
38
38
|
case 'mul': return `${e.k}·${e.a.op === 'var' || e.a.op === 'const' ? showE(e.a) : `(${showE(e.a)})`}`;
|
|
39
|
-
case 'prod':
|
|
39
|
+
case 'prod': {
|
|
40
|
+
const f = (t) => (t.op === 'var' || t.op === 'const' || t.op === 'get' ? showE(t) : `(${showE(t)})`);
|
|
41
|
+
return `${f(e.a)}·${f(e.b)}`;
|
|
42
|
+
}
|
|
40
43
|
case 'min':
|
|
41
44
|
case 'max': return `${e.op}(${showE(e.a)}, ${showE(e.b)})`;
|
|
42
45
|
case 'if': return `if ${showC(e.c)} then ${showE(e.then)} else ${showE(e.else)}`;
|
|
@@ -409,9 +412,10 @@ function lp(must, pool, until, scale = 1, signs = [true, false]) {
|
|
|
409
412
|
}
|
|
410
413
|
return undefined;
|
|
411
414
|
};
|
|
412
|
-
// The widest box first: where it has no solution, none of the smaller has.
|
|
415
|
+
// The widest box first: where it has no solution, none of the smaller has. Past it, a rank's rows (homogeneous in the
|
|
416
|
+
// unknowns, the drop's constant aside) are solved exactly over the rationals, the solution scaled to whole numbers.
|
|
413
417
|
if (!stage(signs.at(-1), bounds.at(-1), true))
|
|
414
|
-
return undefined;
|
|
418
|
+
return fit ? undefined : exactLp(must, pool, until);
|
|
415
419
|
for (const natural of signs)
|
|
416
420
|
for (const bound of bounds)
|
|
417
421
|
for (const wide of [false, true]) {
|
|
@@ -421,14 +425,176 @@ function lp(must, pool, until, scale = 1, signs = [true, false]) {
|
|
|
421
425
|
}
|
|
422
426
|
return undefined;
|
|
423
427
|
}
|
|
428
|
+
const bgcd = (a, b) => { if (a < 0n)
|
|
429
|
+
a = -a; if (b < 0n)
|
|
430
|
+
b = -b; while (b)
|
|
431
|
+
[a, b] = [b, a % b]; return a; };
|
|
432
|
+
const q = (n, d = 1n) => { if (d < 0n)
|
|
433
|
+
(n = -n, d = -d); const g = bgcd(n, d) || 1n; return g === 1n ? { n, d } : { n: n / g, d: d / g }; };
|
|
434
|
+
const Q0 = q(0n), Q1 = q(1n);
|
|
435
|
+
const qadd = (a, b) => (a.d === b.d ? q(a.n + b.n, a.d) : q(a.n * b.d + b.n * a.d, a.d * b.d));
|
|
436
|
+
const qsub = (a, b) => qadd(a, { n: -b.n, d: b.d });
|
|
437
|
+
const qmul = (a, b) => (!a.n || !b.n ? Q0 : q(a.n * b.n, a.d * b.d));
|
|
438
|
+
const qdiv = (a, b) => q(a.n * b.d, a.d * b.n);
|
|
439
|
+
const qlt = (a, b) => a.n * b.d < b.n * a.d;
|
|
440
|
+
// The primal simplex on a tableau whose basis is feasible: minimize cost·y over T·y = rhs (the last column), y >= 0,
|
|
441
|
+
// Bland's rule (the smallest column that improves, the smallest basis index among ties), only `open` columns entering.
|
|
442
|
+
function simplex(T, basis, cost, open, until) {
|
|
443
|
+
const cols = cost.length;
|
|
444
|
+
for (let it = 0; it < 5000; it++) {
|
|
445
|
+
if (performance.now() > until)
|
|
446
|
+
return false;
|
|
447
|
+
let enter = -1;
|
|
448
|
+
for (let j = 0; j < cols && enter < 0; j++) {
|
|
449
|
+
if (!open(j) || basis.includes(j))
|
|
450
|
+
continue;
|
|
451
|
+
let r = cost[j];
|
|
452
|
+
for (const [i, b] of basis.entries())
|
|
453
|
+
if (T[i][j].n && cost[b].n)
|
|
454
|
+
r = qsub(r, qmul(cost[b], T[i][j]));
|
|
455
|
+
if (r.n < 0n)
|
|
456
|
+
enter = j;
|
|
457
|
+
}
|
|
458
|
+
if (enter < 0)
|
|
459
|
+
return true;
|
|
460
|
+
let leave = -1, best;
|
|
461
|
+
for (const [i, row] of T.entries()) {
|
|
462
|
+
if (row[enter].n <= 0n)
|
|
463
|
+
continue;
|
|
464
|
+
const ratio = qdiv(row[cols], row[enter]);
|
|
465
|
+
if (!best || qlt(ratio, best) || (!qlt(best, ratio) && basis[i] < basis[leave]))
|
|
466
|
+
(best = ratio, leave = i);
|
|
467
|
+
}
|
|
468
|
+
if (leave < 0)
|
|
469
|
+
return false; // unbounded: not for an objective bounded below
|
|
470
|
+
const p = T[leave][enter];
|
|
471
|
+
T[leave] = T[leave].map(x => qdiv(x, p));
|
|
472
|
+
for (const [i, row] of T.entries()) {
|
|
473
|
+
if (i === leave || !row[enter].n)
|
|
474
|
+
continue;
|
|
475
|
+
const f = row[enter];
|
|
476
|
+
T[i] = row.map((x, j) => qsub(x, qmul(f, T[leave][j])));
|
|
477
|
+
}
|
|
478
|
+
basis[leave] = enter;
|
|
479
|
+
}
|
|
480
|
+
return false;
|
|
481
|
+
}
|
|
482
|
+
// The rows (lin <= 0) over free unknowns, solved exactly: Σ|u| least (each u as p − q, p, q >= 0), two phases. The
|
|
483
|
+
// rational solution scaled by its denominators' least common multiple and divided by its numerators' common divisor;
|
|
484
|
+
// kept only if every row holds of it exactly (a row with a positive constant, a drop of at least 1, holds of any
|
|
485
|
+
// multiple at least 1 of a solution; one with a negative constant may not).
|
|
486
|
+
function exactSolve(rows, until) {
|
|
487
|
+
const xs = [...new Set(rows.flatMap(l => Object.keys(l)).filter(x => x !== '1'))].sort(), n = xs.length, m = rows.length;
|
|
488
|
+
// Columns: p (n), q (n), a slack per row (m), an artificial per row (m); the right-hand side last.
|
|
489
|
+
const cols = 2 * n + 2 * m, T = [], basis = [];
|
|
490
|
+
for (const [i, l] of rows.entries()) {
|
|
491
|
+
let rhs = -(l['1'] ?? 0);
|
|
492
|
+
const sign = rhs < 0 ? -1 : 1;
|
|
493
|
+
rhs *= sign;
|
|
494
|
+
const row = Array.from({ length: cols + 1 }, () => Q0);
|
|
495
|
+
for (const [j, x] of xs.entries()) {
|
|
496
|
+
const c = sign * (l[x] ?? 0);
|
|
497
|
+
if (c)
|
|
498
|
+
(row[j] = q(BigInt(c)), row[n + j] = q(BigInt(-c)));
|
|
499
|
+
}
|
|
500
|
+
row[2 * n + i] = q(BigInt(sign));
|
|
501
|
+
if (sign < 0) {
|
|
502
|
+
row[2 * n + m + i] = Q1;
|
|
503
|
+
basis.push(2 * n + m + i);
|
|
504
|
+
}
|
|
505
|
+
else
|
|
506
|
+
basis.push(2 * n + i);
|
|
507
|
+
row[cols] = q(BigInt(rhs));
|
|
508
|
+
T.push(row);
|
|
509
|
+
}
|
|
510
|
+
const art = (j) => j >= 2 * n + m;
|
|
511
|
+
const phase1 = Array.from({ length: cols }, (_, j) => (art(j) ? Q1 : Q0));
|
|
512
|
+
if (!simplex(T, basis, phase1, () => true, until))
|
|
513
|
+
return undefined;
|
|
514
|
+
if (T.some((row, i) => art(basis[i]) && row[cols].n))
|
|
515
|
+
return undefined; // infeasible
|
|
516
|
+
// An artificial left in the basis at zero: pivoted out on any other column, or its row dropped (redundant).
|
|
517
|
+
for (let i = T.length - 1; i >= 0; i--) {
|
|
518
|
+
if (!art(basis[i]))
|
|
519
|
+
continue;
|
|
520
|
+
const j = T[i].findIndex((x, c) => c < 2 * n + m && x.n !== 0n);
|
|
521
|
+
if (j < 0) {
|
|
522
|
+
T.splice(i, 1);
|
|
523
|
+
basis.splice(i, 1);
|
|
524
|
+
continue;
|
|
525
|
+
}
|
|
526
|
+
const p = T[i][j];
|
|
527
|
+
T[i] = T[i].map(x => qdiv(x, p));
|
|
528
|
+
for (const [r, row] of T.entries())
|
|
529
|
+
if (r !== i && row[j].n) {
|
|
530
|
+
const f = row[j];
|
|
531
|
+
T[r] = row.map((x, c) => qsub(x, qmul(f, T[i][c])));
|
|
532
|
+
}
|
|
533
|
+
basis[i] = j;
|
|
534
|
+
}
|
|
535
|
+
const phase2 = Array.from({ length: cols }, (_, j) => (j < 2 * n ? Q1 : Q0));
|
|
536
|
+
if (!simplex(T, basis, phase2, j => !art(j), until))
|
|
537
|
+
return undefined;
|
|
538
|
+
const val = (j) => { const i = basis.indexOf(j); return i < 0 ? Q0 : T[i][cols]; };
|
|
539
|
+
const u = xs.map((_, j) => qsub(val(j), val(n + j)));
|
|
540
|
+
let lcm = 1n;
|
|
541
|
+
for (const r of u)
|
|
542
|
+
lcm = lcm / bgcd(lcm, r.d) * r.d;
|
|
543
|
+
const ints = u.map(r => r.n * (lcm / r.d)), g = ints.reduce((a, b) => bgcd(a, b), 0n) || 1n;
|
|
544
|
+
const w = {};
|
|
545
|
+
for (const [j, x] of xs.entries()) {
|
|
546
|
+
const c = Number(ints[j] / g);
|
|
547
|
+
if (!Number.isSafeInteger(c))
|
|
548
|
+
return undefined;
|
|
549
|
+
if (c)
|
|
550
|
+
w[x] = c;
|
|
551
|
+
}
|
|
552
|
+
return rows.every(l => valueAt(l, w) <= 0) ? w : undefined;
|
|
553
|
+
}
|
|
554
|
+
// exactSolve over `must` and the pool's rows, the pool's added as they are violated (the most violated first). The
|
|
555
|
+
// solution is found on the smaller half of the runs (each row's size its largest coefficient, a sample's value or a
|
|
556
|
+
// turn's change) and kept only where it holds of all of them: a coefficient the runs' size forces (32·x + y where x
|
|
557
|
+
// reaches 33, a reset y := x − 1 in the runs) grows with the runs, and is refused, where one a turn's change forces
|
|
558
|
+
// (11·x + 2·y, six tokens for a coin and a coin for five) is the same on both.
|
|
559
|
+
function exactLp(must, pool, until) {
|
|
560
|
+
const size = (l) => { let m = 0; for (const x in l)
|
|
561
|
+
if (x !== '1')
|
|
562
|
+
m = Math.max(m, Math.abs(l[x])); return m; };
|
|
563
|
+
const sizes = pool.map(size), half = sizes.reduce((a, b) => Math.max(a, b), 0) / 2, small = pool.filter((_, i) => sizes[i] <= half), rows = [...must];
|
|
564
|
+
for (let round = 0; round < 40 && performance.now() < until; round++) {
|
|
565
|
+
const w = exactSolve(rows, until);
|
|
566
|
+
if (!w)
|
|
567
|
+
return undefined;
|
|
568
|
+
// The twelve most violated, in one pass.
|
|
569
|
+
const bad = [];
|
|
570
|
+
for (const l of small) {
|
|
571
|
+
const x = valueAt(l, w);
|
|
572
|
+
if (x <= 0 || (bad.length === 12 && x <= bad[11][0]))
|
|
573
|
+
continue;
|
|
574
|
+
bad.push([x, l]);
|
|
575
|
+
bad.sort((p, q) => q[0] - p[0]);
|
|
576
|
+
if (bad.length > 12)
|
|
577
|
+
bad.pop();
|
|
578
|
+
}
|
|
579
|
+
if (!bad.length)
|
|
580
|
+
return pool.every(l => valueAt(l, w) <= 0) ? w : undefined;
|
|
581
|
+
rows.push(...bad.map(b => b[1]));
|
|
582
|
+
}
|
|
583
|
+
return undefined;
|
|
584
|
+
}
|
|
424
585
|
// Lexicographic ranks over the edges (each from a node at a sample to a node at its arguments), every component >= 0
|
|
425
586
|
// at every sample: Bradley–Manna–Sipma's successive elimination, each component the sum of one solution per edge it can
|
|
426
587
|
// make drop. An edge no component makes drop is left (its use's guard keeps it where the rank drops, if anywhere).
|
|
427
|
-
|
|
428
|
-
|
|
588
|
+
// `onEdges`: each component a natural only where an edge compares it (at its ends), not at every sample: a rank the
|
|
589
|
+
// kernel reads truncated (n·n − i, 0 past the square, where no call is made). `weak`: edges the rank must not raise but
|
|
590
|
+
// need not lower (a liveness's steps other than its helpful requests'), each dropped once a component lowers it.
|
|
591
|
+
function lexRanks(idx, nodes, samples, edges, name, until, onEdges = false, weak = []) {
|
|
592
|
+
const ends = onEdges ? edges.flatMap(e => [[e.from, e.s], [e.to, e.t]]) : idx.flatMap(n => samples[n].map(s => [n, s.env]));
|
|
593
|
+
const points = [...new Map(ends.map(([n, env]) => { const l = rankRow(n, nodes, env, name, -1); return [id(l), l]; })).values()];
|
|
429
594
|
const delta = (e) => plus(rankRow(e.to, nodes, e.t, name), rankRow(e.from, nodes, e.s, name, -1));
|
|
430
595
|
const scale = 1 + Math.max(0, ...idx.flatMap(n => samples[n].flatMap(s => Object.values(s.env))));
|
|
431
596
|
let left = [...new Map(edges.map(e => { const l = delta(e); return [id(l), l]; })).values()];
|
|
597
|
+
let keep = [...new Map(weak.map(e => { const l = delta(e); return [id(l), l]; })).values()].filter(l => !left.some(x => id(x) === id(l)));
|
|
432
598
|
const comps = [];
|
|
433
599
|
while (left.length && comps.length < 4 && performance.now() < until) {
|
|
434
600
|
let sum = {};
|
|
@@ -439,7 +605,7 @@ function lexRanks(idx, nodes, samples, edges, name, until) {
|
|
|
439
605
|
if (strict(l) || performance.now() > until || tried.has(sign(l)) || tried.size > 40)
|
|
440
606
|
continue;
|
|
441
607
|
tried.add(sign(l));
|
|
442
|
-
const w = lp([plus(l, { 1: 1 })], [...left, ...points], until, scale);
|
|
608
|
+
const w = lp([plus(l, { 1: 1 })], [...left, ...keep, ...points], until, scale);
|
|
443
609
|
if (w)
|
|
444
610
|
sum = plus(sum, w);
|
|
445
611
|
}
|
|
@@ -451,6 +617,7 @@ function lexRanks(idx, nodes, samples, edges, name, until) {
|
|
|
451
617
|
sum = Object.fromEntries(Object.entries(sum).map(([x, c]) => [x, c / g]));
|
|
452
618
|
comps.push(sum);
|
|
453
619
|
const kept = left.filter(l => !strict(l));
|
|
620
|
+
keep = keep.filter(l => !strict(l));
|
|
454
621
|
if (kept.length === left.length)
|
|
455
622
|
break;
|
|
456
623
|
left = kept;
|
|
@@ -458,7 +625,51 @@ function lexRanks(idx, nodes, samples, edges, name, until) {
|
|
|
458
625
|
return { comps, left: left.length };
|
|
459
626
|
}
|
|
460
627
|
const gcd = (a, b) => (b === 0 ? a : gcd(b, a % b));
|
|
461
|
-
|
|
628
|
+
// The same over named naturals and the edges of runs alone (core/data.ts: a recursive function's calls, each from its
|
|
629
|
+
// parameters' sizes to its arguments'): each component's coefficient per variable and its constant.
|
|
630
|
+
export function ranksOfRuns(vars, points, edges, ms = 1000) {
|
|
631
|
+
const node = { name: 'runs', hyps: [], concl: yes, vars, level: 0, given: true };
|
|
632
|
+
const { comps, left } = lexRanks([0], [node], [points.map(env => ({ env, calls: new Set() }))], edges.map(e => ({ from: 0, to: 0, s: e.s, t: e.t })), shared, performance.now() + ms);
|
|
633
|
+
return { comps: comps.map(c => ({ coef: Object.fromEntries(vars.map((x, j) => [x, c[shared(0, j)] ?? 0])), c: c[shared(0, 'b')] ?? 0 })), left };
|
|
634
|
+
}
|
|
635
|
+
const compE = (c, n, node, name) => unfeat(linE(Object.fromEntries(node.vars.map((x, j) => [x, c[name(n, j)] ?? 0])), c[name(n, 'b')] ?? 0));
|
|
636
|
+
// Ranks of degree 2: a product of two of a node's variables (a square too) a further variable of the LP, its value at
|
|
637
|
+
// each sample the product's, one product at a time (a sparse template: with every product at once, the runs fit a rank
|
|
638
|
+
// of many small parts that no turn past them keeps). The rank found is linear in it, the product read back as the term
|
|
639
|
+
// it is (x·y as prod(x, y)), which the kernel takes as a monomial, its goals the same linear goals over it (with its
|
|
640
|
+
// products of rows and squares, Positivstellensatz of degree 2, where they need more).
|
|
641
|
+
const featSep = '·';
|
|
642
|
+
const products = (names) => names.flatMap((a, i) => names.slice(i).map(b => [a, b]));
|
|
643
|
+
const featName = ([a, b]) => `${a}${featSep}${b}`;
|
|
644
|
+
const withFeat = (env, p) => (p && env[p[0]] !== undefined && env[p[1]] !== undefined ? { ...env, [featName(p)]: env[p[0]] * env[p[1]] } : env);
|
|
645
|
+
// (linE builds sums, differences and multiples of variables and constants only)
|
|
646
|
+
const unfeat = (e) => {
|
|
647
|
+
if (e.op === 'var') {
|
|
648
|
+
const [a, b] = e.name.split(featSep);
|
|
649
|
+
return b === undefined ? e : { op: 'prod', a: v(a), b: v(b) };
|
|
650
|
+
}
|
|
651
|
+
if (e.op === 'add' || e.op === 'sub')
|
|
652
|
+
return { ...e, a: unfeat(e.a), b: unfeat(e.b) };
|
|
653
|
+
return e.op === 'mul' ? { ...e, a: unfeat(e.a) } : e;
|
|
654
|
+
};
|
|
655
|
+
// The products of two variables a term reads, each as its sorted pair.
|
|
656
|
+
const productsIn = (t) => {
|
|
657
|
+
const out = new Map();
|
|
658
|
+
walk(t, o => { if (o.op === 'prod') {
|
|
659
|
+
const a = o.a, b = o.b;
|
|
660
|
+
if (a.op === 'var' && b.op === 'var') {
|
|
661
|
+
const p = [a.name, b.name].sort();
|
|
662
|
+
out.set(id(p), p);
|
|
663
|
+
}
|
|
664
|
+
} });
|
|
665
|
+
return [...out.values()];
|
|
666
|
+
};
|
|
667
|
+
// The nodes, samples and edges with one product of two variables per node a variable (none where `ps[i]` is undefined).
|
|
668
|
+
const withProducts = (nodes, samples, edges, ps) => ({
|
|
669
|
+
nodes: nodes.map((n, i) => (ps[i] ? { ...n, vars: [...n.vars, featName(ps[i])] } : n)),
|
|
670
|
+
samples: samples.map((ss, i) => ss.map(s => ({ ...s, env: withFeat(s.env, ps[i]) }))),
|
|
671
|
+
edges: edges.map(e => ({ ...e, s: withFeat(e.s, ps[e.from]), t: withFeat(e.t, ps[e.to]) })),
|
|
672
|
+
});
|
|
462
673
|
// Strongly connected components (Tarjan), each node's component's index.
|
|
463
674
|
function sccs(graph) {
|
|
464
675
|
const index = [], low = [], on = [], stack = [], comp = [];
|
|
@@ -490,7 +701,8 @@ function sccs(graph) {
|
|
|
490
701
|
visit(u);
|
|
491
702
|
return comp;
|
|
492
703
|
}
|
|
493
|
-
|
|
704
|
+
// `quad`: ranks of degree 2, the product of those two variables a further one of each node that has both.
|
|
705
|
+
function build(facts, cx, nodes, depth, big, until, quad) {
|
|
494
706
|
const inst = nodes.map(n => instancesFor(facts, cx, n, depth));
|
|
495
707
|
const samples = [];
|
|
496
708
|
for (const n of nodes) {
|
|
@@ -539,21 +751,23 @@ function build(facts, cx, nodes, depth, big, until) {
|
|
|
539
751
|
parts.set(i, []);
|
|
540
752
|
continue;
|
|
541
753
|
}
|
|
542
|
-
|
|
754
|
+
const q = quad ? withProducts(nodes, samples, within, nodes.map(n => (n.vars.includes(quad[0]) && n.vars.includes(quad[1]) ? quad : undefined))) : { nodes, samples, edges: within };
|
|
755
|
+
let best = { ...lexRanks(idx, q.nodes, q.samples, q.edges, shared, until, !!quad), name: shared };
|
|
543
756
|
if (best.left && idx.length > 1 && idx.length <= 4) {
|
|
544
|
-
const o = lexRanks(idx, nodes, samples,
|
|
757
|
+
const o = lexRanks(idx, q.nodes, q.samples, q.edges, own(q.nodes), until, !!quad);
|
|
545
758
|
if (o.left < best.left)
|
|
546
|
-
best = { ...o, name: own(nodes) };
|
|
759
|
+
best = { ...o, name: own(q.nodes) };
|
|
547
760
|
}
|
|
548
761
|
for (const i of idx)
|
|
549
|
-
parts.set(i, best.comps.map(cp => compE(cp, i, nodes[i], best.name)));
|
|
762
|
+
parts.set(i, best.comps.map(cp => compE(cp, i, q.nodes[i], best.name)));
|
|
550
763
|
}
|
|
551
764
|
const width = Math.max(0, ...[...parts.values()].map(p => p.length)), tiers = graph.some(g => g.length);
|
|
552
765
|
const ranks = nodes.map((_, i) => [...(tiers ? [k(heightOf(comp[i]))] : []), ...parts.get(i), ...Array.from({ length: width - parts.get(i).length }, () => k(0))]);
|
|
553
766
|
let uses = 0;
|
|
554
767
|
const specs = nodes.map((n, i) => ({
|
|
555
768
|
name: n.name, hyps: n.hyps, concl: n.concl, rank: ranks[i], insts: inst[i].insts,
|
|
556
|
-
|
|
769
|
+
// (a use at a rank that is this node's own, term for term, never applies: its guard is false, so it is left out)
|
|
770
|
+
uses: cands[i].filter((c, u) => active[i][u] && id(ranks[c.node].map(r => substE(r, vs(c.subst)))) !== id(ranks[i])).map(c => {
|
|
557
771
|
uses++;
|
|
558
772
|
const m = nodes[c.node], s = vs(c.subst);
|
|
559
773
|
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])) };
|
|
@@ -672,8 +886,8 @@ export function findCycles(facts, claims, opts = {}) {
|
|
|
672
886
|
const big = Math.max(0, ...constsOf([facts.map(f => f.holds), claims]).filter(c => c <= 1000));
|
|
673
887
|
const given = claims.map(c => nodeOf(c, 1, !opts.houdini));
|
|
674
888
|
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));
|
|
889
|
+
const once = (nodes, depth, quad) => {
|
|
890
|
+
const b = withDeadline(until, () => build(facts, cx, nodes, depth, big, until, quad));
|
|
677
891
|
if ('broken' in b) {
|
|
678
892
|
const at = nodes.find(n => n.name === b.broken.node);
|
|
679
893
|
return { failed: at, verdict: { proven: false, checked: false, goal: at.name, stuck: 'sat', witness: b.broken.env, why: 'the facts, run, break it' } };
|
|
@@ -695,10 +909,10 @@ export function findCycles(facts, claims, opts = {}) {
|
|
|
695
909
|
};
|
|
696
910
|
// Houdini over the nodes that may be dropped (each that fails, dropped, the rest proven again); then each companion
|
|
697
911
|
// dropped in turn where the rest are proven without it (only what the claims need is kept).
|
|
698
|
-
const attempt = (start, depth) => {
|
|
912
|
+
const attempt = (start, depth, quad) => {
|
|
699
913
|
let nodes = start, done;
|
|
700
914
|
for (let round = 0; round <= start.length && nodes.length && performance.now() < until && !done; round++) {
|
|
701
|
-
const t = once(nodes, depth);
|
|
915
|
+
const t = once(nodes, depth, quad);
|
|
702
916
|
if ('proven' in t) {
|
|
703
917
|
done = t.proven;
|
|
704
918
|
break;
|
|
@@ -714,14 +928,16 @@ export function findCycles(facts, claims, opts = {}) {
|
|
|
714
928
|
for (const c of nodes.filter(n => !n.given)) {
|
|
715
929
|
if (performance.now() >= until)
|
|
716
930
|
break;
|
|
717
|
-
const rest = nodes.filter(n => n !== c), t = once(rest, depth);
|
|
931
|
+
const rest = nodes.filter(n => n !== c), t = once(rest, depth, quad);
|
|
718
932
|
if ('proven' in t && t.proven.checked)
|
|
719
933
|
(nodes = rest, done = t.proven);
|
|
720
934
|
}
|
|
721
935
|
return done;
|
|
722
936
|
};
|
|
723
937
|
// 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
|
|
938
|
+
// variables; companions beside them by analogy, then every bound; then ranks of degree 2, over each product of two
|
|
939
|
+
// variables the claims read (up(i, n) = up(i + 1, n) while i < n·n: n·n − i).
|
|
940
|
+
const quads = productsIn(given.map(n => [n.hyps, n.concl])).filter(([a, b]) => given.some(n => n.vars.includes(a) && n.vars.includes(b)));
|
|
725
941
|
const tries = [
|
|
726
942
|
[depth, () => given], [depth + 1, () => given],
|
|
727
943
|
...(opts.generalize === false ? [] : [
|
|
@@ -729,16 +945,17 @@ export function findCycles(facts, claims, opts = {}) {
|
|
|
729
945
|
[depth + 1, () => [...given, ...beside(cx, facts, given, big, until, true)]],
|
|
730
946
|
[depth + 1, () => [...given, ...beside(cx, facts, given, big, until, false)]],
|
|
731
947
|
]),
|
|
948
|
+
...quads.flatMap(p => [[depth, () => given, p], [depth + 1, () => given, p]]),
|
|
732
949
|
];
|
|
733
950
|
const tried = new Set();
|
|
734
|
-
for (const [d, t] of tries) {
|
|
951
|
+
for (const [d, t, quad] of tries) {
|
|
735
952
|
if (performance.now() >= until)
|
|
736
953
|
break;
|
|
737
954
|
const nodes = t();
|
|
738
|
-
if (tried.has(id([d, nodes.map(n => n.name)])))
|
|
955
|
+
if (tried.has(id([d, nodes.map(n => n.name), quad])))
|
|
739
956
|
continue;
|
|
740
|
-
tried.add(id([d, nodes.map(n => n.name)]));
|
|
741
|
-
const out = attempt(nodes, d);
|
|
957
|
+
tried.add(id([d, nodes.map(n => n.name), quad]));
|
|
958
|
+
const out = attempt(nodes, d, quad);
|
|
742
959
|
if (out.proven || out.stuck === 'sat' && out.why)
|
|
743
960
|
return out;
|
|
744
961
|
last = out;
|
|
@@ -803,38 +1020,168 @@ export function findRank(l, laws = [], opts = {}) {
|
|
|
803
1020
|
}
|
|
804
1021
|
}
|
|
805
1022
|
const samples = [[...points.values()].map(env => ({ env, calls: new Set() }))];
|
|
806
|
-
const { comps } = lexRanks([0], [node], samples, [...edges.values()], shared, until);
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
const
|
|
1023
|
+
const { comps, left } = lexRanks([0], [node], samples, [...edges.values()], shared, until);
|
|
1024
|
+
// Ranks of degree 2 where the linear one leaves turns or the kernel refuses it (while i < N·M: N·M − i, where the runs
|
|
1025
|
+
// alone give 299 − i): one product of two variables at a time. The products the loop's own terms read first (a rank
|
|
1026
|
+
// of degree 2 measures what its terms of degree 2 bound), before the invariants; the others after them, within a
|
|
1027
|
+
// quarter of the time.
|
|
1028
|
+
const read = productsIn([l.while, l.body, l.state, l.pre ?? yes, l.assume ?? yes]).filter(([x, y]) => names.includes(x) && names.includes(y));
|
|
1029
|
+
const rest = products(names).filter(p => !read.some(r => id(r) === id(p)));
|
|
1030
|
+
const seen = new Set();
|
|
1031
|
+
const quadratic = (ps, until) => {
|
|
1032
|
+
const out = [];
|
|
1033
|
+
for (const p of ps) {
|
|
1034
|
+
if (performance.now() > until)
|
|
1035
|
+
break;
|
|
1036
|
+
const d = withProducts([node], samples, [...edges.values()], [p]), r = lexRanks([0], d.nodes, d.samples, d.edges, shared, until);
|
|
1037
|
+
if (!r.comps.length || r.left)
|
|
1038
|
+
continue;
|
|
1039
|
+
const r2 = r.comps.map(c => compE(c, 0, d.nodes[0], shared));
|
|
1040
|
+
if (seen.has(id(r2)))
|
|
1041
|
+
continue;
|
|
1042
|
+
seen.add(id(r2));
|
|
1043
|
+
const verdict = withDeadline(until, () => goalsProven(loopObligations(l, laws, r2)));
|
|
1044
|
+
if (trace)
|
|
1045
|
+
console.error(`[cycles] ${l.name}: degree 2 with ${featName(p)}: (${r2.map(showE).join(', ')}) ${'trees' in verdict ? 'proven' : verdict.goal}`);
|
|
1046
|
+
out.push({ rank: r2, verdict });
|
|
1047
|
+
if ('trees' in verdict)
|
|
1048
|
+
break;
|
|
1049
|
+
}
|
|
1050
|
+
return out;
|
|
1051
|
+
};
|
|
1052
|
+
const done = (rank, trees, more = [], texts = []) => ({ proven: true, checked: true, rank, laws: more, found: { rank: `(${rank.map(showE).join(', ')})`, laws: texts }, trees });
|
|
1053
|
+
// The linear rank first, then those of degree 2 over the products the loop reads, each checked in turn.
|
|
1054
|
+
const linear = comps.map(c => compE(c, 0, node, shared));
|
|
1055
|
+
let tried = [];
|
|
1056
|
+
if (comps.length && !left) {
|
|
1057
|
+
seen.add(id(linear));
|
|
1058
|
+
tried.push({ rank: linear, verdict: withDeadline(until, () => goalsProven(loopObligations(l, laws, linear))) });
|
|
1059
|
+
}
|
|
1060
|
+
tried.push(...quadratic(read, until));
|
|
1061
|
+
const won = tried.find(t => 'trees' in t.verdict);
|
|
1062
|
+
if (won && 'trees' in won.verdict)
|
|
1063
|
+
return done(won.rank, won.verdict.trees);
|
|
1064
|
+
if (!tried.length && comps.length)
|
|
1065
|
+
tried.push({ rank: linear, verdict: withDeadline(until, () => goalsProven(loopObligations(l, laws, linear))) });
|
|
1066
|
+
// Invariants the rank needs: the descent as the loop's step laws, inferLaws's candidates chosen by its counterexamples
|
|
1067
|
+
// (within half the time left where products remain to be tried after them).
|
|
1068
|
+
const base = tried[0];
|
|
1069
|
+
if (base && !('trees' in base.verdict)) {
|
|
1070
|
+
const rank = base.rank, m = loopMachine(l), { stay, cover, helps } = livenessParts(m, loopLiveness(l, rank));
|
|
1071
|
+
const ms = (until - performance.now()) * (rest.length ? 0.5 : 1);
|
|
1072
|
+
if (ms > 300) {
|
|
1073
|
+
const inf = inferLaws(m, laws, [stay, cover, ...helps], { ms });
|
|
1074
|
+
const kept = inf.found.filter(f => !f.law.forall && !f.law.keys?.length), more = kept.map(f => f.law);
|
|
1075
|
+
if (inf.proven && more.length) {
|
|
1076
|
+
const all = [...laws, ...more], again = withDeadline(until + 5000, () => goalsProven(loopObligations(l, all, rank)));
|
|
1077
|
+
if ('trees' in again)
|
|
1078
|
+
return done(rank, again.trees, more, kept.map(f => f.text));
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
1082
|
+
// (the products the loop doesn't read within a quarter of its time: a search that fails tries each of them)
|
|
1083
|
+
tried = quadratic(rest, Math.min(until, performance.now() + (opts.ms ?? 10_000) / 4));
|
|
1084
|
+
const late = tried.find(t => 'trees' in t.verdict);
|
|
1085
|
+
if (late && 'trees' in late.verdict)
|
|
1086
|
+
return done(late.rank, late.verdict.trees);
|
|
1087
|
+
if (!base || 'trees' in base.verdict)
|
|
1088
|
+
return { proven: false, checked: false, why: `no rank, linear or of degree 2, in order, drops on every turn of ${edges.size} sampled` };
|
|
1089
|
+
const text = `(${base.rank.map(showE).join(', ')})`, first = base.verdict;
|
|
1090
|
+
return { proven: false, checked: false, rank: base.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` };
|
|
1091
|
+
}
|
|
1092
|
+
export function findLeadsTo(m, laws, l, sample, opts = {}) {
|
|
1093
|
+
const t0 = performance.now(), until = t0 + (opts.ms ?? 10_000), rand = seeded(29), run = interpret(m);
|
|
1094
|
+
const names = [...Object.keys(m.state), ...m.params].sort(), helpful = livenessHelpful(l);
|
|
1095
|
+
const node = { name: l.name, hyps: [], concl: yes, vars: names, level: 0, given: true };
|
|
1096
|
+
const env = (ps, st) => ({ ...ps, ...st.scalars });
|
|
1097
|
+
const holds = (c, ps, st) => { try {
|
|
1098
|
+
return evalMC(c, env(ps, st), st, m);
|
|
1099
|
+
}
|
|
1100
|
+
catch {
|
|
1101
|
+
return undefined;
|
|
1102
|
+
} };
|
|
1103
|
+
const pending = (ps, st) => holds(l.from, ps, st) === true && holds(l.to, ps, st) === false;
|
|
1104
|
+
const points = new Map(), strong = new Map(), weak = new Map();
|
|
1105
|
+
for (const bound of [2, 4, 8, 16])
|
|
1106
|
+
for (let trial = 0; trial < 20 && performance.now() < t0 + 1000; trial++) {
|
|
1107
|
+
let ps;
|
|
1108
|
+
try {
|
|
1109
|
+
ps = sample.params(rand, bound);
|
|
1110
|
+
}
|
|
1111
|
+
catch {
|
|
1112
|
+
continue;
|
|
1113
|
+
}
|
|
1114
|
+
if (m.pre && holds(m.pre, ps, { scalars: {}, maps: {} }) !== true)
|
|
1115
|
+
continue;
|
|
1116
|
+
let st;
|
|
1117
|
+
try {
|
|
1118
|
+
st = run.init(ps);
|
|
1119
|
+
}
|
|
1120
|
+
catch {
|
|
1121
|
+
continue;
|
|
1122
|
+
}
|
|
1123
|
+
for (let turn = 0; turn < 120; turn++) {
|
|
1124
|
+
const here = env(ps, st), was = pending(ps, st);
|
|
1125
|
+
if (was)
|
|
1126
|
+
points.set(id(here), here);
|
|
1127
|
+
// A helpful request in its region half the time (its named inputs at the state, the rest drawn), else any.
|
|
1128
|
+
const regions = helpful.filter(h => holds(h.when, ps, st) === true), h = regions.length && rand(2) ? regions[rand(regions.length)] : undefined;
|
|
1129
|
+
let next;
|
|
1130
|
+
for (let tries = 0; tries < 20 && !next; tries++) {
|
|
1131
|
+
try {
|
|
1132
|
+
const input = sample.input(rand, bound, ps);
|
|
1133
|
+
if (h)
|
|
1134
|
+
for (const [x, t] of Object.entries(h.request))
|
|
1135
|
+
input[x] = evalME(t, here, st, m);
|
|
1136
|
+
next = run.step(ps, st, input);
|
|
1137
|
+
}
|
|
1138
|
+
catch { /* refused: another */ }
|
|
1139
|
+
}
|
|
1140
|
+
if (!next)
|
|
1141
|
+
break;
|
|
1142
|
+
if (was && pending(ps, next)) {
|
|
1143
|
+
const e = { from: 0, to: 0, s: here, t: env(ps, next) };
|
|
1144
|
+
(h ? strong : weak).set(id([e.s, e.t]), e);
|
|
1145
|
+
}
|
|
1146
|
+
st = next;
|
|
1147
|
+
}
|
|
1148
|
+
}
|
|
1149
|
+
const samples = [[...points.values()].map(env => ({ env, calls: new Set() }))];
|
|
1150
|
+
const { comps, left } = lexRanks([0], [node], samples, [...strong.values()], shared, until, false, [...weak.values()]);
|
|
1151
|
+
if (!comps.length || left)
|
|
1152
|
+
return { proven: false, checked: false, why: `no linear rank, in order, that ${strong.size} helpful steps sampled lower and ${weak.size} others keep` };
|
|
1153
|
+
const rank = comps.map(c => compE(c, 0, node, shared)), text = `(${rank.map(showE).join(', ')})`, lv = { ...l, rank };
|
|
1154
|
+
const first = withDeadline(until, () => proveLiveness(m, laws, lv));
|
|
1155
|
+
if (first.proven && first.checked)
|
|
1156
|
+
return { proven: true, checked: true, rank, laws: [], found: { rank: text, laws: [] } };
|
|
815
1157
|
const ms = until - performance.now();
|
|
816
1158
|
if (ms > 300) {
|
|
817
|
-
const inf = inferLaws(m, laws, [stay, cover, ...helps], { ms });
|
|
1159
|
+
const { stay, cover, helps } = livenessParts(m, lv), inf = inferLaws(m, laws, [stay, cover, ...helps], { ms });
|
|
818
1160
|
const kept = inf.found.filter(f => !f.law.forall && !f.law.keys?.length), more = kept.map(f => f.law);
|
|
819
1161
|
if (inf.proven && more.length) {
|
|
820
|
-
const
|
|
821
|
-
if (
|
|
822
|
-
return { proven: true, checked: true, rank, laws: more, found: { rank: text, laws: kept.map(f => f.text) }
|
|
1162
|
+
const again = withDeadline(until + 5000, () => proveLiveness(m, [...laws, ...more], lv));
|
|
1163
|
+
if (again.proven && again.checked)
|
|
1164
|
+
return { proven: true, checked: true, rank, laws: more, found: { rank: text, laws: kept.map(f => f.text) } };
|
|
823
1165
|
}
|
|
824
1166
|
}
|
|
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;
|
|
1167
|
+
return { proven: false, checked: false, rank, found: { rank: text, laws: [] }, ...(first.failed ? { goal: first.failed.goal, stuck: first.failed.stuck, ...(first.failed.witness ? { witness: first.failed.witness } : {}) } : {}), why: `rank ${text} from the runs; the kernel's goals not proven with it` };
|
|
826
1168
|
}
|
|
827
1169
|
export function cycleLemmas(m, laws, stepLaws = [], opts = {}) {
|
|
828
1170
|
const until = performance.now() + (opts.ms ?? 10_000);
|
|
829
1171
|
const fns = Object.entries(m.fns ?? {}).filter(([, f]) => !f.recur && (f.arity ?? 1) === 1);
|
|
830
|
-
|
|
1172
|
+
const fns2 = Object.entries(m.fns ?? {}).filter(([, f]) => !f.recur && f.arity === 2 && f.call);
|
|
1173
|
+
if ((!fns.length && !fns2.length) || !m.facts?.length)
|
|
831
1174
|
return { proven: false, checked: false, found: [], why: 'no function known by its facts' };
|
|
832
1175
|
const facts = m.facts.map(f => ({ name: f.name, keys: [...(f.forall ? [f.forall] : []), ...(f.keys ?? [])], holds: f.holds }));
|
|
833
1176
|
const values = {};
|
|
834
1177
|
for (const [name, f] of fns)
|
|
835
1178
|
values[name] = (q) => fnValue(f, q);
|
|
1179
|
+
for (const [name, f] of fns2) {
|
|
1180
|
+
const g = compileFunction([], `return (${f.call})`)();
|
|
1181
|
+
values[name] = (a, b) => g(a, b);
|
|
1182
|
+
}
|
|
836
1183
|
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 = [];
|
|
1184
|
+
const big = Math.max(24, ...ts), x = 'k$', y = 'j$', claims = [];
|
|
838
1185
|
for (const [name] of fns) {
|
|
839
1186
|
const f = values[name];
|
|
840
1187
|
if (!f)
|
|
@@ -872,14 +1219,72 @@ export function cycleLemmas(m, laws, stepLaws = [], opts = {}) {
|
|
|
872
1219
|
if (vals.slice(vals.length >> 1).includes(top))
|
|
873
1220
|
claims.push({ name: `${name}(k) ≤ ${top}`, concl: le(read, k(top)) });
|
|
874
1221
|
}
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
1222
|
+
// A function of two arguments: over a grid of both, the line that fits exactly, and each bound by 0, either argument or
|
|
1223
|
+
// their sum plus the tightest constant (above, and below where it is one): a constant above only where the values
|
|
1224
|
+
// reach it below half the grid in both arguments (not the grid's own size).
|
|
1225
|
+
for (const [name] of fns2) {
|
|
1226
|
+
const f = values[name], B = Math.min(big, 24), pts = [];
|
|
1227
|
+
for (let a = 0; a <= B; a++)
|
|
1228
|
+
for (let b = 0; b <= B; b++) {
|
|
1229
|
+
let z;
|
|
1230
|
+
try {
|
|
1231
|
+
z = f(a, b);
|
|
1232
|
+
}
|
|
1233
|
+
catch {
|
|
1234
|
+
continue;
|
|
1235
|
+
}
|
|
1236
|
+
if (Number.isSafeInteger(z) && z >= 0)
|
|
1237
|
+
pts.push({ env: { [x]: a, [y]: b }, value: z });
|
|
1238
|
+
}
|
|
1239
|
+
if (pts.length < 16)
|
|
1240
|
+
continue;
|
|
1241
|
+
const read = get(name, argsKey([v(x), v(y)])), show = (l, c) => showE(linE(Object.fromEntries(Object.entries(l).map(([n, q]) => [n === x ? 'k' : 'j', q])), c));
|
|
1242
|
+
const line = fitExact([x, y], pts, until);
|
|
1243
|
+
if (line) {
|
|
1244
|
+
claims.push({ name: `${name}(k, j) = ${show(line.coef, line.c)}`, concl: eqC(read, linE(line.coef, line.c)) });
|
|
1245
|
+
continue;
|
|
1246
|
+
}
|
|
1247
|
+
for (const form of [{}, { [x]: 1 }, { [y]: 1 }, { [x]: 1, [y]: 1 }]) {
|
|
1248
|
+
const at = (p) => Object.entries(form).reduce((acc, [n, q]) => acc + q * p.env[n], 0);
|
|
1249
|
+
const d = Math.max(...pts.map(p => p.value - at(p))), lo = Math.min(...pts.map(p => p.value - at(p)));
|
|
1250
|
+
const reached = pts.some(p => p.value - at(p) === d && p.env[x] <= B / 2 && p.env[y] <= B / 2);
|
|
1251
|
+
if (Object.keys(form).length ? d <= 0 || reached : reached)
|
|
1252
|
+
claims.push({ name: `${name}(k, j) ≤ ${show(form, d)}`, concl: d >= 0 ? le(read, linE(form, d)) : le(add(read, k(-d)), linE(form, 0)) });
|
|
1253
|
+
if (Object.keys(form).length && lo >= 0)
|
|
1254
|
+
claims.push({ name: `${name}(k, j) ≥ ${show(form, lo)}`, concl: le(linE(form, lo), read) });
|
|
1255
|
+
}
|
|
1256
|
+
}
|
|
1257
|
+
// Those of two arguments each proven alone (their keys are pairings, products: a goal that reads several such claims
|
|
1258
|
+
// at once is slow to refute), the rest together; the proof the nodes of each, side by side (no node of one uses
|
|
1259
|
+
// another's).
|
|
1260
|
+
const unique = [...new Map(claims.map(c => [c.name, c])).values()], two = (c) => m.fns?.[readsOf(c.concl)[0]?.map ?? '']?.arity === 2;
|
|
1261
|
+
const proofs = new Map();
|
|
1262
|
+
for (const c of unique.filter(two)) {
|
|
1263
|
+
if (performance.now() >= until)
|
|
1264
|
+
break;
|
|
1265
|
+
const o = findCycles(facts, [c], { ms: Math.min(1000, (until - performance.now()) * 0.1), values, houdini: true, generalize: false });
|
|
1266
|
+
if (o.proven && o.checked && o.proof)
|
|
1267
|
+
proofs.set(c.name, o.proof);
|
|
1268
|
+
}
|
|
1269
|
+
const ones = unique.filter(c => !two(c));
|
|
1270
|
+
if (!ones.length && !proofs.size)
|
|
1271
|
+
return { proven: false, checked: false, found: [], why: unique.length ? 'no lemma proven' : 'no lemma fits the values' };
|
|
1272
|
+
const out = ones.length ? findCycles(facts, ones, { ms: Math.max(0, until - performance.now()) * 0.6, values, houdini: true, generalize: false }) : { proven: true, checked: true, kept: [] };
|
|
1273
|
+
if ((!out.proven || !out.checked || !out.kept?.length) && !proofs.size)
|
|
880
1274
|
return { proven: false, checked: false, found: [], why: out.why ?? 'no lemma proven' };
|
|
881
|
-
const
|
|
882
|
-
|
|
1275
|
+
const kept = [...(out.proven && out.checked ? out.kept ?? [] : []), ...proofs.keys()];
|
|
1276
|
+
const sideBySide = (ps) => {
|
|
1277
|
+
if (!ps.length)
|
|
1278
|
+
return undefined;
|
|
1279
|
+
let at = 0;
|
|
1280
|
+
const nodes = ps.flatMap(p => { const off = at; at += p.nodes.length; return p.nodes.map(n => ({ ...n, uses: n.uses.map(u => ({ ...u, node: u.node + off })) })); });
|
|
1281
|
+
return { facts: ps[0].facts, nodes };
|
|
1282
|
+
};
|
|
1283
|
+
const asLaw = (c) => {
|
|
1284
|
+
const holds = c.hyps?.length ? { op: 'or', xs: [{ op: 'not', x: and(...c.hyps) }, c.concl] } : c.concl;
|
|
1285
|
+
return { law: { name: c.name, forall: x, ...(varsOf(holds).includes(y) ? { keys: [y] } : {}), holds }, text: c.name };
|
|
1286
|
+
};
|
|
1287
|
+
let found = unique.filter(c => kept.includes(c.name)).map(asLaw);
|
|
883
1288
|
const proveWith = (fs) => {
|
|
884
1289
|
const mm = { ...m, facts: [...m.facts, ...fs.map(f => f.law)] };
|
|
885
1290
|
const r = withDeadline(until, () => (stepLaws.length ? proveStepLaws(mm, laws, stepLaws) : proveLaws(mm, laws)));
|
|
@@ -895,5 +1300,7 @@ export function cycleLemmas(m, laws, stepLaws = [], opts = {}) {
|
|
|
895
1300
|
if (again.ok)
|
|
896
1301
|
(found = rest, best = again);
|
|
897
1302
|
}
|
|
898
|
-
|
|
1303
|
+
const merged = sideBySide([...(out.proof && found.some(f => ones.some(c => c.name === f.text)) ? [out.proof] : []), ...found.flatMap(f => (proofs.has(f.text) ? [proofs.get(f.text)] : []))]);
|
|
1304
|
+
const proof = merged && checkCyclic(merged) ? merged : undefined;
|
|
1305
|
+
return { proven: true, checked: true, machine: best.machine, found, ...(proof ? { proof } : {}) };
|
|
899
1306
|
}
|