gdp-wired 0.18.0 → 0.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/core/cycles.d.ts +23 -2
- package/dist/core/cycles.js +451 -51
- package/dist/core/cyclic.d.ts +105 -7
- package/dist/core/cyclic.js +800 -103
- package/dist/core/data.d.ts +61 -5
- package/dist/core/data.js +487 -113
- package/dist/core/discover.js +54 -33
- package/dist/core/dsl.d.ts +4 -2
- package/dist/core/dsl.js +47 -12
- package/dist/core/solver.d.ts +3 -0
- package/dist/core/solver.js +5 -0
- package/dist/core/translate.d.ts +30 -0
- package/dist/core/translate.js +578 -0
- package/dist/kernel/prove.d.ts +16 -0
- package/dist/kernel/prove.js +160 -13
- package/dist/lean-kernel/emit.d.ts +36 -1
- package/dist/lean-kernel/emit.js +527 -85
- package/dist/lean-kernel/library.js +2 -1
- package/package.json +1 -1
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,44 @@ 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
|
-
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);
|
|
628
|
+
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));
|
|
629
|
+
// 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
|
|
630
|
+
// each sample the product's, one product at a time (a sparse template: with every product at once, the runs fit a rank
|
|
631
|
+
// of many small parts that no turn past them keeps). The rank found is linear in it, the product read back as the term
|
|
632
|
+
// 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
|
|
633
|
+
// products of rows and squares, Positivstellensatz of degree 2, where they need more).
|
|
634
|
+
const featSep = '·';
|
|
635
|
+
const products = (names) => names.flatMap((a, i) => names.slice(i).map(b => [a, b]));
|
|
636
|
+
const featName = ([a, b]) => `${a}${featSep}${b}`;
|
|
637
|
+
const withFeat = (env, p) => (p && env[p[0]] !== undefined && env[p[1]] !== undefined ? { ...env, [featName(p)]: env[p[0]] * env[p[1]] } : env);
|
|
638
|
+
// (linE builds sums, differences and multiples of variables and constants only)
|
|
639
|
+
const unfeat = (e) => {
|
|
640
|
+
if (e.op === 'var') {
|
|
641
|
+
const [a, b] = e.name.split(featSep);
|
|
642
|
+
return b === undefined ? e : { op: 'prod', a: v(a), b: v(b) };
|
|
643
|
+
}
|
|
644
|
+
if (e.op === 'add' || e.op === 'sub')
|
|
645
|
+
return { ...e, a: unfeat(e.a), b: unfeat(e.b) };
|
|
646
|
+
return e.op === 'mul' ? { ...e, a: unfeat(e.a) } : e;
|
|
647
|
+
};
|
|
648
|
+
// The products of two variables a term reads, each as its sorted pair.
|
|
649
|
+
const productsIn = (t) => {
|
|
650
|
+
const out = new Map();
|
|
651
|
+
walk(t, o => { if (o.op === 'prod') {
|
|
652
|
+
const a = o.a, b = o.b;
|
|
653
|
+
if (a.op === 'var' && b.op === 'var') {
|
|
654
|
+
const p = [a.name, b.name].sort();
|
|
655
|
+
out.set(id(p), p);
|
|
656
|
+
}
|
|
657
|
+
} });
|
|
658
|
+
return [...out.values()];
|
|
659
|
+
};
|
|
660
|
+
// The nodes, samples and edges with one product of two variables per node a variable (none where `ps[i]` is undefined).
|
|
661
|
+
const withProducts = (nodes, samples, edges, ps) => ({
|
|
662
|
+
nodes: nodes.map((n, i) => (ps[i] ? { ...n, vars: [...n.vars, featName(ps[i])] } : n)),
|
|
663
|
+
samples: samples.map((ss, i) => ss.map(s => ({ ...s, env: withFeat(s.env, ps[i]) }))),
|
|
664
|
+
edges: edges.map(e => ({ ...e, s: withFeat(e.s, ps[e.from]), t: withFeat(e.t, ps[e.to]) })),
|
|
665
|
+
});
|
|
462
666
|
// Strongly connected components (Tarjan), each node's component's index.
|
|
463
667
|
function sccs(graph) {
|
|
464
668
|
const index = [], low = [], on = [], stack = [], comp = [];
|
|
@@ -490,7 +694,8 @@ function sccs(graph) {
|
|
|
490
694
|
visit(u);
|
|
491
695
|
return comp;
|
|
492
696
|
}
|
|
493
|
-
|
|
697
|
+
// `quad`: ranks of degree 2, the product of those two variables a further one of each node that has both.
|
|
698
|
+
function build(facts, cx, nodes, depth, big, until, quad) {
|
|
494
699
|
const inst = nodes.map(n => instancesFor(facts, cx, n, depth));
|
|
495
700
|
const samples = [];
|
|
496
701
|
for (const n of nodes) {
|
|
@@ -539,21 +744,23 @@ function build(facts, cx, nodes, depth, big, until) {
|
|
|
539
744
|
parts.set(i, []);
|
|
540
745
|
continue;
|
|
541
746
|
}
|
|
542
|
-
|
|
747
|
+
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 };
|
|
748
|
+
let best = { ...lexRanks(idx, q.nodes, q.samples, q.edges, shared, until, !!quad), name: shared };
|
|
543
749
|
if (best.left && idx.length > 1 && idx.length <= 4) {
|
|
544
|
-
const o = lexRanks(idx, nodes, samples,
|
|
750
|
+
const o = lexRanks(idx, q.nodes, q.samples, q.edges, own(q.nodes), until, !!quad);
|
|
545
751
|
if (o.left < best.left)
|
|
546
|
-
best = { ...o, name: own(nodes) };
|
|
752
|
+
best = { ...o, name: own(q.nodes) };
|
|
547
753
|
}
|
|
548
754
|
for (const i of idx)
|
|
549
|
-
parts.set(i, best.comps.map(cp => compE(cp, i, nodes[i], best.name)));
|
|
755
|
+
parts.set(i, best.comps.map(cp => compE(cp, i, q.nodes[i], best.name)));
|
|
550
756
|
}
|
|
551
757
|
const width = Math.max(0, ...[...parts.values()].map(p => p.length)), tiers = graph.some(g => g.length);
|
|
552
758
|
const ranks = nodes.map((_, i) => [...(tiers ? [k(heightOf(comp[i]))] : []), ...parts.get(i), ...Array.from({ length: width - parts.get(i).length }, () => k(0))]);
|
|
553
759
|
let uses = 0;
|
|
554
760
|
const specs = nodes.map((n, i) => ({
|
|
555
761
|
name: n.name, hyps: n.hyps, concl: n.concl, rank: ranks[i], insts: inst[i].insts,
|
|
556
|
-
|
|
762
|
+
// (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)
|
|
763
|
+
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
764
|
uses++;
|
|
558
765
|
const m = nodes[c.node], s = vs(c.subst);
|
|
559
766
|
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 +879,8 @@ export function findCycles(facts, claims, opts = {}) {
|
|
|
672
879
|
const big = Math.max(0, ...constsOf([facts.map(f => f.holds), claims]).filter(c => c <= 1000));
|
|
673
880
|
const given = claims.map(c => nodeOf(c, 1, !opts.houdini));
|
|
674
881
|
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));
|
|
882
|
+
const once = (nodes, depth, quad) => {
|
|
883
|
+
const b = withDeadline(until, () => build(facts, cx, nodes, depth, big, until, quad));
|
|
677
884
|
if ('broken' in b) {
|
|
678
885
|
const at = nodes.find(n => n.name === b.broken.node);
|
|
679
886
|
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 +902,10 @@ export function findCycles(facts, claims, opts = {}) {
|
|
|
695
902
|
};
|
|
696
903
|
// Houdini over the nodes that may be dropped (each that fails, dropped, the rest proven again); then each companion
|
|
697
904
|
// dropped in turn where the rest are proven without it (only what the claims need is kept).
|
|
698
|
-
const attempt = (start, depth) => {
|
|
905
|
+
const attempt = (start, depth, quad) => {
|
|
699
906
|
let nodes = start, done;
|
|
700
907
|
for (let round = 0; round <= start.length && nodes.length && performance.now() < until && !done; round++) {
|
|
701
|
-
const t = once(nodes, depth);
|
|
908
|
+
const t = once(nodes, depth, quad);
|
|
702
909
|
if ('proven' in t) {
|
|
703
910
|
done = t.proven;
|
|
704
911
|
break;
|
|
@@ -714,14 +921,16 @@ export function findCycles(facts, claims, opts = {}) {
|
|
|
714
921
|
for (const c of nodes.filter(n => !n.given)) {
|
|
715
922
|
if (performance.now() >= until)
|
|
716
923
|
break;
|
|
717
|
-
const rest = nodes.filter(n => n !== c), t = once(rest, depth);
|
|
924
|
+
const rest = nodes.filter(n => n !== c), t = once(rest, depth, quad);
|
|
718
925
|
if ('proven' in t && t.proven.checked)
|
|
719
926
|
(nodes = rest, done = t.proven);
|
|
720
927
|
}
|
|
721
928
|
return done;
|
|
722
929
|
};
|
|
723
930
|
// 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
|
|
931
|
+
// variables; companions beside them by analogy, then every bound; then ranks of degree 2, over each product of two
|
|
932
|
+
// variables the claims read (up(i, n) = up(i + 1, n) while i < n·n: n·n − i).
|
|
933
|
+
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
934
|
const tries = [
|
|
726
935
|
[depth, () => given], [depth + 1, () => given],
|
|
727
936
|
...(opts.generalize === false ? [] : [
|
|
@@ -729,16 +938,17 @@ export function findCycles(facts, claims, opts = {}) {
|
|
|
729
938
|
[depth + 1, () => [...given, ...beside(cx, facts, given, big, until, true)]],
|
|
730
939
|
[depth + 1, () => [...given, ...beside(cx, facts, given, big, until, false)]],
|
|
731
940
|
]),
|
|
941
|
+
...quads.flatMap(p => [[depth, () => given, p], [depth + 1, () => given, p]]),
|
|
732
942
|
];
|
|
733
943
|
const tried = new Set();
|
|
734
|
-
for (const [d, t] of tries) {
|
|
944
|
+
for (const [d, t, quad] of tries) {
|
|
735
945
|
if (performance.now() >= until)
|
|
736
946
|
break;
|
|
737
947
|
const nodes = t();
|
|
738
|
-
if (tried.has(id([d, nodes.map(n => n.name)])))
|
|
948
|
+
if (tried.has(id([d, nodes.map(n => n.name), quad])))
|
|
739
949
|
continue;
|
|
740
|
-
tried.add(id([d, nodes.map(n => n.name)]));
|
|
741
|
-
const out = attempt(nodes, d);
|
|
950
|
+
tried.add(id([d, nodes.map(n => n.name), quad]));
|
|
951
|
+
const out = attempt(nodes, d, quad);
|
|
742
952
|
if (out.proven || out.stuck === 'sat' && out.why)
|
|
743
953
|
return out;
|
|
744
954
|
last = out;
|
|
@@ -803,38 +1013,168 @@ export function findRank(l, laws = [], opts = {}) {
|
|
|
803
1013
|
}
|
|
804
1014
|
}
|
|
805
1015
|
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
|
|
1016
|
+
const { comps, left } = lexRanks([0], [node], samples, [...edges.values()], shared, until);
|
|
1017
|
+
// 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
|
|
1018
|
+
// alone give 299 − i): one product of two variables at a time. The products the loop's own terms read first (a rank
|
|
1019
|
+
// of degree 2 measures what its terms of degree 2 bound), before the invariants; the others after them, within a
|
|
1020
|
+
// quarter of the time.
|
|
1021
|
+
const read = productsIn([l.while, l.body, l.state, l.pre ?? yes, l.assume ?? yes]).filter(([x, y]) => names.includes(x) && names.includes(y));
|
|
1022
|
+
const rest = products(names).filter(p => !read.some(r => id(r) === id(p)));
|
|
1023
|
+
const seen = new Set();
|
|
1024
|
+
const quadratic = (ps, until) => {
|
|
1025
|
+
const out = [];
|
|
1026
|
+
for (const p of ps) {
|
|
1027
|
+
if (performance.now() > until)
|
|
1028
|
+
break;
|
|
1029
|
+
const d = withProducts([node], samples, [...edges.values()], [p]), r = lexRanks([0], d.nodes, d.samples, d.edges, shared, until);
|
|
1030
|
+
if (!r.comps.length || r.left)
|
|
1031
|
+
continue;
|
|
1032
|
+
const r2 = r.comps.map(c => compE(c, 0, d.nodes[0], shared));
|
|
1033
|
+
if (seen.has(id(r2)))
|
|
1034
|
+
continue;
|
|
1035
|
+
seen.add(id(r2));
|
|
1036
|
+
const verdict = withDeadline(until, () => goalsProven(loopObligations(l, laws, r2)));
|
|
1037
|
+
if (trace)
|
|
1038
|
+
console.error(`[cycles] ${l.name}: degree 2 with ${featName(p)}: (${r2.map(showE).join(', ')}) ${'trees' in verdict ? 'proven' : verdict.goal}`);
|
|
1039
|
+
out.push({ rank: r2, verdict });
|
|
1040
|
+
if ('trees' in verdict)
|
|
1041
|
+
break;
|
|
1042
|
+
}
|
|
1043
|
+
return out;
|
|
1044
|
+
};
|
|
1045
|
+
const done = (rank, trees, more = [], texts = []) => ({ proven: true, checked: true, rank, laws: more, found: { rank: `(${rank.map(showE).join(', ')})`, laws: texts }, trees });
|
|
1046
|
+
// The linear rank first, then those of degree 2 over the products the loop reads, each checked in turn.
|
|
1047
|
+
const linear = comps.map(c => compE(c, 0, node, shared));
|
|
1048
|
+
let tried = [];
|
|
1049
|
+
if (comps.length && !left) {
|
|
1050
|
+
seen.add(id(linear));
|
|
1051
|
+
tried.push({ rank: linear, verdict: withDeadline(until, () => goalsProven(loopObligations(l, laws, linear))) });
|
|
1052
|
+
}
|
|
1053
|
+
tried.push(...quadratic(read, until));
|
|
1054
|
+
const won = tried.find(t => 'trees' in t.verdict);
|
|
1055
|
+
if (won && 'trees' in won.verdict)
|
|
1056
|
+
return done(won.rank, won.verdict.trees);
|
|
1057
|
+
if (!tried.length && comps.length)
|
|
1058
|
+
tried.push({ rank: linear, verdict: withDeadline(until, () => goalsProven(loopObligations(l, laws, linear))) });
|
|
1059
|
+
// Invariants the rank needs: the descent as the loop's step laws, inferLaws's candidates chosen by its counterexamples
|
|
1060
|
+
// (within half the time left where products remain to be tried after them).
|
|
1061
|
+
const base = tried[0];
|
|
1062
|
+
if (base && !('trees' in base.verdict)) {
|
|
1063
|
+
const rank = base.rank, m = loopMachine(l), { stay, cover, helps } = livenessParts(m, loopLiveness(l, rank));
|
|
1064
|
+
const ms = (until - performance.now()) * (rest.length ? 0.5 : 1);
|
|
1065
|
+
if (ms > 300) {
|
|
1066
|
+
const inf = inferLaws(m, laws, [stay, cover, ...helps], { ms });
|
|
1067
|
+
const kept = inf.found.filter(f => !f.law.forall && !f.law.keys?.length), more = kept.map(f => f.law);
|
|
1068
|
+
if (inf.proven && more.length) {
|
|
1069
|
+
const all = [...laws, ...more], again = withDeadline(until + 5000, () => goalsProven(loopObligations(l, all, rank)));
|
|
1070
|
+
if ('trees' in again)
|
|
1071
|
+
return done(rank, again.trees, more, kept.map(f => f.text));
|
|
1072
|
+
}
|
|
1073
|
+
}
|
|
1074
|
+
}
|
|
1075
|
+
// (the products the loop doesn't read within a quarter of its time: a search that fails tries each of them)
|
|
1076
|
+
tried = quadratic(rest, Math.min(until, performance.now() + (opts.ms ?? 10_000) / 4));
|
|
1077
|
+
const late = tried.find(t => 'trees' in t.verdict);
|
|
1078
|
+
if (late && 'trees' in late.verdict)
|
|
1079
|
+
return done(late.rank, late.verdict.trees);
|
|
1080
|
+
if (!base || 'trees' in base.verdict)
|
|
1081
|
+
return { proven: false, checked: false, why: `no rank, linear or of degree 2, in order, drops on every turn of ${edges.size} sampled` };
|
|
1082
|
+
const text = `(${base.rank.map(showE).join(', ')})`, first = base.verdict;
|
|
1083
|
+
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` };
|
|
1084
|
+
}
|
|
1085
|
+
export function findLeadsTo(m, laws, l, sample, opts = {}) {
|
|
1086
|
+
const t0 = performance.now(), until = t0 + (opts.ms ?? 10_000), rand = seeded(29), run = interpret(m);
|
|
1087
|
+
const names = [...Object.keys(m.state), ...m.params].sort(), helpful = livenessHelpful(l);
|
|
1088
|
+
const node = { name: l.name, hyps: [], concl: yes, vars: names, level: 0, given: true };
|
|
1089
|
+
const env = (ps, st) => ({ ...ps, ...st.scalars });
|
|
1090
|
+
const holds = (c, ps, st) => { try {
|
|
1091
|
+
return evalMC(c, env(ps, st), st, m);
|
|
1092
|
+
}
|
|
1093
|
+
catch {
|
|
1094
|
+
return undefined;
|
|
1095
|
+
} };
|
|
1096
|
+
const pending = (ps, st) => holds(l.from, ps, st) === true && holds(l.to, ps, st) === false;
|
|
1097
|
+
const points = new Map(), strong = new Map(), weak = new Map();
|
|
1098
|
+
for (const bound of [2, 4, 8, 16])
|
|
1099
|
+
for (let trial = 0; trial < 20 && performance.now() < t0 + 1000; trial++) {
|
|
1100
|
+
let ps;
|
|
1101
|
+
try {
|
|
1102
|
+
ps = sample.params(rand, bound);
|
|
1103
|
+
}
|
|
1104
|
+
catch {
|
|
1105
|
+
continue;
|
|
1106
|
+
}
|
|
1107
|
+
if (m.pre && holds(m.pre, ps, { scalars: {}, maps: {} }) !== true)
|
|
1108
|
+
continue;
|
|
1109
|
+
let st;
|
|
1110
|
+
try {
|
|
1111
|
+
st = run.init(ps);
|
|
1112
|
+
}
|
|
1113
|
+
catch {
|
|
1114
|
+
continue;
|
|
1115
|
+
}
|
|
1116
|
+
for (let turn = 0; turn < 120; turn++) {
|
|
1117
|
+
const here = env(ps, st), was = pending(ps, st);
|
|
1118
|
+
if (was)
|
|
1119
|
+
points.set(id(here), here);
|
|
1120
|
+
// A helpful request in its region half the time (its named inputs at the state, the rest drawn), else any.
|
|
1121
|
+
const regions = helpful.filter(h => holds(h.when, ps, st) === true), h = regions.length && rand(2) ? regions[rand(regions.length)] : undefined;
|
|
1122
|
+
let next;
|
|
1123
|
+
for (let tries = 0; tries < 20 && !next; tries++) {
|
|
1124
|
+
try {
|
|
1125
|
+
const input = sample.input(rand, bound, ps);
|
|
1126
|
+
if (h)
|
|
1127
|
+
for (const [x, t] of Object.entries(h.request))
|
|
1128
|
+
input[x] = evalME(t, here, st, m);
|
|
1129
|
+
next = run.step(ps, st, input);
|
|
1130
|
+
}
|
|
1131
|
+
catch { /* refused: another */ }
|
|
1132
|
+
}
|
|
1133
|
+
if (!next)
|
|
1134
|
+
break;
|
|
1135
|
+
if (was && pending(ps, next)) {
|
|
1136
|
+
const e = { from: 0, to: 0, s: here, t: env(ps, next) };
|
|
1137
|
+
(h ? strong : weak).set(id([e.s, e.t]), e);
|
|
1138
|
+
}
|
|
1139
|
+
st = next;
|
|
1140
|
+
}
|
|
1141
|
+
}
|
|
1142
|
+
const samples = [[...points.values()].map(env => ({ env, calls: new Set() }))];
|
|
1143
|
+
const { comps, left } = lexRanks([0], [node], samples, [...strong.values()], shared, until, false, [...weak.values()]);
|
|
1144
|
+
if (!comps.length || left)
|
|
1145
|
+
return { proven: false, checked: false, why: `no linear rank, in order, that ${strong.size} helpful steps sampled lower and ${weak.size} others keep` };
|
|
1146
|
+
const rank = comps.map(c => compE(c, 0, node, shared)), text = `(${rank.map(showE).join(', ')})`, lv = { ...l, rank };
|
|
1147
|
+
const first = withDeadline(until, () => proveLiveness(m, laws, lv));
|
|
1148
|
+
if (first.proven && first.checked)
|
|
1149
|
+
return { proven: true, checked: true, rank, laws: [], found: { rank: text, laws: [] } };
|
|
815
1150
|
const ms = until - performance.now();
|
|
816
1151
|
if (ms > 300) {
|
|
817
|
-
const inf = inferLaws(m, laws, [stay, cover, ...helps], { ms });
|
|
1152
|
+
const { stay, cover, helps } = livenessParts(m, lv), inf = inferLaws(m, laws, [stay, cover, ...helps], { ms });
|
|
818
1153
|
const kept = inf.found.filter(f => !f.law.forall && !f.law.keys?.length), more = kept.map(f => f.law);
|
|
819
1154
|
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) }
|
|
1155
|
+
const again = withDeadline(until + 5000, () => proveLiveness(m, [...laws, ...more], lv));
|
|
1156
|
+
if (again.proven && again.checked)
|
|
1157
|
+
return { proven: true, checked: true, rank, laws: more, found: { rank: text, laws: kept.map(f => f.text) } };
|
|
823
1158
|
}
|
|
824
1159
|
}
|
|
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;
|
|
1160
|
+
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
1161
|
}
|
|
827
1162
|
export function cycleLemmas(m, laws, stepLaws = [], opts = {}) {
|
|
828
1163
|
const until = performance.now() + (opts.ms ?? 10_000);
|
|
829
1164
|
const fns = Object.entries(m.fns ?? {}).filter(([, f]) => !f.recur && (f.arity ?? 1) === 1);
|
|
830
|
-
|
|
1165
|
+
const fns2 = Object.entries(m.fns ?? {}).filter(([, f]) => !f.recur && f.arity === 2 && f.call);
|
|
1166
|
+
if ((!fns.length && !fns2.length) || !m.facts?.length)
|
|
831
1167
|
return { proven: false, checked: false, found: [], why: 'no function known by its facts' };
|
|
832
1168
|
const facts = m.facts.map(f => ({ name: f.name, keys: [...(f.forall ? [f.forall] : []), ...(f.keys ?? [])], holds: f.holds }));
|
|
833
1169
|
const values = {};
|
|
834
1170
|
for (const [name, f] of fns)
|
|
835
1171
|
values[name] = (q) => fnValue(f, q);
|
|
1172
|
+
for (const [name, f] of fns2) {
|
|
1173
|
+
const g = compileFunction([], `return (${f.call})`)();
|
|
1174
|
+
values[name] = (a, b) => g(a, b);
|
|
1175
|
+
}
|
|
836
1176
|
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 = [];
|
|
1177
|
+
const big = Math.max(24, ...ts), x = 'k$', y = 'j$', claims = [];
|
|
838
1178
|
for (const [name] of fns) {
|
|
839
1179
|
const f = values[name];
|
|
840
1180
|
if (!f)
|
|
@@ -872,14 +1212,72 @@ export function cycleLemmas(m, laws, stepLaws = [], opts = {}) {
|
|
|
872
1212
|
if (vals.slice(vals.length >> 1).includes(top))
|
|
873
1213
|
claims.push({ name: `${name}(k) ≤ ${top}`, concl: le(read, k(top)) });
|
|
874
1214
|
}
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
1215
|
+
// A function of two arguments: over a grid of both, the line that fits exactly, and each bound by 0, either argument or
|
|
1216
|
+
// their sum plus the tightest constant (above, and below where it is one): a constant above only where the values
|
|
1217
|
+
// reach it below half the grid in both arguments (not the grid's own size).
|
|
1218
|
+
for (const [name] of fns2) {
|
|
1219
|
+
const f = values[name], B = Math.min(big, 24), pts = [];
|
|
1220
|
+
for (let a = 0; a <= B; a++)
|
|
1221
|
+
for (let b = 0; b <= B; b++) {
|
|
1222
|
+
let z;
|
|
1223
|
+
try {
|
|
1224
|
+
z = f(a, b);
|
|
1225
|
+
}
|
|
1226
|
+
catch {
|
|
1227
|
+
continue;
|
|
1228
|
+
}
|
|
1229
|
+
if (Number.isSafeInteger(z) && z >= 0)
|
|
1230
|
+
pts.push({ env: { [x]: a, [y]: b }, value: z });
|
|
1231
|
+
}
|
|
1232
|
+
if (pts.length < 16)
|
|
1233
|
+
continue;
|
|
1234
|
+
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));
|
|
1235
|
+
const line = fitExact([x, y], pts, until);
|
|
1236
|
+
if (line) {
|
|
1237
|
+
claims.push({ name: `${name}(k, j) = ${show(line.coef, line.c)}`, concl: eqC(read, linE(line.coef, line.c)) });
|
|
1238
|
+
continue;
|
|
1239
|
+
}
|
|
1240
|
+
for (const form of [{}, { [x]: 1 }, { [y]: 1 }, { [x]: 1, [y]: 1 }]) {
|
|
1241
|
+
const at = (p) => Object.entries(form).reduce((acc, [n, q]) => acc + q * p.env[n], 0);
|
|
1242
|
+
const d = Math.max(...pts.map(p => p.value - at(p))), lo = Math.min(...pts.map(p => p.value - at(p)));
|
|
1243
|
+
const reached = pts.some(p => p.value - at(p) === d && p.env[x] <= B / 2 && p.env[y] <= B / 2);
|
|
1244
|
+
if (Object.keys(form).length ? d <= 0 || reached : reached)
|
|
1245
|
+
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)) });
|
|
1246
|
+
if (Object.keys(form).length && lo >= 0)
|
|
1247
|
+
claims.push({ name: `${name}(k, j) ≥ ${show(form, lo)}`, concl: le(linE(form, lo), read) });
|
|
1248
|
+
}
|
|
1249
|
+
}
|
|
1250
|
+
// Those of two arguments each proven alone (their keys are pairings, products: a goal that reads several such claims
|
|
1251
|
+
// at once is slow to refute), the rest together; the proof the nodes of each, side by side (no node of one uses
|
|
1252
|
+
// another's).
|
|
1253
|
+
const unique = [...new Map(claims.map(c => [c.name, c])).values()], two = (c) => m.fns?.[readsOf(c.concl)[0]?.map ?? '']?.arity === 2;
|
|
1254
|
+
const proofs = new Map();
|
|
1255
|
+
for (const c of unique.filter(two)) {
|
|
1256
|
+
if (performance.now() >= until)
|
|
1257
|
+
break;
|
|
1258
|
+
const o = findCycles(facts, [c], { ms: Math.min(1000, (until - performance.now()) * 0.1), values, houdini: true, generalize: false });
|
|
1259
|
+
if (o.proven && o.checked && o.proof)
|
|
1260
|
+
proofs.set(c.name, o.proof);
|
|
1261
|
+
}
|
|
1262
|
+
const ones = unique.filter(c => !two(c));
|
|
1263
|
+
if (!ones.length && !proofs.size)
|
|
1264
|
+
return { proven: false, checked: false, found: [], why: unique.length ? 'no lemma proven' : 'no lemma fits the values' };
|
|
1265
|
+
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: [] };
|
|
1266
|
+
if ((!out.proven || !out.checked || !out.kept?.length) && !proofs.size)
|
|
880
1267
|
return { proven: false, checked: false, found: [], why: out.why ?? 'no lemma proven' };
|
|
881
|
-
const
|
|
882
|
-
|
|
1268
|
+
const kept = [...(out.proven && out.checked ? out.kept ?? [] : []), ...proofs.keys()];
|
|
1269
|
+
const sideBySide = (ps) => {
|
|
1270
|
+
if (!ps.length)
|
|
1271
|
+
return undefined;
|
|
1272
|
+
let at = 0;
|
|
1273
|
+
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 })) })); });
|
|
1274
|
+
return { facts: ps[0].facts, nodes };
|
|
1275
|
+
};
|
|
1276
|
+
const asLaw = (c) => {
|
|
1277
|
+
const holds = c.hyps?.length ? { op: 'or', xs: [{ op: 'not', x: and(...c.hyps) }, c.concl] } : c.concl;
|
|
1278
|
+
return { law: { name: c.name, forall: x, ...(varsOf(holds).includes(y) ? { keys: [y] } : {}), holds }, text: c.name };
|
|
1279
|
+
};
|
|
1280
|
+
let found = unique.filter(c => kept.includes(c.name)).map(asLaw);
|
|
883
1281
|
const proveWith = (fs) => {
|
|
884
1282
|
const mm = { ...m, facts: [...m.facts, ...fs.map(f => f.law)] };
|
|
885
1283
|
const r = withDeadline(until, () => (stepLaws.length ? proveStepLaws(mm, laws, stepLaws) : proveLaws(mm, laws)));
|
|
@@ -895,5 +1293,7 @@ export function cycleLemmas(m, laws, stepLaws = [], opts = {}) {
|
|
|
895
1293
|
if (again.ok)
|
|
896
1294
|
(found = rest, best = again);
|
|
897
1295
|
}
|
|
898
|
-
|
|
1296
|
+
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)] : []))]);
|
|
1297
|
+
const proof = merged && checkCyclic(merged) ? merged : undefined;
|
|
1298
|
+
return { proven: true, checked: true, machine: best.machine, found, ...(proof ? { proof } : {}) };
|
|
899
1299
|
}
|