gdp-wired 0.17.0 → 0.19.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 +10 -3
- package/dist/core/cycles.d.ts +73 -0
- package/dist/core/cycles.js +899 -0
- package/dist/core/cyclic.d.ts +106 -6
- package/dist/core/cyclic.js +1138 -52
- package/dist/core/data.d.ts +258 -0
- package/dist/core/data.js +1858 -0
- package/dist/core/dsl.d.ts +7 -1
- package/dist/core/dsl.js +43 -10
- package/dist/core/translate.d.ts +30 -0
- package/dist/core/translate.js +572 -0
- package/dist/kernel/prove.d.ts +18 -0
- package/dist/kernel/prove.js +272 -15
- package/dist/lean-kernel/emit.d.ts +95 -1
- package/dist/lean-kernel/emit.js +615 -83
- package/dist/lean-kernel/library.js +4 -1
- package/package.json +10 -2
package/dist/kernel/prove.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { add, k, refuteFast, scale, solve, sub, v } from "../core/solver.js";
|
|
2
|
-
import {
|
|
2
|
+
import { checkRefutation } from "../core/check.js";
|
|
3
|
+
import { allowNode, certCyclic, certMs, certOn, certSearch, closingInsts, crossCyclic, cyclicOn, cycLog, hasQuantified, machineNodes, proveCyclic, qflagNodes, reqTS, searchedStepNodes, skMachine, timedKind, withWitness, translatedProof, uncoveredWhy } from "../core/cyclic.js";
|
|
3
4
|
import { assumed, atomOf, boundOf, canon, canonFlip, columnTerm, decided, factorsOf, isCell, isMonomial, stepZone, check, checkLaws, checkStepLaws, historyMachine, leafConstraints, leafConstraintsReusing, reusing, linear, livenessHelpful, livenessParts, obligations, plainKey, progressObligations, stepLawObligations, trueC, truth, unreachable, witnessesOf, withProducts } from "./kernel.js";
|
|
4
5
|
// The atoms the path leaves open, in evaluation order: deciding any of them makes progress.
|
|
5
6
|
function openE(e, path, out) {
|
|
@@ -77,7 +78,10 @@ function openH(c, path, out, positive = true) {
|
|
|
77
78
|
if (out.length === before && !positive && c.op === '=' && truth(c, path) === undefined)
|
|
78
79
|
openC(c, path, out);
|
|
79
80
|
}
|
|
80
|
-
// Pairs of reads of one map whose keys the path doesn't yet compare.
|
|
81
|
+
// Pairs of reads of one map whose keys the path doesn't yet compare. With `pairedKeys.skip` (core/data.ts), reads at
|
|
82
|
+
// keys a pairing builds ((a + b)² + b, keys whose difference has monomials) are left uncompared: that encoding states
|
|
83
|
+
// their congruence as facts, and comparing their keys only brings products into leaves its linear rows decide.
|
|
84
|
+
export const pairedKeys = { skip: false };
|
|
81
85
|
function openCells(rows, path) {
|
|
82
86
|
const names = [...new Set(rows.flatMap(r => Object.keys(r.lin)))].filter(isCell);
|
|
83
87
|
const out = [];
|
|
@@ -87,7 +91,27 @@ function openCells(rows, path) {
|
|
|
87
91
|
if (p.slice(0, p.indexOf('[')) !== q.slice(0, q.indexOf('[')))
|
|
88
92
|
continue;
|
|
89
93
|
const d = sub(parse(keyText(p)), parse(keyText(q)));
|
|
94
|
+
if (pairedKeys.skip && Object.keys(d).some(isMonomial))
|
|
95
|
+
continue;
|
|
90
96
|
const dec = (lin) => path.get(canon(lin))?.holds ?? (path.has(canonFlip(lin)) ? !path.get(canonFlip(lin)).holds : undefined);
|
|
97
|
+
// Two cells of a run's map (core/cyclic.ts: time u and key k at u + k·Ω) at keys k·Ω and k'·Ω: k = k' first, a
|
|
98
|
+
// linear atom; the keys' own difference only where k = k' (its other side then refuted by (k − k')·Ω = 0, a product).
|
|
99
|
+
const q0 = cellProducts.on ? quotient(d) : undefined;
|
|
100
|
+
if (q0) {
|
|
101
|
+
const qa = dec(q0), qb = dec(scale(q0, -1));
|
|
102
|
+
if (qa === undefined) {
|
|
103
|
+
out.push(q0);
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
if (qa === true && qb === undefined) {
|
|
107
|
+
out.push(scale(q0, -1));
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
if (!(qa && qb))
|
|
111
|
+
continue;
|
|
112
|
+
cellDiffs.set(canon(d), q0);
|
|
113
|
+
cellDiffs.set(canon(scale(d, -1)), scale(q0, -1));
|
|
114
|
+
}
|
|
91
115
|
const a = dec(d), b = dec(scale(d, -1));
|
|
92
116
|
if (a === undefined)
|
|
93
117
|
out.push(d);
|
|
@@ -96,6 +120,24 @@ function openCells(rows, path) {
|
|
|
96
120
|
}
|
|
97
121
|
return out;
|
|
98
122
|
}
|
|
123
|
+
// A key difference q·F over monomials that share one factor F (Ω), with no other part: q, else undefined.
|
|
124
|
+
const quotient = (d) => {
|
|
125
|
+
const keys = Object.keys(d);
|
|
126
|
+
if (!keys.length || keys.some(key => key === '1' || !isMonomial(key)))
|
|
127
|
+
return undefined;
|
|
128
|
+
const shared = factorsOf(keys[0]).find(f => keys.every(key => factorsOf(key).includes(f)));
|
|
129
|
+
if (!shared)
|
|
130
|
+
return undefined;
|
|
131
|
+
const out = {};
|
|
132
|
+
for (const key of keys) {
|
|
133
|
+
const fs = [...factorsOf(key)];
|
|
134
|
+
fs.splice(fs.indexOf(shared), 1);
|
|
135
|
+
out[fs.join('*')] = (out[fs.join('*')] ?? 0) + d[key];
|
|
136
|
+
}
|
|
137
|
+
return out;
|
|
138
|
+
};
|
|
139
|
+
// The cells' key differences split at their quotient's equality, by canonical text: their quotient.
|
|
140
|
+
const cellDiffs = new Map();
|
|
99
141
|
function parse(text) {
|
|
100
142
|
const out = {};
|
|
101
143
|
let depth = 0, start = 0;
|
|
@@ -225,7 +267,7 @@ function growPlain(goal, path, budget) {
|
|
|
225
267
|
return { stuck: 'unknown', path, why: outOfBudget(goal, budget.nodes > 200_000 ? 'nodes' : 'time', path.size) };
|
|
226
268
|
// Rows that read a term column only once a cut has put one on the path; before, the rows a choice-free search uses
|
|
227
269
|
// (what it saw before term columns), renumbered to the whole leaf's when a leaf is found.
|
|
228
|
-
const full = timed(() =>
|
|
270
|
+
const full = timed(() => rowsAt(goal, path), ms => { profile.rowsMs += ms; });
|
|
229
271
|
const columns = [...path.values()].some(a => Object.keys(a.lin).some(key => key.startsWith('⟦')));
|
|
230
272
|
const idx = full.flatMap((r, i) => (columns || !Object.keys(r.lin).some(key => key.startsWith('⟦')) ? [i] : []));
|
|
231
273
|
const rows = idx.map(i => full[i]), at = (i) => (i < rows.length ? idx[i] : full.length + i - rows.length);
|
|
@@ -278,6 +320,21 @@ function growPlain(goal, path, budget) {
|
|
|
278
320
|
return { tree: { leaf: fast.steps }, nodes: budget.nodes };
|
|
279
321
|
}
|
|
280
322
|
let split = next ?? (verdict.kind === 'unknown' ? bisect(rows, path) ?? fractional(verdict.point, path) : undefined);
|
|
323
|
+
// A cell difference's failing side (k = k' on the path, the keys apart): (k − k')·Ω = 0 refutes it, a product of each
|
|
324
|
+
// of the path's two atoms k − k' <= 0 and k' − k <= 0 with Ω >= 0.
|
|
325
|
+
if (cellProducts.on && path.size) {
|
|
326
|
+
const [id, last] = [...path].at(-1), q0 = !last.holds ? cellDiffs.get(id) : undefined;
|
|
327
|
+
if (q0) {
|
|
328
|
+
const at = (lin) => rows.findIndex(r => canon(r.lin) === canon(lin));
|
|
329
|
+
const d = last.lin, f = factorsOf(Object.keys(d)[0]).find(x => !Object.keys(q0).includes(x) && Object.keys(d).every(key => factorsOf(key).includes(x)));
|
|
330
|
+
const e1 = at(q0), e2 = at(scale(q0, -1)), sign = f === undefined ? -1 : at({ [f]: -1 });
|
|
331
|
+
if (e1 >= 0 && e2 >= 0 && sign >= 0) {
|
|
332
|
+
const pairs = [[e1, sign], [e2, sign]], out = solve(withProducts(rows, pairs));
|
|
333
|
+
if (out.kind === 'refuted')
|
|
334
|
+
return { tree: leafOf(out.steps, pairs), nodes: budget.nodes };
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
}
|
|
281
338
|
// Products: with monomials about, try every pair of rows multiplied; failing that, split a factor at zero.
|
|
282
339
|
const monomials = rows.some(r => Object.keys(r.lin).some(isMonomial));
|
|
283
340
|
// A witness that multiplies out (each monomial's value is its factors' product) is a real model: nothing to refute.
|
|
@@ -500,6 +557,24 @@ function squareForms(rows) {
|
|
|
500
557
|
// the decision's own row, and any row only it makes, are not there), every split kept. The derivations are the same
|
|
501
558
|
// arithmetic over the same rows, and the kernel checks the whole tree in the end. Undefined at the first leaf that
|
|
502
559
|
// needs the decision.
|
|
560
|
+
// A leaf's rows, by its path's decisions in order (what they are a function of): a lift reads every leaf of a subtree
|
|
561
|
+
// at its own path, found when it was grown, and at the path without the split, which the next lift up reads again.
|
|
562
|
+
const rowsSeen = new WeakMap();
|
|
563
|
+
function rowsAt(goal, path) {
|
|
564
|
+
let byPath = rowsSeen.get(goal);
|
|
565
|
+
if (!byPath)
|
|
566
|
+
rowsSeen.set(goal, (byPath = new Map()));
|
|
567
|
+
let id = '';
|
|
568
|
+
for (const [key, a] of path)
|
|
569
|
+
id += `${a.holds ? '+' : '-'}${key}|`;
|
|
570
|
+
let rows = byPath.get(id);
|
|
571
|
+
if (!rows) {
|
|
572
|
+
if (byPath.size >= 4096)
|
|
573
|
+
byPath.clear();
|
|
574
|
+
byPath.set(id, (rows = leafConstraintsReusing(goal, path)));
|
|
575
|
+
}
|
|
576
|
+
return rows;
|
|
577
|
+
}
|
|
503
578
|
function lift(goal, tree, from, to) {
|
|
504
579
|
const own = to.size; // the decision's row: the last of `from`'s path rows, below every later split's
|
|
505
580
|
const reads = (t) => ('leaf' in t ? t.leaf.some(st => st.kind === 'input' && st.index === own) || (t.products ?? []).some(([i, j]) => i === own || j === own) : reads(t.yes) || reads(t.no));
|
|
@@ -514,7 +589,7 @@ function lift(goal, tree, from, to) {
|
|
|
514
589
|
const no = yes && go(t.no, new Map(a).set(sid, { lin: t.split, holds: false }), new Map(b).set(sid, { lin: t.split, holds: false }));
|
|
515
590
|
return yes && no ? { split: t.split, yes, no } : undefined;
|
|
516
591
|
}
|
|
517
|
-
const ra =
|
|
592
|
+
const ra = rowsAt(goal, a), rb = rowsAt(goal, b), at = new Map();
|
|
518
593
|
rb.forEach((r, i) => { const key = canon(r.lin); if (!at.has(key))
|
|
519
594
|
at.set(key, i); });
|
|
520
595
|
const map = (i) => (i < ra.length ? at.get(canon(ra[i].lin)) : undefined);
|
|
@@ -540,6 +615,86 @@ function lift(goal, tree, from, to) {
|
|
|
540
615
|
};
|
|
541
616
|
return go(tree, from, to);
|
|
542
617
|
}
|
|
618
|
+
// A tree for a weaker goal (some hypotheses left out, some conjuncts of one left out) made a tree for the whole: at any
|
|
619
|
+
// path a leaf's rows are among the whole goal's (each hypothesis' facts are a hypothesis' own, the cells, terms and
|
|
620
|
+
// naturals they read a subset), so each input is renumbered to the same row there, a square or product past the rows
|
|
621
|
+
// moved by the difference. Where a dropped conjunct makes the whole hypothesis false under the path, its row 1 <= 0
|
|
622
|
+
// refutes the leaf alone; a leaf neither maps is grown again on the whole goal.
|
|
623
|
+
// `opaque`: variables the weaker goal has for terms of the whole (a step's value, read the same in two hypotheses), each
|
|
624
|
+
// read as the term's linear form at the whole goal's path; a split the whole's path already decides is followed, not
|
|
625
|
+
// made again.
|
|
626
|
+
export function widen(part, whole, tree, opaque = {}, pp = new Map(), wp = pp) {
|
|
627
|
+
const names = Object.keys(opaque);
|
|
628
|
+
const rename = (lin) => {
|
|
629
|
+
if (!names.length)
|
|
630
|
+
return lin;
|
|
631
|
+
let out = {};
|
|
632
|
+
for (const [key, c] of Object.entries(lin)) {
|
|
633
|
+
const e = opaque[key];
|
|
634
|
+
if (e) {
|
|
635
|
+
const l = linear(e, wp);
|
|
636
|
+
if (!l)
|
|
637
|
+
return undefined;
|
|
638
|
+
out = add(out, scale(l, c));
|
|
639
|
+
}
|
|
640
|
+
else if (names.some(n => key.includes(n)))
|
|
641
|
+
return undefined;
|
|
642
|
+
else
|
|
643
|
+
out = add(out, { [key]: c });
|
|
644
|
+
}
|
|
645
|
+
return out;
|
|
646
|
+
};
|
|
647
|
+
if (!('leaf' in tree)) {
|
|
648
|
+
const id = canon(tree.split), wl = rename(tree.split);
|
|
649
|
+
if (!wl) {
|
|
650
|
+
const o = grow(whole, wp);
|
|
651
|
+
return 'tree' in o ? o.tree : undefined;
|
|
652
|
+
}
|
|
653
|
+
const wid = canon(wl), known = Object.keys(wl).every(key => key === '1') ? (wl['1'] ?? 0) <= 0 : wp.has(wid) ? wp.get(wid).holds : wp.has(canonFlip(wl)) ? !wp.get(canonFlip(wl)).holds : undefined;
|
|
654
|
+
if (known !== undefined)
|
|
655
|
+
return widen(part, whole, known ? tree.yes : tree.no, opaque, new Map(pp).set(id, { lin: tree.split, holds: known }), wp);
|
|
656
|
+
const yes = widen(part, whole, tree.yes, opaque, new Map(pp).set(id, { lin: tree.split, holds: true }), new Map(wp).set(wid, { lin: wl, holds: true }));
|
|
657
|
+
const no = yes && widen(part, whole, tree.no, opaque, new Map(pp).set(id, { lin: tree.split, holds: false }), new Map(wp).set(wid, { lin: wl, holds: false }));
|
|
658
|
+
return yes && no ? { split: wl, yes, no } : undefined;
|
|
659
|
+
}
|
|
660
|
+
const rp = rowsAt(part, pp), rw = rowsAt(whole, wp), at = new Map();
|
|
661
|
+
rw.forEach((r, i) => { const key = canon(r.lin); if (!at.has(key))
|
|
662
|
+
at.set(key, i); });
|
|
663
|
+
const map = (i) => { if (i >= rp.length)
|
|
664
|
+
return i - rp.length + rw.length; const l = rename(rp[i].lin); return l && at.get(canon(l)); };
|
|
665
|
+
const leaf = [], products = [], squares = (tree.squares ?? []).map(rename);
|
|
666
|
+
let ok = squares.every(Boolean);
|
|
667
|
+
for (const st of ok ? tree.leaf : []) {
|
|
668
|
+
if (st.kind !== 'input') {
|
|
669
|
+
leaf.push(st);
|
|
670
|
+
continue;
|
|
671
|
+
}
|
|
672
|
+
const j = map(st.index);
|
|
673
|
+
if (j === undefined) {
|
|
674
|
+
ok = false;
|
|
675
|
+
break;
|
|
676
|
+
}
|
|
677
|
+
leaf.push({ ...st, index: j });
|
|
678
|
+
}
|
|
679
|
+
for (const [i, j] of ok ? tree.products ?? [] : []) {
|
|
680
|
+
const pi = map(i), pj = map(j);
|
|
681
|
+
if (pi === undefined || pj === undefined) {
|
|
682
|
+
ok = false;
|
|
683
|
+
break;
|
|
684
|
+
}
|
|
685
|
+
products.push([pi, pj]);
|
|
686
|
+
}
|
|
687
|
+
// (renamed, a derivation through products or cuts is replayed: a cut's divisor may not divide the terms' coefficients)
|
|
688
|
+
if (ok && names.length && !checkRefutation(withProducts(rw, products, squares), leaf))
|
|
689
|
+
ok = false;
|
|
690
|
+
if (ok)
|
|
691
|
+
return { leaf, ...(tree.products ? { products } : {}), ...(tree.squares ? { squares: squares } : {}) };
|
|
692
|
+
const absurd = rw.findIndex(r => Object.keys(r.lin).every(key => key === '1') && (r.lin['1'] ?? 0) > 0);
|
|
693
|
+
if (absurd >= 0)
|
|
694
|
+
return { leaf: [{ kind: 'input', index: absurd }] };
|
|
695
|
+
const out = grow(whole, wp);
|
|
696
|
+
return 'tree' in out ? out.tree : undefined;
|
|
697
|
+
}
|
|
543
698
|
// Whether each hypothesis reads only names the goal's conclusion (its last hypothesis) reads.
|
|
544
699
|
const nearSeen = new WeakMap();
|
|
545
700
|
const namesOf = (t) => (t === undefined ? [] : [...JSON.stringify(t).matchAll(/"(?:name|map)":"([^"]+)"/g)].map(m => m[1]));
|
|
@@ -1051,9 +1206,9 @@ function triggered(m, laws, inst, build, focus = []) {
|
|
|
1051
1206
|
// fewer hypotheses (a law about pairs of items with 900 instance hypotheses and 30 leaves using a few of them, say).
|
|
1052
1207
|
export const minimal = { on: false };
|
|
1053
1208
|
// The hypotheses a tree's leaves read: each leaf's input rows, and the rows its products multiply.
|
|
1054
|
-
function usedHyps(goal, tree, path = new Map(), out = new Set()) {
|
|
1209
|
+
export function usedHyps(goal, tree, path = new Map(), out = new Set()) {
|
|
1055
1210
|
if ('leaf' in tree) {
|
|
1056
|
-
const rows =
|
|
1211
|
+
const rows = rowsAt(goal, path), n = rows.length, sq = tree.squares?.length ?? 0;
|
|
1057
1212
|
const mark = (i) => {
|
|
1058
1213
|
if (i < n) {
|
|
1059
1214
|
const s = rows[i].src;
|
|
@@ -1207,15 +1362,54 @@ function proveLawsAsGiven(m, laws) {
|
|
|
1207
1362
|
focus: [...(law.forall ? [`${law.forall}#`] : []), ...(law.keys ?? [])],
|
|
1208
1363
|
});
|
|
1209
1364
|
});
|
|
1210
|
-
|
|
1365
|
+
// (GDP_CERT=cyclic: a covered machine's certificate is its cyclic proof, core/cyclic.ts's certCyclic; the usual trees
|
|
1366
|
+
// are its input, not checked again)
|
|
1367
|
+
// (what no instance covers, read only where a cyclic mode is on: the usual route's own proofs, inferLaws' trial ones
|
|
1368
|
+
// among them, do nothing more than before)
|
|
1369
|
+
// A machine with a history: its laws are its plain machine's (historyMachine; the record goals leaf goals beside them).
|
|
1370
|
+
const modes = cyclicOn() || certOn(), hist = modes && m.history ? historyMachine(m) : undefined, mc = hist ? hist.machine : m;
|
|
1371
|
+
const why = modes ? uncoveredWhy(mc, laws) : undefined;
|
|
1372
|
+
// (flags that hold at every key: searched, where GDP_CERT_SEARCH=1)
|
|
1373
|
+
const q = why === 'a flag at every key' && certOn() && certSearch(), cert = certOn() && (!why || q);
|
|
1374
|
+
usualCheck.on = !cert || q;
|
|
1375
|
+
const t0 = performance.now(), out = (() => { try {
|
|
1376
|
+
return proveThinned(m, laws, { ...instances });
|
|
1377
|
+
}
|
|
1378
|
+
finally {
|
|
1379
|
+
usualCheck.on = true;
|
|
1380
|
+
} })(), usualMs = performance.now() - t0;
|
|
1381
|
+
if (out.proof)
|
|
1382
|
+
usualProof.last = { m, laws, proof: out.proof };
|
|
1383
|
+
const inst = out.proof?.instances ?? instances;
|
|
1384
|
+
const searched = () => { const { facts, specs } = q ? qflagNodes(mc, laws, inst) : machineNodes(mc, laws, inst); return proveCyclic(facts, specs); };
|
|
1385
|
+
const translated = !q && out.proof ? ((st) => translatedProof(() => JSON.stringify(['machine', mc, laws, inst]), () => timedKind('instance', 0, () => machineNodes(mc, laws, inst, out.proof, st)), st)) : undefined;
|
|
1211
1386
|
if (cyclicOn()) {
|
|
1212
|
-
if (
|
|
1213
|
-
crossCyclic('machine', m.name, out.proven && out.checked,
|
|
1387
|
+
if (!why)
|
|
1388
|
+
crossCyclic('machine', m.name, out.proven && out.checked, searched, usualMs, translated);
|
|
1214
1389
|
else
|
|
1215
|
-
|
|
1390
|
+
cycLog({ kind: 'machine', name: m.name, usual: out.proven && out.checked, uncovered: true, why });
|
|
1391
|
+
}
|
|
1392
|
+
if (cert && out.proven && out.proof) {
|
|
1393
|
+
const until = performance.now() + certMs(), c = q ? withDeadline(until, () => certCyclic('machine', m.name, undefined, searched, usualMs)) : certCyclic('machine', m.name, translated, searched, usualMs);
|
|
1394
|
+
if (q && !c.checked)
|
|
1395
|
+
uncovered('machine', m.name, `${why}: no cyclic proof found${performance.now() > until ? ` within ${certMs()} ms` : ''}`);
|
|
1396
|
+
else {
|
|
1397
|
+
// (a history's record goals checked as they are: leaf goals)
|
|
1398
|
+
const records = !hist || hist.goals.every(g => out.proof.trees[g.name] !== undefined && check(g, out.proof.trees[g.name]));
|
|
1399
|
+
out.checked = c.checked && records;
|
|
1400
|
+
Object.assign(out.proof, { cyclic: c.proof });
|
|
1401
|
+
}
|
|
1216
1402
|
}
|
|
1403
|
+
else if (certOn() && why && out.proven)
|
|
1404
|
+
cycLog({ kind: 'machine', name: m.name, cert: true, uncovered: true, why });
|
|
1217
1405
|
return out;
|
|
1218
1406
|
}
|
|
1407
|
+
// A proof of a kind no cyclic instance covers, counted (`cyclic: {..., uncovered: true}`).
|
|
1408
|
+
const uncovered = (kind, name, why) => cycLog({ kind, name, uncovered: true, why });
|
|
1409
|
+
// Whether the usual proof's trees are checked (off while GDP_CERT=cyclic makes the certificate a cyclic proof).
|
|
1410
|
+
export const usualCheck = { on: true };
|
|
1411
|
+
// The last machine proof made (core/dsl.ts's recurrences read their machine's, to translate it: core/cyclic.ts).
|
|
1412
|
+
export const usualProof = {};
|
|
1219
1413
|
// Each law with its instances thinned (`triggered`) where that proves it, else with all of them.
|
|
1220
1414
|
function proveThinned(m, laws, instances) {
|
|
1221
1415
|
const trees = {}, fulls = { ...instances };
|
|
@@ -1261,7 +1455,7 @@ function proveThinned(m, laws, instances) {
|
|
|
1261
1455
|
leaves += countLeaves(out.tree);
|
|
1262
1456
|
}
|
|
1263
1457
|
const proof = { instances, trees };
|
|
1264
|
-
return { proven: true, nodes, leaves, checked: checkLaws(m, laws, proof), proof };
|
|
1458
|
+
return { proven: true, nodes, leaves, checked: !usualCheck.on || checkLaws(m, laws, proof), proof };
|
|
1265
1459
|
}
|
|
1266
1460
|
// Every goal of the laws with these instances.
|
|
1267
1461
|
function proveWith(m, laws, instances) {
|
|
@@ -1276,7 +1470,7 @@ function proveWith(m, laws, instances) {
|
|
|
1276
1470
|
leaves += countLeaves(out.tree);
|
|
1277
1471
|
}
|
|
1278
1472
|
const proof = { instances, trees };
|
|
1279
|
-
return { proven: true, nodes, leaves, checked: checkLaws(m, laws, proof), proof };
|
|
1473
|
+
return { proven: true, nodes, leaves, checked: !usualCheck.on || checkLaws(m, laws, proof), proof };
|
|
1280
1474
|
}
|
|
1281
1475
|
export function proveStepLaws(m, given, stepLaws) {
|
|
1282
1476
|
const base = proveLaws(m, given);
|
|
@@ -1296,6 +1490,7 @@ export function proveStepLaws(m, given, stepLaws) {
|
|
|
1296
1490
|
function proveStepLawsAsGiven(m, laws, stepLaws, base) {
|
|
1297
1491
|
if (!base.proof)
|
|
1298
1492
|
return { proven: false, nodes: base.nodes, leaves: base.leaves, checked: false };
|
|
1493
|
+
const t0 = performance.now();
|
|
1299
1494
|
const plain = m.history ? historyMachine(m).machine : m;
|
|
1300
1495
|
// Each step law its own instances, from its own goal: the keys the step and the laws read, and its own (a k-step
|
|
1301
1496
|
// law's later frames read keys no other step law has); where positions shift or a law is about several keys, the
|
|
@@ -1350,12 +1545,44 @@ function proveStepLawsAsGiven(m, laws, stepLaws, base) {
|
|
|
1350
1545
|
leaves += countLeaves(out.tree);
|
|
1351
1546
|
}
|
|
1352
1547
|
const proof = { laws: base.proof, instances, ...(Object.keys(perStep).length ? { perStep } : {}), trees };
|
|
1353
|
-
|
|
1548
|
+
const out = { proven: true, nodes, leaves, checked: checkStepLaws(m, laws, stepLaws, proof), proof };
|
|
1549
|
+
// Step laws of a machine the cyclic instances cover (one step, or several over no map) are translated with the laws
|
|
1550
|
+
// (core/cyclic.ts); the rest are counted as no instance's.
|
|
1551
|
+
if (!cyclicOn() && !certOn())
|
|
1552
|
+
return out;
|
|
1553
|
+
// (a machine with a history: its plain machine's; flags at every key and k-step laws over a map searched, where
|
|
1554
|
+
// GDP_CERT_SEARCH=1)
|
|
1555
|
+
const why = uncoveredWhy(plain, laws), one = stepLaws.filter(sl => (sl.steps ?? 1) === 1 || (!Object.keys(plain.maps ?? {}).length && !plain.inputMaps?.length));
|
|
1556
|
+
if (!why && one.length && out.checked) {
|
|
1557
|
+
const steps = one.map(sl => { const inst = perStep[sl.name] ?? instances, goal = stepLawObligations(m, laws, inst, sl)[0]; return { sl, inst, goal, tree: trees[goal.name] }; });
|
|
1558
|
+
const translated = (st) => translatedProof(() => JSON.stringify(['steps', plain, laws, base.proof.instances, steps.map(x => [x.sl, x.inst])]), reused => timedKind('instance', 0, () => machineNodes(plain, laws, base.proof.instances, { trees: base.proof.trees, steps, lawsMade: reused }, st)), st, () => JSON.stringify(['machine', plain, laws, base.proof.instances]));
|
|
1559
|
+
const usualMs = performance.now() - t0;
|
|
1560
|
+
if (cyclicOn())
|
|
1561
|
+
crossCyclic('step law', m.name, true, undefined, usualMs, translated);
|
|
1562
|
+
if (certOn())
|
|
1563
|
+
out.checked = certCyclic('step law', m.name, translated, undefined, usualMs).checked;
|
|
1564
|
+
}
|
|
1565
|
+
const rest = stepLaws.filter(sl => why || !one.includes(sl));
|
|
1566
|
+
if (certOn() && certSearch() && out.checked && rest.length && (!why || why === 'a flag at every key')) {
|
|
1567
|
+
// The rest searched: the laws' nodes (searched), each step law's node (core/cyclic.ts's stepLawNodes).
|
|
1568
|
+
const searchedSteps = () => {
|
|
1569
|
+
const b = why ? qflagNodes(plain, laws, base.proof.instances) : machineNodes(plain, laws, base.proof.instances);
|
|
1570
|
+
return proveCyclic(b.facts, [...b.specs, ...searchedStepNodes(plain, laws, rest.map(sl => ({ law: sl, instances: perStep[sl.name] ?? instances })), b.facts, b.specs)]);
|
|
1571
|
+
};
|
|
1572
|
+
const until = performance.now() + certMs(), c = withDeadline(until, () => certCyclic('step law', m.name, undefined, searchedSteps, performance.now() - t0));
|
|
1573
|
+
const none = `no cyclic proof found${performance.now() > until ? ` within ${certMs()} ms` : ''}`;
|
|
1574
|
+
for (const sl of c.checked ? [] : rest)
|
|
1575
|
+
uncovered((sl.steps ?? 1) > 1 ? 'k-step law' : 'step law', m.name, `${why ?? `${sl.steps} steps over a map`}: ${none}`);
|
|
1576
|
+
}
|
|
1577
|
+
else if (cyclicOn() || certOn())
|
|
1578
|
+
for (const sl of rest)
|
|
1579
|
+
uncovered((sl.steps ?? 1) > 1 ? 'k-step law' : 'step law', m.name, why ?? `${sl.steps} steps over a map`);
|
|
1580
|
+
return out;
|
|
1354
1581
|
}
|
|
1355
1582
|
// Progress (kernel.ts's progressObligations): the laws proven first, then each goal, the every-key laws assumed at every
|
|
1356
1583
|
// key the goals read a map at.
|
|
1357
1584
|
export function proveProgress(m, laws, p) {
|
|
1358
|
-
const base = proveLaws(m, laws);
|
|
1585
|
+
const t0 = performance.now(), base = proveLaws(m, laws);
|
|
1359
1586
|
if (!base.proven || !base.checked)
|
|
1360
1587
|
return { proven: false, nodes: base.nodes, leaves: base.leaves, ...(base.failed ? { failed: base.failed } : {}), checked: false };
|
|
1361
1588
|
const ls = base.laws ?? laws;
|
|
@@ -1370,12 +1597,42 @@ export function proveProgress(m, laws, p) {
|
|
|
1370
1597
|
nodes += out.nodes;
|
|
1371
1598
|
leaves += countLeaves(out.tree);
|
|
1372
1599
|
}
|
|
1373
|
-
|
|
1600
|
+
const checked = progressObligations(m, ls, instances, p).every(g => check(g, trees[g.name]));
|
|
1601
|
+
// (GDP_CERT=cyclic with GDP_CERT_SEARCH=1: a request allowed whose claim is that alone, over a machine without a history
|
|
1602
|
+
// or carried maps, gets core/cyclic.ts's allowNode after the laws' nodes, BridgeRules.lean's cycAOk: from every
|
|
1603
|
+
// reachable state, every request meeting the region, its terms and the witnesses withWitness finds is allowed)
|
|
1604
|
+
const alone = JSON.stringify(p.then) === JSON.stringify(trueC) && !m.history && !m.inputMaps?.length && m.assume !== undefined;
|
|
1605
|
+
if (certOn() && certSearch() && alone && checked && base.proof) {
|
|
1606
|
+
const until = performance.now() + certMs(), lp = base.proof, q = hasQuantified(m);
|
|
1607
|
+
const allowed = () => {
|
|
1608
|
+
const b = q ? qflagNodes(m, ls, lp.instances) : machineNodes(m, ls, lp.instances, lp);
|
|
1609
|
+
if (b.why || (b.specs[0]?.rank.length ?? 2) < 2)
|
|
1610
|
+
return { proven: false, checked: false, node: m.name, goal: 'allowed', stuck: b.why ?? 'laws ranked by time alone' };
|
|
1611
|
+
let last = { proven: false, checked: false, node: m.name, goal: 'allowed', stuck: 'no witness' };
|
|
1612
|
+
const found = withWitness(m, p.request, wit => {
|
|
1613
|
+
if (performance.now() > until)
|
|
1614
|
+
return undefined;
|
|
1615
|
+
const w = { op: 'and', xs: [p.when, reqTS(Object.entries(p.request)), reqTS(wit)] };
|
|
1616
|
+
const spec = allowNode(skMachine(m), ls, instances, w, b.facts, b.specs, `${m.name}: allowed`, q ? closingInsts(m, b.facts, [instances, ...Object.values(lp.instances)], [{ op: 'var', name: 't$' }], ls) : []);
|
|
1617
|
+
last = proveCyclic(b.facts, [...b.specs, spec]);
|
|
1618
|
+
return last.proven && last.checked ? last : undefined;
|
|
1619
|
+
});
|
|
1620
|
+
return found?.out ?? last;
|
|
1621
|
+
};
|
|
1622
|
+
const c = withDeadline(until, () => certCyclic('progress', m.name, undefined, allowed, performance.now() - t0));
|
|
1623
|
+
if (!c.checked)
|
|
1624
|
+
uncovered('progress', m.name, `a request allowed: no cyclic proof found${performance.now() > until ? ` within ${certMs()} ms` : ''}`);
|
|
1625
|
+
}
|
|
1626
|
+
else if (cyclicOn() || certOn())
|
|
1627
|
+
uncovered('progress', m.name, alone ? 'a request allowed' : 'a request allowed and a claim after it');
|
|
1628
|
+
return { proven: true, nodes, leaves, checked, instances, trees };
|
|
1374
1629
|
}
|
|
1375
1630
|
// Liveness (kernel.ts's livenessParts): its step laws, proven together. The claim needs no more (a run takes the
|
|
1376
1631
|
// request, so it is allowed); but a request never allowed would leave the claim about no run, so where a helpful request
|
|
1377
1632
|
// names every input a client sends and carries no maps, it is proven allowed in its region too.
|
|
1378
1633
|
export function proveLiveness(m, laws, l) {
|
|
1634
|
+
if (cyclicOn() || certOn())
|
|
1635
|
+
uncovered('liveness', m.name, 'a claim about runs that never end');
|
|
1379
1636
|
const { stay, cover, helps } = livenessParts(m, l);
|
|
1380
1637
|
const a = proveStepLaws(m, laws, [stay, cover, ...helps]);
|
|
1381
1638
|
if (!a.proven || !a.checked)
|
|
@@ -37,6 +37,7 @@ export declare function readable(id: string, laws: Law[], vars: string[], maps:
|
|
|
37
37
|
binders: string;
|
|
38
38
|
hyp: string;
|
|
39
39
|
call: string;
|
|
40
|
+
have?: (j: number) => string;
|
|
40
41
|
}): string;
|
|
41
42
|
export type ProgressCase = {
|
|
42
43
|
id: string;
|
|
@@ -61,6 +62,10 @@ export declare function mmachineToLean(id: string, m: Machine, laws: Law[], opts
|
|
|
61
62
|
stepLaws?: StepLaw[];
|
|
62
63
|
}): string;
|
|
63
64
|
export declare const hasSets: (m: Machine) => boolean;
|
|
65
|
+
export declare function machineLit(mm: Machine, t: {
|
|
66
|
+
e: (x: E) => string;
|
|
67
|
+
c: (x: C) => string;
|
|
68
|
+
}, ix: (name: string) => number, mx: (name: string) => number): string;
|
|
64
69
|
export declare function smachineToLean(id: string, given: Machine, laws: Law[], opts?: {
|
|
65
70
|
stepLaws?: StepLaw[];
|
|
66
71
|
liveness?: Liveness[];
|
|
@@ -96,6 +101,39 @@ export declare function smachineToLean(id: string, given: Machine, laws: Law[],
|
|
|
96
101
|
};
|
|
97
102
|
noFacts?: boolean;
|
|
98
103
|
}): string;
|
|
104
|
+
export declare const cyclicCerts: () => boolean;
|
|
105
|
+
export declare const certReport: {
|
|
106
|
+
cyclic: string[];
|
|
107
|
+
usual: {
|
|
108
|
+
id: string;
|
|
109
|
+
why: string;
|
|
110
|
+
}[];
|
|
111
|
+
translatedSteps: string[];
|
|
112
|
+
};
|
|
113
|
+
export declare function cycMachineToLean(id: string, given: Machine, laws: Law[], opts?: {
|
|
114
|
+
stepLaws?: StepLaw[];
|
|
115
|
+
liveness?: Liveness[];
|
|
116
|
+
simp?: string[];
|
|
117
|
+
free?: string[];
|
|
118
|
+
ghosts?: string[];
|
|
119
|
+
after?: (ix: (name: string) => number) => string;
|
|
120
|
+
loop?: {
|
|
121
|
+
loop: Loop;
|
|
122
|
+
};
|
|
123
|
+
noFacts?: boolean;
|
|
124
|
+
refined?: {
|
|
125
|
+
impl: Machine;
|
|
126
|
+
spec: Machine;
|
|
127
|
+
wit: Record<string, E>;
|
|
128
|
+
stutter: C;
|
|
129
|
+
specPre: number;
|
|
130
|
+
step: StepLaw;
|
|
131
|
+
};
|
|
132
|
+
}): {
|
|
133
|
+
text: string;
|
|
134
|
+
} | {
|
|
135
|
+
why: string;
|
|
136
|
+
};
|
|
99
137
|
type Uses = Record<string, {
|
|
100
138
|
lean?: string;
|
|
101
139
|
recur?: Recurrence;
|
|
@@ -152,15 +190,71 @@ export type LeanItem = {
|
|
|
152
190
|
proof: Cyclic;
|
|
153
191
|
}[];
|
|
154
192
|
after?: (ix: (name: string) => number, mx: (map: string) => number) => string;
|
|
193
|
+
bridge?: CycBridge;
|
|
194
|
+
recur?: CycRecur;
|
|
195
|
+
fold?: {
|
|
196
|
+
fold: Fold;
|
|
197
|
+
laws: Law[];
|
|
198
|
+
};
|
|
199
|
+
data?: true;
|
|
155
200
|
};
|
|
156
201
|
export declare function toLean(items: LeanItem[], opts?: {
|
|
157
202
|
title?: string;
|
|
158
203
|
native?: boolean;
|
|
204
|
+
cyclic?: boolean;
|
|
159
205
|
}): string;
|
|
160
206
|
export declare function leanProject(file: string, opts?: {
|
|
161
207
|
name?: string;
|
|
162
208
|
}): Record<string, string>;
|
|
209
|
+
export type CycBridge = {
|
|
210
|
+
machine: Machine;
|
|
211
|
+
laws: Law[];
|
|
212
|
+
tampered?: {
|
|
213
|
+
what: string;
|
|
214
|
+
machine: Machine;
|
|
215
|
+
}[];
|
|
216
|
+
};
|
|
217
|
+
export type CycRecur = {
|
|
218
|
+
name: string;
|
|
219
|
+
args: string[];
|
|
220
|
+
zero: E;
|
|
221
|
+
next: E;
|
|
222
|
+
lemmas: {
|
|
223
|
+
name: string;
|
|
224
|
+
holds: C;
|
|
225
|
+
}[];
|
|
226
|
+
between: {
|
|
227
|
+
name: string;
|
|
228
|
+
holds: C;
|
|
229
|
+
}[];
|
|
230
|
+
};
|
|
163
231
|
export declare function cyclicToLean(id: string, p: Cyclic, tampered?: {
|
|
164
232
|
what: string;
|
|
165
233
|
proof: Cyclic;
|
|
166
|
-
}[], after?: (ix: (name: string) => number, mx: (map: string) => number) => string
|
|
234
|
+
}[], after?: (ix: (name: string) => number, mx: (map: string) => number) => string, bridge?: CycBridge, recur?: CycRecur, fold?: {
|
|
235
|
+
fold: Fold;
|
|
236
|
+
laws: Law[];
|
|
237
|
+
}): string;
|
|
238
|
+
export declare function cycEncodingLean(id: string, facts: {
|
|
239
|
+
name: string;
|
|
240
|
+
keys: string[];
|
|
241
|
+
holds: C;
|
|
242
|
+
}[], nodes: {
|
|
243
|
+
name: string;
|
|
244
|
+
hyps: C[];
|
|
245
|
+
concl: C;
|
|
246
|
+
}[], b: CycBridge): string;
|
|
247
|
+
export declare function cycRecurCheckLean(id: string, facts: {
|
|
248
|
+
name: string;
|
|
249
|
+
keys: string[];
|
|
250
|
+
holds: C;
|
|
251
|
+
}[], r: CycRecur): string;
|
|
252
|
+
export declare function cycFoldCheckLean(id: string, facts: {
|
|
253
|
+
name: string;
|
|
254
|
+
keys: string[];
|
|
255
|
+
holds: C;
|
|
256
|
+
}[], nodes: {
|
|
257
|
+
name: string;
|
|
258
|
+
hyps: C[];
|
|
259
|
+
concl: C;
|
|
260
|
+
}[], f: Fold, laws: Law[]): string;
|